Skip to content
Commits on Source (4)
language: "python"
language: python
dist: xenial
python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
- 2.7
- 3.5
- 3.6
- 3.7
- pypy2.7-6.0
- pypy3.5-6.0
install:
- pip install flake8
- pip install codecov
before_script:
flake8 .
script:
- coverage run --branch --source=geojson setup.py test
- coverage xml
after_success:
- codecov
stages:
- lint
- test
- deploy
jobs:
include:
- stage: lint
env: TOXENV=flake8
python: 3.7
install: pip install flake8
script: flake8 .
- stage: deploy
env:
python: 3.7
install: skip
script: skip
deploy:
provider: pypi
server: https://jazzband.co/projects/python-geojson/upload
distributions: sdist bdist_wheel
user: jazzband
password:
secure: V2dsQfSMdChZ+G9w4cBcoa/Gmo/kJf2BMaN3sb7pDcAkTHuPYHppfSfj3vWDfpZFNobg8kTed4xjeYdKJ/oxFMpSGBWhGGpzQauzXO8n6LFXM44r/b5uXzyOyzdwwnjWXBn3NkubNU/n3dSjnRr9ZTeXGjiOGTPW1wAWjARJvgg=
on:
tags: true
repo: jazzband/python-geojson
Changes
=======
2.5.0 (2019-07-18)
------------------
- Add "precision" parameter to GeoJSON Object classes with default precision of 6 (0.1m)
- https://github.com/jazzband/python-geojson/pull/131
- Fix bug where `map_geometries()` util was not preserving Feature IDs
- https://github.com/jazzband/python-geojson/pull/128
- https://github.com/jazzband/python-geojson/pull/130
- Remove `crs` module and features to conform to official WGS84-only GeoJSON spec
- https://github.com/jazzband/python-geojson/pull/124
- Set up semi-automatic PyPi releases via Travis/Jazzband
- https://github.com/jazzband/python-geojson/pull/123
2.4.2 (2019-03-12)
------------------
- Tie Travis CI to jazzband instance
- Remove EOL 3.3 and 3.4 version support
- https://github.com/jazzband/python-geojson/pull/120
2.4.1 (2018-10-17)
------------------
......
python-geojson
==============
.. image:: https://img.shields.io/travis/frewsxcv/python-geojson.svg
:target: https://travis-ci.org/frewsxcv/python-geojson
.. image:: https://img.shields.io/codecov/c/github/frewsxcv/python-geojson.svg
:target: https://codecov.io/github/frewsxcv/python-geojson?branch=master
.. image:: https://img.shields.io/travis/jazzband/python-geojson.svg
:target: https://travis-ci.org/jazzband/python-geojson
.. image:: https://img.shields.io/codecov/c/github/jazzband/python-geojson.svg
:target: https://codecov.io/github/jazzband/python-geojson?branch=master
This library contains:
......@@ -21,7 +21,7 @@ This library contains:
Installation
------------
python-geojson is compatible with Python 2.7, 3.3, 3.4, 3.5 and 3.6. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
python-geojson is compatible with Python 2.7, 3.5, 3.6 and 3.7. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
.. code::
......@@ -266,6 +266,22 @@ This encoding/decoding functionality shown in the previous can be extended to cu
>>> geojson.dumps(point_instance, sort_keys=True) # doctest: +ELLIPSIS
'{"coordinates": [52.23..., -19.23...], "type": "Point"}'
Default and custom precision
~~~~~~~~~~~~~~~~~
GeoJSON Object-based classes in this package have an additional `precision` attribute which rounds off
coordinates to 6 decimal places (roughly 0.1 meters) by default and can be customized per object instance.
.. code:: python
>>> from geojson import Point
>>> Point((-115.123412341234, 37.123412341234)) # rounded to 6 decimal places by default
{"coordinates": [-115.123412, 37.123412], "type": "Point"}
>>> Point((-115.12341234, 37.12341234), precision=8) # rounded to 8 decimal places
{"coordinates": [-115.12341234, 37.12341234], "type": "Point"}
Helpful utilities
-----------------
......@@ -369,7 +385,9 @@ generate_random
Development
-----------
To build this project, run :code:`python setup.py build`. To run the unit tests, run :code:`python setup.py test`.
To build this project, run :code:`python setup.py build`.
To run the unit tests, run :code:`python setup.py test`.
To run the style checks, run :code:`flake8` (install `flake8` if needed).
Credits
-------
......@@ -380,6 +398,7 @@ Credits
* Blake Grotewold <hello@grotewold.me>
* Zsolt Ero <zsolt.ero@gmail.com>
* Sergey Romanov <xxsmotur@gmail.com>
* Ray Riga <ray@strongoutput.com>
.. _GeoJSON: http://geojson.org/
......
python-geojson (2.4.1-2) UNRELEASED; urgency=medium
python-geojson (2.5.0-1) unstable; urgency=medium
* Team upload.
* New upstream release.
* Bump Standards-Version to 4.4.0, no changes.
* Update gbp.conf to use --source-only-changes by default.
-- Bas Couwenberg <sebastic@debian.org> Tue, 25 Dec 2018 23:07:04 +0100
-- Bas Couwenberg <sebastic@debian.org> Fri, 19 Jul 2019 07:00:33 +0200
python-geojson (2.4.1-1) unstable; urgency=medium
......
__version__ = "2.4.1"
__version__ = "2.5.0"
__version_info__ = tuple(map(int, __version__.split(".")))
from geojson.base import GeoJSON
class CoordinateReferenceSystem(GeoJSON):
"""
Represents a CRS.
"""
def __init__(self, properties=None, **extra):
super(CoordinateReferenceSystem, self).__init__(**extra)
self["properties"] = properties or {}
class Named(CoordinateReferenceSystem):
"""
Represents a named CRS.
"""
def __init__(self, properties=None, **extra):
super(Named, self).__init__(properties=properties, **extra)
self["type"] = "name"
def __repr__(self):
return super(Named, self).__repr__()
class Linked(CoordinateReferenceSystem):
"""
Represents a linked CRS.
"""
def __init__(self, properties=None, **extra):
super(Linked, self).__init__(properties=properties, **extra)
self["type"] = "link"
class Default(object):
"""GeoJSON default, long/lat WGS84, is not serialized."""
......@@ -3,13 +3,9 @@ from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon
from geojson.geometry import GeometryCollection
from geojson.feature import Feature, FeatureCollection
from geojson.base import GeoJSON
from geojson.crs import Named, Linked
__all__ = ([Point, LineString, Polygon] +
[MultiLineString, MultiPoint, MultiPolygon] +
[GeometryCollection] +
[Feature, FeatureCollection] +
[GeoJSON])
name = Named
link = Linked
......@@ -16,28 +16,28 @@ class Geometry(GeoJSON):
Represents an abstract base class for a WGS84 geometry.
"""
def __init__(self, coordinates=None, crs=None, validate=False, **extra):
def __init__(self, coordinates=None, validate=False, precision=6, **extra):
"""
Initialises a Geometry object.
:param coordinates: Coordinates of the Geometry object.
:type coordinates: tuple or list of tuple
:param crs: CRS
:type crs: CRS object
:param validate: Raise exception if validation errors are present?
:type validate: boolean
:param precision: Number of decimal places for lat/lon coords.
:type precision: integer
"""
super(Geometry, self).__init__(**extra)
self["coordinates"] = self.clean_coordinates(coordinates or [])
self["coordinates"] = self.clean_coordinates(
coordinates or [], precision)
if validate:
errors = self.errors()
if errors:
raise ValueError('{}: {}'.format(errors, coordinates))
if crs:
self["crs"] = self.to_instance(crs, strict=True)
@classmethod
def clean_coordinates(cls, coords):
def clean_coordinates(cls, coords, precision):
if isinstance(coords, cls):
return coords['coordinates']
......@@ -46,11 +46,11 @@ class Geometry(GeoJSON):
coords = [coords]
for coord in coords:
if isinstance(coord, (list, tuple)):
new_coords.append(cls.clean_coordinates(coord))
new_coords.append(cls.clean_coordinates(coord, precision))
elif isinstance(coord, Geometry):
new_coords.append(coord['coordinates'])
elif isinstance(coord, _JSON_compliant_types):
new_coords.append(coord)
new_coords.append(round(coord, precision))
else:
raise ValueError("%r is not a JSON compliant number" % coord)
return new_coords
......
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
try:
import simplejson as json
except ImportError:
......@@ -7,9 +11,6 @@ except ImportError:
import geojson
mapping_base = MutableMapping
GEO_INTERFACE_MARKER = "__geo_interface__"
......
......@@ -119,12 +119,8 @@ def map_geometries(func, obj):
geoms = [func(geom) if geom else None for geom in obj['geometries']]
return {'type': obj['type'], 'geometries': geoms}
elif obj['type'] == 'Feature':
geom = func(obj['geometry']) if obj['geometry'] else None
return {
'type': obj['type'],
'geometry': geom,
'properties': obj['properties'],
}
obj['geometry'] = func(obj['geometry']) if obj['geometry'] else None
return obj
elif obj['type'] == 'FeatureCollection':
feats = [map_geometries(func, feat) for feat in obj['features']]
return {'type': obj['type'], 'features': feats}
......
......@@ -29,9 +29,10 @@ def test_suite():
return suite
if sys.version_info[:2] not in [(2, 6), (2, 7)] and \
sys.version_info[:1] not in [(3, )]:
sys.stderr.write("Sorry, only Python 2.7, and 3.x are supported "
major_version, minor_version = sys.version_info[:2]
if not ((major_version == 2 and minor_version == 7)
or (major_version == 3 and minor_version >= 5)):
sys.stderr.write("Sorry, only Python 2.7, 3.5, 3.6 and 3.7 are supported "
"at this time.\n")
exit(1)
......@@ -47,9 +48,9 @@ setup(
keywords="gis geography json",
author="Sean Gillies",
author_email="sgillies@frii.com",
maintainer="Corey Farwell",
maintainer_email="coreyf@rwell.org",
url="https://github.com/frewsxcv/python-geojson",
maintainer="Ray Riga",
maintainer_email="ray@strongoutput.com",
url="https://github.com/jazzband/python-geojson",
long_description=readme_text,
packages=["geojson"],
package_dir={"geojson": "geojson"},
......@@ -66,10 +67,9 @@ setup(
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Scientific/Engineering :: GIS",
]
)
......@@ -61,7 +61,7 @@ class BaseTestCase(unittest.TestCase):
def test_to_instance(self):
FAKE = 'fake'
self.assertEquals(FAKE, geojson.GeoJSON.to_instance(
self.assertEqual(FAKE, geojson.GeoJSON.to_instance(
None, (lambda: FAKE)))
with self.assertRaises(ValueError):
......
......@@ -13,21 +13,21 @@ class TestGeoJSONConstructor(unittest.TestCase):
def test_copy_construction(self):
coords = [1, 2]
pt = geojson.Point(coords)
self.assertEquals(geojson.Point(pt), pt)
self.assertEqual(geojson.Point(pt), pt)
def test_nested_constructors(self):
a = [5, 6]
b = [9, 10]
c = [-5, 12]
mp = geojson.MultiPoint([geojson.Point(a), b])
self.assertEquals(mp.coordinates, [a, b])
self.assertEqual(mp.coordinates, [a, b])
mls = geojson.MultiLineString([geojson.LineString([a, b]), [a, c]])
self.assertEquals(mls.coordinates, [[a, b], [a, c]])
self.assertEqual(mls.coordinates, [[a, b], [a, c]])
outer = [a, b, c, a]
poly = geojson.Polygon(geojson.MultiPoint(outer))
other = [[1, 1], [1, 2], [2, 1], [1, 1]]
poly2 = geojson.Polygon([outer, other])
self.assertEquals(geojson.MultiPolygon([poly, poly2]).coordinates,
self.assertEqual(geojson.MultiPolygon([poly, poly2]).coordinates,
[[outer], [outer, other]])
......@@ -3,12 +3,18 @@ import unittest
import geojson
from geojson.utils import coords, map_coords
TOO_PRECISE = (1.12341234, -2.12341234)
class CoordsTestCase(unittest.TestCase):
def test_point(self):
itr = coords(geojson.Point((-115.81, 37.24)))
self.assertEqual(next(itr), (-115.81, 37.24))
def test_point_rounding(self):
itr = coords(geojson.Point(TOO_PRECISE))
self.assertEqual(next(itr), tuple([round(c, 6) for c in TOO_PRECISE]))
def test_dict(self):
itr = coords({'type': 'Point', 'coordinates': [-115.81, 37.24]})
self.assertEqual(next(itr), (-115.81, 37.24))
......@@ -66,6 +72,16 @@ class CoordsTestCase(unittest.TestCase):
self.assertEqual(result['coordinates'][0][0][0], (3.78, 9.28))
self.assertEqual(result['coordinates'][-1][-1][-1], (23.18, -34.29))
def test_map_feature(self):
g = geojson.Feature(
id='123',
geometry=geojson.Point([-115.81, 37.24])
)
result = map_coords(lambda x: x, g)
self.assertEqual(result['type'], 'Feature')
self.assertEqual(result['id'], '123')
self.assertEqual(result['geometry']['coordinates'], (-115.81, 37.24))
def test_map_invalid(self):
with self.assertRaises(ValueError):
map_coords(lambda x: x, {"type": ""})
import unittest
import geojson
class CRSTest(unittest.TestCase):
def setUp(self):
self.crs = geojson.crs.Named(
properties={
"name": "urn:ogc:def:crs:EPSG::3785",
}
)
def test_crs_repr(self):
actual = repr(self.crs)
expected = ('{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"},'
' "type": "name"}')
self.assertEqual(actual, expected)
def test_crs_encode(self):
actual = geojson.dumps(self.crs, sort_keys=True)
expected = ('{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"},'
' "type": "name"}')
self.assertEqual(actual, expected)
def test_crs_decode(self):
dumped = geojson.dumps(self.crs)
actual = geojson.loads(dumped)
self.assertEqual(actual, self.crs)
......@@ -15,13 +15,13 @@ class FeaturesTest(unittest.TestCase):
f = {
'type': 'Feature',
'id': '1',
'geometry': {'type': 'Point', 'coordinates': [53, -4]},
'geometry': {'type': 'Point', 'coordinates': [53.0, -4.0]},
'properties': {'title': 'Dict 1'},
}
json = geojson.dumps(f, sort_keys=True)
self.assertEqual(json, '{"geometry":'
' {"coordinates": [53, -4],'
' {"coordinates": [53.0, -4.0],'
' "type": "Point"},'
' "id": "1",'
' "properties": {"title": "Dict 1"},'
......@@ -30,7 +30,7 @@ class FeaturesTest(unittest.TestCase):
o = geojson.loads(json)
output = geojson.dumps(o, sort_keys=True)
self.assertEqual(output, '{"geometry":'
' {"coordinates": [53, -4],'
' {"coordinates": [53.0, -4.0],'
' "type": "Point"},'
' "id": "1",'
' "properties": {"title": "Dict 1"},'
......@@ -49,7 +49,7 @@ class FeaturesTest(unittest.TestCase):
from geojson.examples import SimpleWebFeature
feature = SimpleWebFeature(
id='1',
geometry={'type': 'Point', 'coordinates': [53, -4]},
geometry={'type': 'Point', 'coordinates': [53.0, -4.0]},
title='Feature 1', summary='The first feature',
link='http://example.org/features/1'
)
......@@ -61,10 +61,10 @@ class FeaturesTest(unittest.TestCase):
self.assertEqual(feature.properties['link'],
'http://example.org/features/1')
self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True),
'{"coordinates": [53, -4], "type": "Point"}')
'{"coordinates": [53.0, -4.0], "type": "Point"}')
# Encoding
json = ('{"geometry": {"coordinates": [53, -4],'
json = ('{"geometry": {"coordinates": [53.0, -4.0],'
' "type": "Point"},'
' "id": "1",'
' "properties":'
......@@ -77,7 +77,7 @@ class FeaturesTest(unittest.TestCase):
# Decoding
factory = geojson.examples.create_simple_web_feature
json = ('{"geometry": {"type": "Point",'
' "coordinates": [53, -4]},'
' "coordinates": [53.0, -4.0]},'
' "id": "1",'
' "properties": {"summary": "The first feature",'
' "link": "http://example.org/features/1",'
......@@ -91,7 +91,7 @@ class FeaturesTest(unittest.TestCase):
self.assertEqual(feature.properties['link'],
'http://example.org/features/1')
self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True),
'{"coordinates": [53, -4], "type": "Point"}')
'{"coordinates": [53.0, -4.0], "type": "Point"}')
def test_geo_interface(self):
class Thingy(object):
......@@ -108,12 +108,12 @@ class FeaturesTest(unittest.TestCase):
"geometry": {"type": "Point",
"coordinates": (self.x, self.y)}})
ob = Thingy('1', 'thingy one', -106, 40)
ob = Thingy('1', 'thingy one', -106.0, 40.0)
self.assertEqual(geojson.dumps(ob.__geo_interface__['geometry'],
sort_keys=True),
'{"coordinates": [-106, 40], "type": "Point"}')
'{"coordinates": [-106.0, 40.0], "type": "Point"}')
self.assertEqual(geojson.dumps(ob, sort_keys=True),
('{"geometry": {"coordinates": [-106, 40],'
('{"geometry": {"coordinates": [-106.0, 40.0],'
' "type": "Point"},'
' "id": "1",'
' "properties": {"title": "thingy one"}}'))
......@@ -65,21 +65,21 @@ class EncodingDecodingTest(unittest.TestCase):
properties={'name': self.name})
self.name = "In N Out Burger"
self.latlng = [-54, 4]
self.latlng = [-54.0, 4.0]
self.restaurant_nogeo = Restaurant(self.name, self.latlng)
self.restaurant1 = Restaurant1(self.name, self.latlng)
self.restaurant2 = Restaurant2(self.name, self.latlng)
self.restaurant_str = ('{"coordinates": [-54, 4],'
self.restaurant_str = ('{"coordinates": [-54.0, 4.0],'
' "type": "Point"}')
self.restaurant_feature1 = RestaurantFeature1(self.name, self.latlng)
self.restaurant_feature2 = RestaurantFeature2(self.name, self.latlng)
self.restaurant_feature_str = ('{"geometry":'
' {"coordinates": [-54, 4],'
' {"coordinates": [-54.0, 4.0],'
' "type": "Point"},'
' "properties":'
' {"name": "In N Out Burger"},'
......@@ -133,15 +133,15 @@ class EncodingDecodingTest(unittest.TestCase):
def test_invalid(self):
with self.assertRaises(ValueError) as cm:
geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4]]}')
geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4.0]]}')
self.assertIn('is not JSON compliant', str(cm.exception))
def test_mapping(self):
self.assertEquals(to_mapping(geojson.Point([1, 2])),
{"coordinates": [1, 2], "type": "Point"})
self.assertEqual(to_mapping(geojson.Point([1.0, 2.0])),
{"coordinates": [1.0, 2.0], "type": "Point"})
def test_GeoJSON(self):
self.assertEquals(None, geojson.GeoJSON().__geo_interface__)
self.assertEqual(None, geojson.GeoJSON().__geo_interface__)
self.assertEquals({"type": "GeoJSON"}, to_mapping(geojson.GeoJSON()))
self.assertEqual({"type": "GeoJSON"}, to_mapping(geojson.GeoJSON()))
......@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
envlist = py{27,33,34,35,36}, pypy, pypy3
envlist = py{27,35,36,37}, pypy, pypy3
[testenv]
commands = {envpython} setup.py test