GeoJSON Polygons with Holes Parsing – How to Parse GeoJSON Polygons with Holes Using Java

geojsonjavapolygon

We're trying to use polygons taken from OpenStreetMap and determine user locations.
We're using geojson to parse the GeoJSONs we generate from OSM, but the deserialized polygons aren't being populated correctly – no holes are detected, instead we get a set of polygons.

Examining the the exported GeoJSON from OSM does seem to represent a set of multi-polygons and not a polygon with holes.
ie, Rome (OSM),
Vatican isn't being populated as a hole, instead there are two polygons – the Rome polygon(containing the Vatican), and the Vatican polygon.

Checking some tools such as Mapbox or JOSM do parse it correct and display the holes.

Is there a java lib that parses polygons with holes even on such cases?

Rome's GeoJSON (OSM)

Snippet (Kotlin):

val romeGeom = GeometryJSON().readGeometryCollection(getFileContent("rome.json")).getGeometryN(0)
    with (org.locationtech.jts.geom.GeometryFactory()) {
        val map = mapOf(
            "rome" to createPoint(org.locationtech.jts.geom.Coordinate(12.4458, 41.9040)),
            "vatican" to createPoint(org.locationtech.jts.geom.Coordinate(12.44597, 41.90197)), // Expected to be false
            "rome2" to createPoint(org.locationtech.jts.geom.Coordinate(12.3049, 42.1121)))

        map.forEach { (key, value) ->
            println("$key: ${value.within(romeGeom)}")
        }
    }

Using gt-geojsondatastore v26.3 yields StackOverflowError (tried couple of geo-jsons):

val store = GeoJSONDataStore(getFileUrl("rome.json"))
val featureSource = store.getFeatureSource(store.typeNames[0])
val feat = featureSource.features //StackOverflowError

Error:

Exception in thread "main" java.lang.StackOverflowError
at java.io.UnixFileSystem.getBooleanAttributes0(Native Method)
at java.io.UnixFileSystem.getBooleanAttributes(UnixFileSystem.java:242)
at java.io.File.isDirectory(File.java:860)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:82)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188)
at java.net.URL.openStream(URL.java:1068)
at org.geotools.data.geojson.GeoJSONReader.isConnected(GeoJSONReader.java:124)
at org.geotools.data.geojson.GeoJSONReader.getIterator(GeoJSONReader.java:406)
at org.geotools.data.geojson.GeoJSONFeatureSource.buildFeatureType(GeoJSONFeatureSource.java:107)
at org.geotools.data.geojson.GeoJSONFeatureStore.buildFeatureType(GeoJSONFeatureStore.java:78)
at org.geotools.data.store.ContentFeatureSource.getAbsoluteSchema(ContentFeatureSource.java:338)
at org.geotools.data.store.ContentFeatureSource.getSchema(ContentFeatureSource.java:307)
at org.geotools.data.store.ContentDataStore.getSchema(ContentDataStore.java:295)
at org.geotools.data.store.ContentDataStore.getSchema(ContentDataStore.java:633)
at org.geotools.data.geojson.GeoJSONDataStore.getSchema(GeoJSONDataStore.java:111)
at org.geotools.data.geojson.GeoJSONFeatureSource.buildFeatureType(GeoJSONFeatureSource.java:121)
at org.geotools.data.geojson.GeoJSONFeatureStore.buildFeatureType(GeoJSONFeatureStore.java:78)
at org.geotools.data.store.ContentFeatureSource.getAbsoluteSchema(ContentFeatureSource.java:338)
at org.geotools.data.store.ContentFeatureSource.getSchema(ContentFeatureSource.java:307)
at org.geotools.data.store.ContentDataStore.getSchema(ContentDataStore.java:295)
at org.geotools.data.store.ContentDataStore.getSchema(ContentDataStore.java:633)
at org.geotools.data.geojson.GeoJSONDataStore.getSchema(GeoJSONDataStore.java:111)
at org.geotools.data.geojson.GeoJSONFeatureSource.buildFeatureType(GeoJSONFeatureSource.java:121)
at org.geotools.data.geojson.GeoJSONFeatureStore.buildFeatureType(GeoJSONFeatureStore.java:78)
at org.geotools.data.store.ContentFeatureSource.getAbsoluteSchema(ContentFeatureSource.java:338)
at org.geotools.data.store.ContentFeatureSource.getSchema(ContentFeatureSource.java:307)

Best Answer

I looked at this a few weeks ago on a separate platform:

Here is what I said:


Well, it looks like the multipolygons are incorrectly generated. From the spec,

A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are counterclockwise, and holes are clockwise

Check the exterior ring for Rome. Technically speaking, it is the “inner” ring with another “inner” ring from the Vatican.


For a compliant geojson where software doesn't have to guess, I would use Nominatim. An earlier version of the specification did not specify that winding rule, but software may not want to "guess" what the appropriate behavior is.

Example: https://nominatim.openstreetmap.org/search/Rome?format=geojson&polygon_geojson=1

See https://nominatim.org/release-docs/develop/api/Overview/ for the full API. See https://operations.osmfoundation.org/policies/nominatim/ for the usage policy