diff --git a/tiles/src/main/java/com/protomaps/basemap/layers/Pois.java b/tiles/src/main/java/com/protomaps/basemap/layers/Pois.java index bc9d13af..21a420ec 100644 --- a/tiles/src/main/java/com/protomaps/basemap/layers/Pois.java +++ b/tiles/src/main/java/com/protomaps/basemap/layers/Pois.java @@ -25,10 +25,15 @@ import com.protomaps.basemap.names.OsmNames; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; @SuppressWarnings("java:S1192") public class Pois implements ForwardingProfile.LayerPostProcessor { + // Matches numeric metre values with an optional trailing "m" suffix, e.g. "569", "569.5", "569 m", or "569m". + private static final Pattern ELEVATION_PATTERN = + Pattern.compile("^([+-]?(?:\\d+(?:\\.\\d+)?|\\.\\d+))(?:\\s*m)?$"); + private Map qrankGrading = Map.of( "station", new int[][]{{10, 50000}, {12, 20000}, {13, 10000}}, "aerodrome", new int[][]{{10, 50000}, {12, 20000}, {13, 5000}, {14, 2500}}, @@ -46,6 +51,25 @@ public Pois(QrankDb qrankDb) { public static final String LAYER_NAME = "pois"; + static Integer parseElevation(String elevation) { + if (elevation == null) { + return null; + } + + var matcher = ELEVATION_PATTERN.matcher(elevation.trim()); + if (!matcher.matches()) { + return null; + } + + var parsed = parseDoubleOrNull(matcher.group(1)); + if (parsed == null) { + return null; + } + + var rounded = Math.round(parsed); + return rounded >= Integer.MIN_VALUE && rounded <= Integer.MAX_VALUE ? (int) rounded : null; + } + private static final Expression WITH_OPERATOR_USFS = with("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", "USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", "United State Forest Service", "U.S. National Forest Service"); @@ -501,9 +525,7 @@ public void processOsm(SourceFeature sf, FeatureCollector features) { // Assign outputFeature if (hasNamedPolygon) { - outputFeature = features.pointOnSurface(this.name()) - //.setAttr("area_debug", wayArea) // DEBUG - .setAttr("elevation", sf.getString("ele")); + outputFeature = features.pointOnSurface(this.name()); } else if (sf.isPoint()) { outputFeature = features.point(this.name()); } else { @@ -525,7 +547,8 @@ public void processOsm(SourceFeature sf, FeatureCollector features) { .setZoomRange(Math.min(minZoom, 15), 15) // Core OSM tags for different kinds of places // Special airport only tag (to indicate if it's an airport with regular commercial flights) - .setAttr("iata", sf.getString("iata")); + .setAttr("iata", sf.getString("iata")) + .setAttr("elevation", parseElevation(sf.getString("ele"))); // Core Tilezen schema properties if (!kindDetail.equals("pm:undefined")) diff --git a/tiles/src/test/java/com/protomaps/basemap/layers/PoisTest.java b/tiles/src/test/java/com/protomaps/basemap/layers/PoisTest.java index 7ea79337..26e41745 100644 --- a/tiles/src/test/java/com/protomaps/basemap/layers/PoisTest.java +++ b/tiles/src/test/java/com/protomaps/basemap/layers/PoisTest.java @@ -1,6 +1,7 @@ package com.protomaps.basemap.layers; import static com.onthegomap.planetiler.TestUtils.*; +import static org.junit.jupiter.api.Assertions.assertFalse; import com.onthegomap.planetiler.reader.SimpleFeature; import java.util.HashMap; @@ -197,6 +198,61 @@ void zoom_14_peak() { ))); } + @Test + void peakElevation() { + assertFeatures(14, + List.of(Map.of("kind", "peak", "min_zoom", 14, "elevation", 569)), + process(SimpleFeature.create( + newPoint(1, 1), + new HashMap<>(Map.of("natural", "peak", "ele", "569")), + "osm", null, 0 + ))); + } + + @Test + void peakElevationDecimal() { + assertFeatures(14, + List.of(Map.of("kind", "peak", "min_zoom", 14, "elevation", 570)), + process(SimpleFeature.create( + newPoint(1, 1), + new HashMap<>(Map.of("natural", "peak", "ele", "569.5")), + "osm", null, 0 + ))); + } + + @Test + void peakElevationMeterSuffix() { + assertFeatures(14, + List.of(Map.of("kind", "peak", "min_zoom", 14, "elevation", 569)), + process(SimpleFeature.create( + newPoint(1, 1), + new HashMap<>(Map.of("natural", "peak", "ele", "569 m")), + "osm", null, 0 + ))); + } + + @Test + void peakElevationMeterSuffixWithoutWhitespace() { + assertFeatures(14, + List.of(Map.of("kind", "peak", "min_zoom", 14, "elevation", 569)), + process(SimpleFeature.create( + newPoint(1, 1), + new HashMap<>(Map.of("natural", "peak", "ele", "569m")), + "osm", null, 0 + ))); + } + + @Test + void peakElevationNonMeterUnitOmitted() { + var features = process(SimpleFeature.create( + newPoint(1, 1), + new HashMap<>(Map.of("natural", "peak", "ele", "569 ft")), + "osm", null, 0 + )); + + assertFalse(toMap(features.iterator().next(), 14).containsKey("elevation")); + } + @Test void zoom_14_golfCourse() { assertFeatures(14, @@ -629,7 +685,7 @@ void zoom_8_forest_largeArea() { @Test void zoom_12_tallBuilding_100mHeight() { assertFeatures(12, - List.of(Map.of("kind", "office", "min_zoom", 12, "name", "Skyscraper", "elevation", "100")), + List.of(Map.of("kind", "office", "min_zoom", 12, "name", "Skyscraper", "elevation", 100)), process(SimpleFeature.create( AREA_12K_SQ_M, new HashMap<>(Map.of(