From 48ce4cbe23cc455fc41ba044f55d8706b269ad12 Mon Sep 17 00:00:00 2001 From: Michael Barry Date: Thu, 27 Jan 2022 06:14:37 -0500 Subject: [PATCH] Fix pedestrian area multipolygons (#63) --- .../basemap/layers/Transportation.java | 5 ++-- .../basemap/layers/AbstractLayerTest.java | 11 +++++++ .../basemap/layers/TransportationTest.java | 29 +++++++++++++++++-- .../planetiler/reader/SimpleFeature.java | 23 ++++++++++++++- .../planetiler/reader/osm/OsmReader.java | 14 +++++++-- 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/planetiler-basemap/src/main/java/com/onthegomap/planetiler/basemap/layers/Transportation.java b/planetiler-basemap/src/main/java/com/onthegomap/planetiler/basemap/layers/Transportation.java index 3f116c17..2d9da5ed 100644 --- a/planetiler-basemap/src/main/java/com/onthegomap/planetiler/basemap/layers/Transportation.java +++ b/planetiler-basemap/src/main/java/com/onthegomap/planetiler/basemap/layers/Transportation.java @@ -513,8 +513,9 @@ public class Transportation implements public void process(Tables.OsmHighwayPolygon element, FeatureCollector features) { String manMade = element.manMade(); if (isBridgeOrPier(manMade) || - // ignore underground pedestrian areas - (element.isArea() && element.layer() >= 0)) { + // only allow closed ways where area=yes, and multipolygons + // and ignore underground pedestrian areas + (!element.source().canBeLine() && element.layer() >= 0)) { String highwayClass = highwayClass(element.highway(), element.publicTransport(), null, element.manMade()); if (highwayClass != null) { features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) diff --git a/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/AbstractLayerTest.java b/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/AbstractLayerTest.java index d40a9021..2ecfae6a 100644 --- a/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/AbstractLayerTest.java +++ b/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/AbstractLayerTest.java @@ -131,6 +131,17 @@ public abstract class AbstractLayerTest { ); } + SourceFeature closedWayFeature(Map props) { + return SimpleFeature.createFakeOsmFeature( + newLineString(0, 0, 1, 0, 1, 1, 0, 1, 0, 0), + new HashMap<>(props), + OSM_SOURCE, + null, + 0, + null + ); + } + SourceFeature polygonFeatureWithArea(double area, Map props) { return SimpleFeature.create( GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(area))), diff --git a/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/TransportationTest.java b/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/TransportationTest.java index 04fb63fd..28d24fd4 100644 --- a/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/TransportationTest.java +++ b/planetiler-basemap/src/test/java/com/onthegomap/planetiler/basemap/layers/TransportationTest.java @@ -990,7 +990,7 @@ public class TransportationTest extends AbstractLayerTest { @Test public void testPedestrianArea() { - assertFeatures(10, List.of(Map.of( + Map pedestrianArea = Map.of( "_layer", "transportation", "class", "path", "subclass", "pedestrian", @@ -998,13 +998,36 @@ public class TransportationTest extends AbstractLayerTest { "_minzoom", 13, "_maxzoom", 14, "_type", "polygon" - )), process(polygonFeature(Map.of( + ); + Map circularPath = Map.of( + "_layer", "transportation", + "class", "path", + "subclass", "pedestrian", + + "_minzoom", 14, + "_maxzoom", 14, + "_type", "line" + ); + assertFeatures(14, List.of(pedestrianArea), process(closedWayFeature(Map.of( "highway", "pedestrian", "area", "yes", "foot", "yes" )))); + assertFeatures(14, List.of(pedestrianArea), process(polygonFeature(Map.of( + "highway", "pedestrian", + "foot", "yes" + )))); + assertFeatures(14, List.of(circularPath), process(closedWayFeature(Map.of( + "highway", "pedestrian", + "foot", "yes" + )))); + assertFeatures(14, List.of(circularPath), process(closedWayFeature(Map.of( + "highway", "pedestrian", + "foot", "yes", + "area", "no" + )))); // ignore underground pedestrian areas - assertFeatures(10, List.of(), + assertFeatures(14, List.of(), process(polygonFeature(Map.of( "highway", "pedestrian", "area", "yes", diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java index 79d08606..5e829e57 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java @@ -8,7 +8,9 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Lineal; +import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.Polygonal; import org.locationtech.jts.geom.Puntal; @@ -77,7 +79,26 @@ public class SimpleFeature extends SourceFeature { /** Returns a new feature with OSM relation info. Useful for setting up inputs for OSM unit tests. */ public static SimpleFeature createFakeOsmFeature(Geometry latLonGeometry, Map tags, String source, String sourceLayer, long id, List> relations) { - return new SimpleFeature(latLonGeometry, null, tags, source, sourceLayer, id, relations); + String area = (String) tags.get("area"); + return new SimpleFeature(latLonGeometry, null, tags, source, sourceLayer, id, relations) { + @Override + public boolean canBePolygon() { + return latLonGeometry instanceof Polygonal || (latLonGeometry instanceof LineString line + && OsmReader.canBePolygon(line.isClosed(), area, latLonGeometry.getNumPoints())); + } + + @Override + public boolean canBeLine() { + return latLonGeometry instanceof MultiLineString || (latLonGeometry instanceof LineString line + && OsmReader.canBeLine(line.isClosed(), area, latLonGeometry.getNumPoints())); + } + + @Override + protected Geometry computePolygon() { + var geom = worldGeometry(); + return geom instanceof LineString line ? GeoUtils.JTS_FACTORY.createPolygon(line.getCoordinates()) : geom; + } + }; } @Override diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java index 18cd8ef3..96f5161a 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java @@ -514,6 +514,16 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate { } } + /** Returns {@code true} if a way can be interpreted as a line. */ + public static boolean canBeLine(boolean closed, String area, int points) { + return (!closed || !"yes".equals(area)) && points >= 2; + } + + /** Returns {@code true} if a way can be interpreted as a polygon. */ + public static boolean canBePolygon(boolean closed, String area, int points) { + return (closed && !"no".equals(area)) && points >= 4; + } + /** * A {@link LineString} or {@link Polygon} created from an OSM way. *

@@ -529,8 +539,8 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate { public WaySourceFeature(ReaderWay way, boolean closed, String area, NodeLocationProvider nodeLocations, List> relationInfo) { super(way, false, - (!closed || !"yes".equals(area)) && way.getNodes().size() >= 2, // line - (closed && !"no".equals(area)) && way.getNodes().size() >= 4, // polygon + OsmReader.canBeLine(closed, area, way.getNodes().size()), + OsmReader.canBePolygon(closed, area, way.getNodes().size()), relationInfo ); this.nodeIds = way.getNodes();