2
2
Fork
You've already forked overpass-api-python-wrapper
2

Multipolygons are breaking geojson convertion #135

Closed
opened 2021年02月01日 14:31:00 +01:00 by Monstrofil · 6 comments
Monstrofil commented 2021年02月01日 14:31:00 +01:00 (Migrated from github.com)
Copy link

When trying to fetch multipolygons (e.g. 11038555), I get error:

Received corrupt data from Overpass (incomplete polygon).

It seems that happens because outer and inner members are not always closed: they may be just ways.

Are you planning to improve this?

When trying to fetch multipolygons (e.g. 11038555), I get error: ``` Received corrupt data from Overpass (incomplete polygon). ``` It seems that happens because outer and inner members are not always closed: they may be just ways. Are you planning to improve this?
Monstrofil commented 2021年02月01日 14:32:05 +01:00 (Migrated from github.com)
Copy link

UPD: I did this patch for me, it works, but it definitely need to be slightly improved before merge:

Index: overpass/api.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- overpass/api.py	(revision cd544c59c3546d442df0b5b16853ec555dd34def)
+++ overpass/api.py	(date 1612186223887)
@@ -9,7 +9,10 @@
 import geojson
 import logging
 from datetime import datetime
-from shapely.geometry import Polygon, Point
+
+from geojson import MultiLineString
+from shapely.geometry import Polygon, Point, mapping, LineString
+from shapely import geometry as geom, ops
 from io import StringIO
 from .errors import (
 OverpassSyntaxError,
@@ -222,40 +225,43 @@
 elif elem_type == "relation":
 # Initialize polygon list
 polygons = []
+ outer_ways = []
+ inner_ways = []
 # 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:
- raise UnknownOverpassError("Received corrupt data from Overpass (invalid element).")
+ if member["role"] not in ["outer", "inner"]:
+ continue
+ points = [(coords["lon"], coords["lat"]) for coords in member.get("geometry", [])]
+ if member["role"] == "outer":
+ outer_ways.append(geom.LineString(points))
+ elif member["role"] == "inner":
+ inner_ways.append(geom.LineString(points))
+ outer_merged_line = ops.linemerge(outer_ways)
+ if isinstance(outer_merged_line, LineString):
+ iterator = [outer_merged_line]
+ else:
+ iterator = outer_merged_line.geoms
+ for line in iterator:
+ polygons.append(Polygon(line.coords))
+
+ inner_merged_line = ops.linemerge(inner_ways)
+ if isinstance(inner_merged_line, LineString):
+ iterator = [inner_merged_line]
+ else:
+ iterator = inner_merged_line.geoms
+ for line in iterator:
+ inner_poly = Polygon(line.coords)
+
+ tmp_polygons = []
+ for poly in polygons[:]:
+ if poly.contains(inner_poly):
+ poly -= inner_poly
+ tmp_polygons.append(poly)
+ polygons = tmp_polygons
+
+ all_polygons = [mapping(poly)['coordinates'] for poly in polygons]
+ if all_polygons:
+ geometry = geojson.MultiPolygon(all_polygons)
 
 if geometry:
 feature = geojson.Feature(
UPD: I did this patch for me, it works, but it definitely need to be slightly improved before merge: ``` Index: overpass/api.py IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- overpass/api.py (revision cd544c59c3546d442df0b5b16853ec555dd34def) +++ overpass/api.py (date 1612186223887) @@ -9,7 +9,10 @@ import geojson import logging from datetime import datetime -from shapely.geometry import Polygon, Point + +from geojson import MultiLineString +from shapely.geometry import Polygon, Point, mapping, LineString +from shapely import geometry as geom, ops from io import StringIO from .errors import ( OverpassSyntaxError, @@ -222,40 +225,43 @@ elif elem_type == "relation": # Initialize polygon list polygons = [] + outer_ways = [] + inner_ways = [] # 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: - raise UnknownOverpassError("Received corrupt data from Overpass (invalid element).") + if member["role"] not in ["outer", "inner"]: + continue + points = [(coords["lon"], coords["lat"]) for coords in member.get("geometry", [])] + if member["role"] == "outer": + outer_ways.append(geom.LineString(points)) + elif member["role"] == "inner": + inner_ways.append(geom.LineString(points)) + outer_merged_line = ops.linemerge(outer_ways) + if isinstance(outer_merged_line, LineString): + iterator = [outer_merged_line] + else: + iterator = outer_merged_line.geoms + for line in iterator: + polygons.append(Polygon(line.coords)) + + inner_merged_line = ops.linemerge(inner_ways) + if isinstance(inner_merged_line, LineString): + iterator = [inner_merged_line] + else: + iterator = inner_merged_line.geoms + for line in iterator: + inner_poly = Polygon(line.coords) + + tmp_polygons = [] + for poly in polygons[:]: + if poly.contains(inner_poly): + poly -= inner_poly + tmp_polygons.append(poly) + polygons = tmp_polygons + + all_polygons = [mapping(poly)['coordinates'] for poly in polygons] + if all_polygons: + geometry = geojson.MultiPolygon(all_polygons) if geometry: feature = geojson.Feature( ```

Thanks for spotting and fixing!

Thanks for spotting and fixing!
dericke commented 2021年03月04日 18:39:33 +01:00 (Migrated from github.com)
Copy link

I'm wondering if it might be best to import another lib for converting OSM format to geojson and let this lib focus on the API stuff, rather than maintain a homegrown converter. Something like osm2geojson, perhaps?

I'm wondering if it might be best to import another lib for converting OSM format to geojson and let this lib focus on the API stuff, rather than maintain a homegrown converter. Something like [osm2geojson](https://pypi.org/project/osm2geojson/), perhaps?
dericke commented 2021年03月04日 21:08:38 +01:00 (Migrated from github.com)
Copy link

I had a quick look at replacing the built-in geojson method with osm2geojson, found that osm2geojson uses 7 decimal points of precision for coordinates, while this library has been using 6 (default from the geojson library) and osm2geojson nests the id property of a feature under properties, whereas the built-in method puts it in the top level of the feature.

I had a quick look at replacing the built-in geojson method with osm2geojson, found that osm2geojson uses 7 decimal points of precision for coordinates, while this library has been using 6 (default from the geojson library) and osm2geojson nests the `id` property of a feature under properties, whereas the built-in method puts it in the top level of the feature.

I think the best way to go about supporting GeoJSON properly is not to roll our own but instead support __geo_interface__. Since geojson implements this interface too, there's no need to introduce any other dependencies.

I think the best way to go about supporting GeoJSON properly is not to roll our own but instead support [`__geo_interface__`](https://gist.github.com/sgillies/2217756). Since [`geojson` implements this interface too](https://pypi.org/project/geojson/), there's no need to introduce any other dependencies.

This is fixed on the main branch now with #140 merged in. Looking to get 0.7.1 out soon with this fix.

This is fixed on the main branch now with #140 merged in. Looking to get 0.7.1 out soon with this fix.
Sign in to join this conversation.
No Branch/Tag specified
main
tutorial/d1-httpx-nvim
0.8.x
dev-contributor-tooling
1.0-dev
dev
feature/api-modernization
maintenance/base-0.8
maintenance/geojson-fixtures
archive/async-models
maintenance/endpoint-guard
maintenance/live-query-tool
maintenance/modernize-plan
archive/dev-prebaseline-20260502
archive/modernize
archive/chore-tests
chore/poetry
archive/readme
v0.8.2
v0.8.1
v0.8.0
v0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.7
0.5.6
0.5.5
0.4.0
0.1.0
0.0.2
0.0.1
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mvexel/overpass-api-python-wrapper#135
Reference in a new issue
mvexel/overpass-api-python-wrapper
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?