Skip to content
This repository was archived by the owner on Jun 10, 2025. It is now read-only.

Relation and multipolygon support #115

Merged
merged 8 commits into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name = "pypi"
geojson = ">=1.3.1"
requests = ">=2.20.0"
nose = ">=1.3.7"
shapely = ">=1.6.4"

[dev-packages]

Expand Down
66 changes: 52 additions & 14 deletions overpass/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import csv
import geojson
import logging
from shapely.geometry import Polygon, Point
from io import StringIO
from .errors import (
OverpassSyntaxError,
Expand Down Expand Up @@ -191,21 +192,58 @@ def _as_geojson(self, elements):
geometry = None
for elem in elements:
elem_type = elem.get("type")
if elem_type and elem_type == "node":
elem_tags = elem.get("tags")
elem_geom = elem.get("geometry", [])
if elem_type == "node":
# Create Point geometry
geometry = geojson.Point((elem.get("lon"), elem.get("lat")))
elif elem_type and elem_type == "way":
points = []
geom = elem.get("geometry")
if geom:
for coords in elem.get("geometry"):
points.append((coords["lon"], coords["lat"]))
geometry = geojson.LineString(points)
elif elem_type == "way":
# Create LineString geometry
geometry = geojson.LineString([(coords["lon"], coords["lat"]) for coords in elem_geom])
elif elem_type == "relation":
# Initialize polygon list
polygons = []
# First obtain the outer polygons
for member in elem.get("members", []):
if member["role"] == "outer":
points = [(coords["lon"], coords["lat"]) for coords in member.get("geometry", [])]
# Check that the outer polygon is complete
if points and points[-1] == points[0]:
polygons.append([points])
else:
raise UnknownOverpassError("Received corrupt data from Overpass (incomplete polygon).")
# Then get the inner polygons
for member in elem.get("members", []):
if member["role"] == "inner":
points = [(coords["lon"], coords["lat"]) for coords in member.get("geometry", [])]
# Check that the inner polygon is complete
if points and points[-1] == points[0]:
# We need to check to which outer polygon the inner polygon belongs
point = Point(points[0])
check = False
for poly in polygons:
polygon = Polygon(poly[0])
if polygon.contains(point):
poly.append(points)
check = True
break
if not check:
raise UnknownOverpassError("Received corrupt data from Overpass (inner polygon cannot "
"be matched to outer polygon).")
else:
raise UnknownOverpassError("Received corrupt data from Overpass (incomplete polygon).")
# Finally create MultiPolygon geometry
if polygons:
geometry = geojson.MultiPolygon(polygons)
else:
continue

feature = geojson.Feature(
id=elem["id"], geometry=geometry, properties=elem.get("tags")
)
features.append(feature)
raise UnknownOverpassError("Received corrupt data from Overpass (invalid element).")

if geometry:
feature = geojson.Feature(
id=elem["id"],
geometry=geometry,
properties=elem_tags
)
features.append(feature)

return geojson.FeatureCollection(features)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
geojson>=1.3.1
requests>=2.8.1
nose>=1.3.7
shapely>=1.6.4
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"Topic :: Scientific/Engineering :: GIS",
"Topic :: Utilities",
],
install_requires=["requests>=2.3.0", "geojson>=1.0.9"],
install_requires=["requests>=2.3.0", "geojson>=1.0.9", "shapely>=1.6.4"],
extras_require={"test": ["pytest"]},
)
1 change: 1 addition & 0 deletions tests/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "FeatureCollection", "features": [{"type": "Feature", "id": 6518385, "geometry": {"type": "MultiPolygon", "coordinates": [[[[-122.197877, 37.85778], [-122.194092, 37.857792], [-122.194969, 37.856665], [-122.19383, 37.856063], [-122.192671, 37.855601], [-122.191239, 37.854941], [-122.190287, 37.854202], [-122.18951, 37.853395], [-122.189155, 37.85316], [-122.188619, 37.852042], [-122.188587, 37.850433], [-122.188458, 37.8468], [-122.193004, 37.84685], [-122.193102, 37.844092], [-122.196167, 37.843758], [-122.196416, 37.843891], [-122.196527, 37.84398], [-122.196596, 37.844063], [-122.196625, 37.844159], [-122.196626, 37.844275], [-122.196591, 37.8446], [-122.196555, 37.844785], [-122.19655, 37.844859], [-122.196562, 37.845099], [-122.196595, 37.845336], [-122.196707, 37.845596], [-122.19685, 37.845815], [-122.197436, 37.846209], [-122.197763, 37.846534], [-122.1982, 37.846859], [-122.198509, 37.847245], [-122.198686, 37.847378], [-122.198832, 37.847411], [-122.199029, 37.847391], [-122.199673, 37.847378], [-122.200188, 37.847506], [-122.200603, 37.847587], [-122.200899, 37.847626], [-122.201293, 37.847766], [-122.201452, 37.847847], [-122.201547, 37.847906], [-122.201615, 37.847969], [-122.201662, 37.84805], [-122.201686, 37.848126], [-122.201714, 37.848254], [-122.201749, 37.848329], [-122.201797, 37.848392], [-122.201848, 37.848454], [-122.201904, 37.848511], [-122.201952, 37.848548], [-122.202784, 37.849109], [-122.202848, 37.849167], [-122.202922, 37.849257], [-122.203008, 37.849391], [-122.20307, 37.849528], [-122.203293, 37.849857], [-122.203312, 37.849699], [-122.203361, 37.849483], [-122.203425, 37.84928], [-122.203476, 37.84912], [-122.203566, 37.848937], [-122.203649, 37.848788], [-122.203775, 37.848634], [-122.203864, 37.848741], [-122.203814, 37.848821], [-122.203839, 37.848899], [-122.203951, 37.84896], [-122.204071, 37.848987], [-122.204475, 37.848981], [-122.204632, 37.849013], [-122.204699, 37.849004], [-122.204719, 37.848972], [-122.204722, 37.848938], [-122.204701, 37.848915], [-122.204612, 37.848879], [-122.204503, 37.848825], [-122.204432, 37.848755], [-122.204427, 37.848645], [-122.204533, 37.84831], [-122.20456, 37.848005], [-122.204584, 37.847916], [-122.204615, 37.847869], [-122.204655, 37.847849], [-122.204778, 37.847778], [-122.204807, 37.847701], [-122.204831, 37.847448], [-122.204949, 37.847111], [-122.205346, 37.846512], [-122.205394, 37.846387], [-122.20541, 37.846273], [-122.205352, 37.845942], [-122.205358, 37.845857], [-122.205495, 37.845739], [-122.205632, 37.84565], [-122.205692, 37.845581], [-122.205716, 37.845454], [-122.205755, 37.845186], [-122.205853, 37.844948], [-122.205926, 37.844826], [-122.206013, 37.844757], [-122.206318, 37.844628], [-122.206408, 37.844569], [-122.206432, 37.844509], [-122.206444, 37.844375], [-122.206447, 37.844294], [-122.206449, 37.844236], [-122.206428, 37.844158], [-122.206375, 37.844106], [-122.206287, 37.844071], [-122.206153, 37.844079], [-122.206148, 37.843571], [-122.206622, 37.843483], [-122.208596, 37.850482], [-122.208577, 37.850955], [-122.208566, 37.851015], [-122.208541, 37.851063], [-122.208502, 37.851109], [-122.208438, 37.851157], [-122.208486, 37.85169], [-122.209044, 37.852325], [-122.210718, 37.85423], [-122.210978, 37.854526], [-122.210778, 37.855026], [-122.210957, 37.855322], [-122.211079, 37.855523], [-122.211163, 37.856313], [-122.210923, 37.856645], [-122.211096, 37.856762], [-122.21085, 37.856934], [-122.211025, 37.857108], [-122.211307, 37.856894], [-122.212047, 37.857809], [-122.212137, 37.858256], [-122.211748, 37.85828], [-122.212917, 37.859008], [-122.213903, 37.858986], [-122.215701, 37.860658], [-122.216433, 37.860582], [-122.216481, 37.860648], [-122.216546, 37.860723], [-122.21661, 37.860783], [-122.216911, 37.86104], [-122.217074, 37.861197], [-122.217137, 37.861274], [-122.217197, 37.861362], [-122.217243, 37.861458], [-122.217274, 37.861549], [-122.217316, 37.861709], [-122.217372, 37.861838], [-122.217531, 37.861998], [-122.217921, 37.862287], [-122.21805, 37.862382], [-122.218422, 37.862781], [-122.218616, 37.86293], [-122.218267, 37.863282], [-122.217, 37.862143], [-122.21633, 37.862981], [-122.215336, 37.86253], [-122.214499, 37.862013], [-122.213738, 37.861522], [-122.212933, 37.8612], [-122.21206, 37.86088], [-122.2115, 37.860812], [-122.211345, 37.860794], [-122.210262, 37.860607], [-122.209757, 37.860235], [-122.209453, 37.859626], [-122.208785, 37.859769], [-122.20885, 37.859909], [-122.208941, 37.860063], [-122.207408, 37.859896], [-122.207189, 37.861261], [-122.209038, 37.861482], [-122.208806, 37.862683], [-122.206636, 37.862631], [-122.206689, 37.861446], [-122.198009, 37.861454], [-122.197877, 37.85778]], [[-122.195626, 37.850528], [-122.195622, 37.850664], [-122.194665, 37.85065], [-122.194668, 37.850514], [-122.195626, 37.850528]], [[-122.193631, 37.850507], [-122.193653, 37.849682], [-122.192015, 37.849655], [-122.191993, 37.85048], [-122.191979, 37.851031], [-122.192966, 37.851048], [-122.192981, 37.850497], [-122.193631, 37.850507]]], [[[-122.214506, 37.865654], [-122.215787, 37.863741], [-122.213115, 37.862469], [-122.211749, 37.862006], [-122.211099, 37.861874], [-122.211011, 37.861948], [-122.210893, 37.862033], [-122.210753, 37.862151], [-122.210621, 37.862247], [-122.210469, 37.86238], [-122.210317, 37.862539], [-122.210193, 37.862681], [-122.21007, 37.862832], [-122.209987, 37.86298], [-122.209865, 37.863175], [-122.209788, 37.863354], [-122.209725, 37.863555], [-122.209671, 37.863764], [-122.209635, 37.863967], [-122.209617, 37.864195], [-122.209609, 37.864417], [-122.20983, 37.86445], [-122.210077, 37.864798], [-122.212723, 37.864933], [-122.212632, 37.865082], [-122.214506, 37.865654]]], [[[-122.183933, 37.846772], [-122.179485, 37.846769], [-122.179469, 37.850483], [-122.174886, 37.850508], [-122.174897, 37.846908], [-122.174357, 37.846912], [-122.174214, 37.845764], [-122.174206, 37.845438], [-122.174174, 37.844848], [-122.174222, 37.844019], [-122.174158, 37.843805], [-122.173633, 37.843335], [-122.173283, 37.842525], [-122.173042, 37.842191], [-122.172325, 37.841867], [-122.171624, 37.841611], [-122.170393, 37.841024], [-122.170366, 37.839856], [-122.173416, 37.839848], [-122.175136, 37.839844], [-122.176957, 37.837722], [-122.178038, 37.838142], [-122.178221, 37.837887], [-122.179037, 37.838177], [-122.179495, 37.838386], [-122.179526, 37.839474], [-122.18098, 37.839502], [-122.180953, 37.839917], [-122.182271, 37.839908], [-122.182634, 37.839717], [-122.182912, 37.83957], [-122.183949, 37.839595], [-122.183929, 37.840341], [-122.183935, 37.841137], [-122.183933, 37.846772]]]]}, "properties": {"boundary": "national_park", "contact:website": "http://www.ebparks.org/parks/sibley", "leisure": "park", "name": "Sibley Volcanic Regional Preserve", "operator": "East Bay Regional Park District", "owner": "East Bay Regional Park District", "source": "https://www.ebparks.org/images/Assets/files/parks/sibley/Sibley-map_2250w-04-23-18.gif", "type": "multipolygon", "website": "https://www.ebparks.org/parks/sibley/", "wikidata": "Q7349780", "wikipedia": "en:Robert Sibley Volcanic Regional Preserve"}}, {"type": "Feature", "id": 10322303, "geometry": {"type": "LineString", "coordinates": [[-122.318477, 37.869901], [-122.318412, 37.869652], [-122.318357, 37.869442], [-122.318313, 37.869271], [-122.318271, 37.86911], [-122.318218, 37.868906], [-122.318134, 37.868831], [-122.317998, 37.868763], [-122.317754, 37.86875], [-122.317622, 37.868773], [-122.317266, 37.86893], [-122.317185, 37.869015], [-122.317255, 37.869279], [-122.317297, 37.869439], [-122.317345, 37.869618], [-122.317421, 37.869906], [-122.317464, 37.87007]]}, "properties": {"addr:city": "Berkeley", "foot": "yes", "highway": "service"}}, {"type": "Feature", "id": 4927326183, "geometry": {"type": "Point", "coordinates": [-122.318412, 37.869652]}, "properties": {}}]}
Binary file added tests/example.response
Binary file not shown.
32 changes: 32 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# See LICENSE.txt for the full license text.

import overpass
import geojson
import pickle
import os


def test_initialize_api():
Expand All @@ -21,3 +24,32 @@ def test_geojson():

osm_geo = api.get("node(area:3602758138)[amenity=cafe]")
assert len(osm_geo["features"]) > 1


def test_geojson_extended():

class API(overpass.API):
def _get_from_overpass(self, query):
return pickle.load(open(os.path.join(os.path.dirname(__file__), "example.response"), "rb"))

# The commented code should only be executed once when major changes to the Overpass API and/or to this wrapper are
# introduced. One than has to manually verify that the date in the example.response file from the Overpass API
# matches the data in the example.json file generated by this wrapper.
#
# The reason for this approach is the following: It is not safe to make calls to the actual API in this test as the
# API might momentarily be unavailable and the underlying data can also change at any moment. The commented code is
# needed to create the example.response and example.json files. The example.response file is subsequently used to
# fake the _get_from_overpass method during the tests and the example.json file is the reference that we are
# asserting against.
#
# api = overpass.API()
# osm_geo = api.get("rel(6518385);out body geom;way(10322303);out body geom;node(4927326183);", verbosity='body geom')
# pickle.dump(api._get_from_overpass("[out:json];rel(6518385);out body geom;way(10322303);out body geom;node(4927326183);out body geom;"),
# open(os.path.join(os.path.dirname(__file__), "example.response"), "wb"),
# protocol=2)
# geojson.dump(osm_geo, open(os.path.join(os.path.dirname(__file__), "example.json"), "w"))

api = API()
osm_geo = api.get("rel(6518385);out body geom;way(10322303);out body geom;node(4927326183);", verbosity='body geom')
ref_geo = geojson.load(open(os.path.join(os.path.dirname(__file__), "example.json"), "r"))
assert osm_geo==ref_geo