From 336fb13e956e1eef5a40b75e9bba4cd026fc1a69 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Fri, 11 Oct 2024 13:14:13 +0200 Subject: [PATCH 1/5] vector tiles: add no-thru traffic layers + group no-thru traffic and permission layers by mode --- .../apis/vectortiles/DebugStyleSpec.java | 102 ++- .../apis/vectortiles/model/StyleBuilder.java | 38 +- .../vector/edge/EdgePropertyMapper.java | 28 +- .../apis/vectortiles/style.json | 579 +++++++++++------- 4 files changed, 490 insertions(+), 257 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index 1fc87176af8..beb8a087866 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -1,5 +1,7 @@ package org.opentripplanner.apis.vectortiles; +import static org.opentripplanner.inspector.vector.edge.EdgePropertyMapper.streetPermissionAsString; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -13,6 +15,7 @@ import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; +import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.AreaEdge; import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; @@ -30,8 +33,8 @@ import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; /** - * A Mapbox/Mapblibre style specification for rendering debug information about transit and - * street data. + * A Mapbox/Mapblibre style specification for rendering debug information about transit and street + * data. */ public class DebugStyleSpec { @@ -47,13 +50,18 @@ public class DebugStyleSpec { private static final String DARK_GREEN = "#136b04"; private static final String PURPLE = "#BC55F2"; private static final String BLACK = "#140d0e"; + private static final String GRAY = "#DDDDDD"; private static final int MAX_ZOOM = 23; + private static final int LINE_DETAIL_ZOOM = 13; private static final ZoomDependentNumber LINE_OFFSET = new ZoomDependentNumber( - List.of(new ZoomStop(13, 0.3f), new ZoomStop(MAX_ZOOM, 6)) + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.4f), new ZoomStop(MAX_ZOOM, 7)) ); private static final ZoomDependentNumber LINE_WIDTH = new ZoomDependentNumber( - List.of(new ZoomStop(13, 0.2f), new ZoomStop(MAX_ZOOM, 8)) + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.2f), new ZoomStop(MAX_ZOOM, 8)) + ); + private static final ZoomDependentNumber LINE_HALF_WIDTH = new ZoomDependentNumber( + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.1f), new ZoomStop(MAX_ZOOM, 6)) ); private static final ZoomDependentNumber CIRCLE_STROKE = new ZoomDependentNumber( List.of(new ZoomStop(15, 0.2f), new ZoomStop(MAX_ZOOM, 3)) @@ -70,7 +78,14 @@ public class DebugStyleSpec { private static final String EDGES_GROUP = "Edges"; private static final String STOPS_GROUP = "Stops"; private static final String VERTICES_GROUP = "Vertices"; - private static final String TRAVERSAL_PERMISSIONS_GROUP = "Traversal permissions"; + private static final String PERMISSIONS_GROUP = "Permissions"; + private static final String NO_THRU_TRAFFIC_GROUP = "No-thru traffic"; + + private static final StreetTraversalPermission[] streetModes = new StreetTraversalPermission[] { + StreetTraversalPermission.PEDESTRIAN, + StreetTraversalPermission.BICYCLE, + StreetTraversalPermission.CAR, + }; static StyleSpec build( VectorSourceLayer regularStops, @@ -90,8 +105,9 @@ static StyleSpec build( allSources, ListUtils.combine( List.of(StyleBuilder.ofId("background").typeRaster().source(BACKGROUND_SOURCE).minZoom(0)), - traversalPermissions(edges), edges(edges), + traversalPermissions(edges), + noThruTraffic(edges), vertices(vertices), stops(regularStops, areaStops, groupStops) ) @@ -181,9 +197,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(MAGENTA) - .edgeFilter(EDGES_TO_DISPLAY) - .lineWidth(LINE_WIDTH) + .lineColor(GRAY) + .lineWidth(LINE_HALF_WIDTH) .lineOffset(LINE_OFFSET) .minZoom(6) .maxZoom(MAX_ZOOM) @@ -194,7 +209,6 @@ private static List edges(VectorSourceLayer edges) { .typeSymbol() .lineText("name") .vectorSourceLayer(edges) - .edgeFilter(EDGES_TO_DISPLAY) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(), @@ -203,7 +217,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(BRIGHT_GREEN) + .lineColor(GRAY) + .lineOpacity(0.2f) .edgeFilter( StreetTransitStopLink.class, StreetTransitEntranceLink.class, @@ -222,26 +237,28 @@ private static List edges(VectorSourceLayer edges) { private static List traversalPermissions(VectorSourceLayer edges) { var permissionStyles = Arrays - .stream(StreetTraversalPermission.values()) - .map(p -> + .stream(streetModes) + .map(streetTraversalPermission -> StyleBuilder - .ofId(p.name()) + .ofId("permission " + streetTraversalPermission) .vectorSourceLayer(edges) - .group(TRAVERSAL_PERMISSIONS_GROUP) + .group(PERMISSIONS_GROUP) .typeLine() - .lineColor(permissionColor(p)) - .permissionsFilter(p) + .filterValueInProperty(streetTraversalPermission.name(), "permission") + .lineCap("butt") + .lineColorMatch("permission", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) .lineOffset(LINE_OFFSET) - .minZoom(6) + .minZoom(LINE_DETAIL_ZOOM) .maxZoom(MAX_ZOOM) .intiallyHidden() ) .toList(); + var textStyle = StyleBuilder .ofId("permission-text") .vectorSourceLayer(edges) - .group(TRAVERSAL_PERMISSIONS_GROUP) + .group(PERMISSIONS_GROUP) .typeSymbol() .lineText("permission") .textOffset(1) @@ -249,12 +266,55 @@ private static List traversalPermissions(VectorSourceLayer edges) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(); + return ListUtils.combine(permissionStyles, List.of(textStyle)); } - private static String permissionColor(StreetTraversalPermission p) { + private static List noThruTraffic(VectorSourceLayer edges) { + var noThruTrafficStyles = Arrays + .stream(streetModes) + .map(streetTraversalPermission -> + StyleBuilder + .ofId("no-thru-traffic " + streetTraversalPermission) + .vectorSourceLayer(edges) + .group(NO_THRU_TRAFFIC_GROUP) + .typeLine() + .filterValueInProperty(streetTraversalPermission.name(), "noThruTraffic") + .lineCap("butt") + .lineColorMatch("noThruTraffic", permissionColors(), BLACK) + .lineWidth(LINE_WIDTH) + .lineOffset(LINE_OFFSET) + .minZoom(LINE_DETAIL_ZOOM) + .maxZoom(MAX_ZOOM) + .intiallyHidden() + ) + .toList(); + + var textStyle = StyleBuilder + .ofId("no-thru-traffic-text") + .vectorSourceLayer(edges) + .group(NO_THRU_TRAFFIC_GROUP) + .typeSymbol() + .lineText("noThruTraffic") + .textOffset(1) + .edgeFilter(EDGES_TO_DISPLAY) + .minZoom(17) + .maxZoom(MAX_ZOOM) + .intiallyHidden(); + + return ListUtils.combine(noThruTrafficStyles, List.of(textStyle)); + } + + private static List permissionColors() { + return Arrays + .stream(StreetTraversalPermission.values()) + .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColors(p))) + .toList(); + } + + private static String permissionColors(StreetTraversalPermission p) { return switch (p) { - case NONE -> "#000"; + case NONE -> BLACK; case PEDESTRIAN -> "#2ba812"; case BICYCLE, PEDESTRIAN_AND_BICYCLE -> "#10d3b6"; case CAR -> "#f92e13"; diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index 93b7ea91e7c..316fa56d4eb 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -29,7 +30,7 @@ public class StyleBuilder { private final Map layout = new LinkedHashMap<>(); private final Map metadata = new LinkedHashMap<>(); private final Map line = new LinkedHashMap<>(); - private List filter = List.of(); + private List filter = List.of(); public static StyleBuilder ofId(String id) { return new StyleBuilder(id); @@ -167,12 +168,42 @@ public StyleBuilder circleRadius(ZoomDependentNumber radius) { } // Line styling + public StyleBuilder lineCap(String lineCap) { + layout.put("line-cap", lineCap); + return this; + } public StyleBuilder lineColor(String color) { paint.put("line-color", validateColor(color)); return this; } + public StyleBuilder lineColorMatch( + String propertyName, + Collection values, + String defaultValue + ) { + paint.put( + "line-color", + ListUtils.combine( + List.of("match", List.of("get", propertyName)), + (Collection) values, + List.of(defaultValue) + ) + ); + return this; + } + + public StyleBuilder lineOpacity(float lineOpacity) { + paint.put("line-opacity", lineOpacity); + return this; + } + + public StyleBuilder lineDasharray(float... dashArray) { + paint.put("line-dasharray", dashArray); + return this; + } + public StyleBuilder lineWidth(float width) { paint.put("line-width", width); return this; @@ -235,6 +266,11 @@ public final StyleBuilder vertexFilter(Class... classToFilter) return filterClasses(classToFilter); } + public StyleBuilder filterValueInProperty(String value, String propertyName) { + filter = List.of("in", value, List.of("string", List.of("get", propertyName))); + return this; + } + public JsonNode toJson() { validate(); diff --git a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java index d43a91d384d..ae7e4969428 100644 --- a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java +++ b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java @@ -9,6 +9,7 @@ import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.EscalatorEdge; import org.opentripplanner.street.model.edge.StreetEdge; @@ -29,8 +30,9 @@ protected Collection map(Edge input) { private static List mapStreetEdge(StreetEdge se) { var props = Lists.newArrayList( - kv("permission", se.getPermission().toString()), - kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor())) + kv("permission", streetPermissionAsString(se.getPermission())), + kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor())), + kv("noThruTraffic", noThruTrafficAsString(se)) ); if (se.hasBogusName()) { props.addFirst(kv("name", "%s (generated)".formatted(se.getName().toString()))); @@ -39,4 +41,26 @@ private static List mapStreetEdge(StreetEdge se) { } return props; } + + public static String streetPermissionAsString(StreetTraversalPermission permission) { + return ( + permission == StreetTraversalPermission.ALL + ? "PEDESTRIAN_AND_BICYCLE_AND_CAR" + : permission.toString() + ).replace("_AND_", " "); + } + + private static String noThruTrafficAsString(StreetEdge se) { + var noThruPermission = StreetTraversalPermission.NONE; + if (se.isWalkNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.PEDESTRIAN); + } + if (se.isBicycleNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.BICYCLE); + } + if (se.isMotorVehicleNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.CAR); + } + return streetPermissionAsString(noThruPermission); + } } diff --git a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index 8a0e457396e..1aad369a557 100644 --- a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -28,14 +28,14 @@ } }, { - "id" : "NONE", + "id" : "edge", + "type" : "line", "source" : "vectorSource", "source-layer" : "edges", - "type" : "line", "minzoom" : 6, "maxzoom" : 23, "paint" : { - "line-color" : "#000", + "line-color" : "#DDDDDD", "line-width" : [ "interpolate", [ @@ -45,9 +45,9 @@ "zoom" ], 13, - 0.2, + 0.1, 23, - 8.0 + 6.0 ], "line-offset" : [ "interpolate", @@ -58,33 +58,72 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, - "filter" : [ - "==", - "permission", - "NONE" - ], "layout" : { "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Edges" } }, { - "id" : "PEDESTRIAN", + "id" : "edge-name", + "type" : "symbol", "source" : "vectorSource", "source-layer" : "edges", + "minzoom" : 17, + "maxzoom" : 23, + "paint" : { + "text-color" : "#000", + "text-halo-color" : "#fff", + "text-halo-blur" : 4, + "text-halo-width" : 3 + }, + "layout" : { + "symbol-placement" : "line-center", + "symbol-spacing" : 1000, + "text-field" : "{name}", + "text-font" : [ + "KlokanTech Noto Sans Regular" + ], + "text-size" : [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 6.0, + 24, + 12.0 + ], + "text-max-width" : 100, + "text-keep-upright" : true, + "text-rotation-alignment" : "map", + "text-overlap" : "never", + "visibility" : "none" + }, + "metadata" : { + "group" : "Edges" + } + }, + { + "id" : "link", "type" : "line", - "minzoom" : 6, + "source" : "vectorSource", + "source-layer" : "edges", + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#2ba812", + "line-color" : "#DDDDDD", + "line-opacity" : 0.2, "line-width" : [ "interpolate", [ @@ -107,33 +146,61 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "PEDESTRIAN" + "in", + "class", + "StreetTransitStopLink", + "StreetTransitEntranceLink", + "BoardingLocationToStopLink", + "StreetVehicleRentalLink", + "StreetVehicleParkingLink", + "StreetStationCentroidLink" ], "layout" : { "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Edges" } }, { - "id" : "BICYCLE", + "id" : "permission PEDESTRIAN", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#10d3b6", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -156,33 +223,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE" + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "PEDESTRIAN_AND_BICYCLE", + "id" : "permission BICYCLE", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#10d3b6", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -205,33 +301,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "PEDESTRIAN_AND_BICYCLE" + "in", + "BICYCLE", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "CAR", + "id" : "permission CAR", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#f92e13", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -254,47 +379,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "CAR" + "in", + "CAR", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "PEDESTRIAN_AND_CAR", + "id" : "permission-text", "source" : "vectorSource", "source-layer" : "edges", - "type" : "line", - "minzoom" : 6, + "type" : "symbol", + "minzoom" : 17, "maxzoom" : 23, "paint" : { - "line-color" : "#e25f8f", - "line-width" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.2, - 23, - 8.0 + "text-color" : "#000", + "text-halo-color" : "#fff", + "text-halo-blur" : 4, + "text-halo-width" : 3 + }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], + "layout" : { + "symbol-placement" : "line-center", + "symbol-spacing" : 1000, + "text-field" : "{permission}", + "text-font" : [ + "KlokanTech Noto Sans Regular" ], - "line-offset" : [ + "text-size" : [ "interpolate", [ "linear" @@ -302,34 +442,57 @@ [ "zoom" ], - 13, - 0.3, - 23, - 6.0 - ] - }, - "filter" : [ - "==", - "permission", - "PEDESTRIAN_AND_CAR" - ], - "layout" : { - "line-cap" : "round", + 10, + 6.0, + 24, + 12.0 + ], + "text-max-width" : 100, + "text-keep-upright" : true, + "text-rotation-alignment" : "map", + "text-overlap" : "never", + "text-offset" : [ + 0, + 1.0 + ], "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "BICYCLE_AND_CAR", + "id" : "no-thru-traffic PEDESTRIAN", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#e25f8f", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -352,33 +515,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE_AND_CAR" + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "No-thru traffic" } }, { - "id" : "ALL", + "id" : "no-thru-traffic BICYCLE", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#adb2b0", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -401,91 +593,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, - "filter" : [ - "==", - "permission", - "ALL" - ], - "layout" : { - "line-cap" : "round", - "visibility" : "none" - }, - "metadata" : { - "group" : "Traversal permissions" - } - }, - { - "id" : "permission-text", - "source" : "vectorSource", - "source-layer" : "edges", - "type" : "symbol", - "minzoom" : 17, - "maxzoom" : 23, - "paint" : { - "text-color" : "#000", - "text-halo-color" : "#fff", - "text-halo-blur" : 4, - "text-halo-width" : 3 - }, "filter" : [ "in", - "class", - "StreetEdge", - "AreaEdge", - "EscalatorEdge", - "PathwayEdge", - "ElevatorHopEdge", - "TemporaryPartialStreetEdge", - "TemporaryFreeEdge" + "BICYCLE", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "symbol-placement" : "line-center", - "symbol-spacing" : 1000, - "text-field" : "{permission}", - "text-font" : [ - "KlokanTech Noto Sans Regular" - ], - "text-size" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 10, - 6.0, - 24, - 12.0 - ], - "text-max-width" : 100, - "text-keep-upright" : true, - "text-rotation-alignment" : "map", - "text-overlap" : "never", - "text-offset" : [ - 0, - 1.0 - ], + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "No-thru traffic" } }, { - "id" : "edge", - "type" : "line", + "id" : "no-thru-traffic CAR", "source" : "vectorSource", "source-layer" : "edges", - "minzoom" : 6, + "type" : "line", + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#f21d52", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -508,35 +671,35 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ "in", - "class", - "StreetEdge", - "AreaEdge", - "EscalatorEdge", - "PathwayEdge", - "ElevatorHopEdge", - "TemporaryPartialStreetEdge", - "TemporaryFreeEdge" + "CAR", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Edges" + "group" : "No-thru traffic" } }, { - "id" : "edge-name", - "type" : "symbol", + "id" : "no-thru-traffic-text", "source" : "vectorSource", "source-layer" : "edges", + "type" : "symbol", "minzoom" : 17, "maxzoom" : 23, "paint" : { @@ -559,7 +722,7 @@ "layout" : { "symbol-placement" : "line-center", "symbol-spacing" : 1000, - "text-field" : "{name}", + "text-field" : "{noThruTraffic}", "text-font" : [ "KlokanTech Noto Sans Regular" ], @@ -580,64 +743,14 @@ "text-keep-upright" : true, "text-rotation-alignment" : "map", "text-overlap" : "never", - "visibility" : "none" - }, - "metadata" : { - "group" : "Edges" - } - }, - { - "id" : "link", - "type" : "line", - "source" : "vectorSource", - "source-layer" : "edges", - "minzoom" : 13, - "maxzoom" : 23, - "paint" : { - "line-color" : "#22DD9E", - "line-width" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.2, - 23, - 8.0 + "text-offset" : [ + 0, + 1.0 ], - "line-offset" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.3, - 23, - 6.0 - ] - }, - "filter" : [ - "in", - "class", - "StreetTransitStopLink", - "StreetTransitEntranceLink", - "BoardingLocationToStopLink", - "StreetVehicleRentalLink", - "StreetVehicleParkingLink", - "StreetStationCentroidLink" - ], - "layout" : { - "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Edges" + "group" : "No-thru traffic" } }, { From 14cef7cba71f9309a968e541586d6de08450a3f9 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 21:57:38 +0100 Subject: [PATCH 2/5] vector tile: use ALL for permissions --- .../apis/vectortiles/DebugStyleSpec.java | 12 ++++++++++-- .../apis/vectortiles/model/StyleBuilder.java | 19 ++++++++----------- .../vector/edge/EdgePropertyMapper.java | 6 +----- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index f821f7c0e82..999b90a8bbd 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -244,7 +244,11 @@ private static List traversalPermissions(VectorSourceLayer edges) .vectorSourceLayer(edges) .group(PERMISSIONS_GROUP) .typeLine() - .filterValueInProperty(streetTraversalPermission.name(), "permission") + .filterValueInProperty( + "permission", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) .lineCap("butt") .lineColorMatch("permission", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) @@ -279,7 +283,11 @@ private static List noThruTraffic(VectorSourceLayer edges) { .vectorSourceLayer(edges) .group(NO_THRU_TRAFFIC_GROUP) .typeLine() - .filterValueInProperty(streetTraversalPermission.name(), "noThruTraffic") + .filterValueInProperty( + "noThruTraffic", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) .lineCap("butt") .lineColorMatch("noThruTraffic", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index c2b9c2a9d22..d84ee0f533d 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -11,7 +12,6 @@ import java.util.stream.Stream; import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.framework.json.ObjectMappers; -import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.utils.collection.ListUtils; @@ -250,14 +250,6 @@ public final StyleBuilder edgeFilter(Class... classToFilter) { return filterClasses(classToFilter); } - /** - * Filter the entities by their "permission" property. - */ - public final StyleBuilder permissionsFilter(StreetTraversalPermission p) { - filter = List.of("==", "permission", p.name()); - return this; - } - /** * Only apply the style to the given vertices. */ @@ -266,8 +258,13 @@ public final StyleBuilder vertexFilter(Class... classToFilter) return filterClasses(classToFilter); } - public StyleBuilder filterValueInProperty(String value, String propertyName) { - filter = List.of("in", value, List.of("string", List.of("get", propertyName))); + public StyleBuilder filterValueInProperty(String propertyName, String... values) { + var newFilter = new ArrayList<>(); + newFilter.add("any"); + for (String value : values) { + newFilter.add(List.of("in", value, List.of("string", List.of("get", propertyName)))); + } + filter = newFilter; return this; } diff --git a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java index 70041c6f5b4..d6e2d7250ea 100644 --- a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java +++ b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java @@ -43,11 +43,7 @@ private static List mapStreetEdge(StreetEdge se) { } public static String streetPermissionAsString(StreetTraversalPermission permission) { - return ( - permission == StreetTraversalPermission.ALL - ? "PEDESTRIAN_AND_BICYCLE_AND_CAR" - : permission.toString() - ).replace("_AND_", " "); + return permission.name().replace("_AND_", " "); } private static String noThruTrafficAsString(StreetEdge se) { From 3415473bbb3012158f687086501bdb472dafa38b Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 21:57:56 +0100 Subject: [PATCH 3/5] vector tile: update/restore edge styles --- .../apis/vectortiles/DebugStyleSpec.java | 13 +- .../apis/vectortiles/style.json | 183 ++++++++++++++---- 2 files changed, 150 insertions(+), 46 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index 999b90a8bbd..289841a53d6 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -14,7 +14,6 @@ import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber; import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; -import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.AreaEdge; import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; @@ -50,7 +49,6 @@ public class DebugStyleSpec { private static final String DARK_GREEN = "#136b04"; private static final String PURPLE = "#BC55F2"; private static final String BLACK = "#140d0e"; - private static final String GRAY = "#DDDDDD"; private static final int MAX_ZOOM = 23; private static final int LINE_DETAIL_ZOOM = 13; @@ -197,7 +195,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(GRAY) + .lineColor(MAGENTA) + .edgeFilter(EDGES_TO_DISPLAY) .lineWidth(LINE_HALF_WIDTH) .lineOffset(LINE_OFFSET) .minZoom(6) @@ -209,6 +208,7 @@ private static List edges(VectorSourceLayer edges) { .typeSymbol() .lineText("name") .vectorSourceLayer(edges) + .edgeFilter(EDGES_TO_DISPLAY) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(), @@ -217,8 +217,7 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(GRAY) - .lineOpacity(0.2f) + .lineColor(BRIGHT_GREEN) .edgeFilter( StreetTransitStopLink.class, StreetTransitEntranceLink.class, @@ -316,11 +315,11 @@ private static List noThruTraffic(VectorSourceLayer edges) { private static List permissionColors() { return Arrays .stream(StreetTraversalPermission.values()) - .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColors(p))) + .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColor(p))) .toList(); } - private static String permissionColors(StreetTraversalPermission p) { + private static String permissionColor(StreetTraversalPermission p) { return switch (p) { case NONE -> BLACK; case PEDESTRIAN -> "#2ba812"; diff --git a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index 1aad369a557..2a25a3722f9 100644 --- a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -35,7 +35,7 @@ "minzoom" : 6, "maxzoom" : 23, "paint" : { - "line-color" : "#DDDDDD", + "line-color" : "#f21d52", "line-width" : [ "interpolate", [ @@ -63,6 +63,17 @@ 7.0 ] }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], "layout" : { "line-cap" : "round", "visibility" : "none" @@ -84,6 +95,17 @@ "text-halo-blur" : 4, "text-halo-width" : 3 }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], "layout" : { "symbol-placement" : "line-center", "symbol-spacing" : 1000, @@ -122,8 +144,7 @@ "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#DDDDDD", - "line-opacity" : 0.2, + "line-color" : "#22DD9E", "line-width" : [ "interpolate", [ @@ -197,7 +218,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -229,13 +250,27 @@ ] }, "filter" : [ - "in", - "PEDESTRIAN", + "any", [ - "string", + "in", + "PEDESTRIAN", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -275,7 +310,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -307,13 +342,27 @@ ] }, "filter" : [ - "in", - "BICYCLE", + "any", [ - "string", + "in", + "BICYCLE", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -353,7 +402,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -385,13 +434,27 @@ ] }, "filter" : [ - "in", - "CAR", + "any", [ - "string", + "in", + "CAR", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -489,7 +552,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -521,13 +584,27 @@ ] }, "filter" : [ - "in", - "PEDESTRIAN", + "any", [ - "string", + "in", + "PEDESTRIAN", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], @@ -567,7 +644,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -599,13 +676,27 @@ ] }, "filter" : [ - "in", - "BICYCLE", + "any", [ - "string", + "in", + "BICYCLE", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], @@ -645,7 +736,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -677,13 +768,27 @@ ] }, "filter" : [ - "in", - "CAR", + "any", [ - "string", + "in", + "CAR", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], From 8e786c016ef0fa36a0a6db6acb3f7e81b1714999 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 22:02:12 +0100 Subject: [PATCH 4/5] debug client: dynamically update interactive layers --- .../src/components/MapView/LayerControl.tsx | 33 ++++++++++++++++--- client/src/components/MapView/MapView.tsx | 5 +-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index d6be2d641d7..e79ae95e61e 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -4,6 +4,7 @@ import { IControl, Map, TypedStyleLayer } from 'maplibre-gl'; type LayerControlProps = { position: ControlPosition; + setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; }; /** @@ -15,6 +16,12 @@ type LayerControlProps = { class LayerControl implements IControl { private readonly container: HTMLDivElement = document.createElement('div'); + private readonly setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; + + constructor(setInteractiveLayerIds: (interactiveLayerIds: string[]) => void) { + this.setInteractiveLayerIds = setInteractiveLayerIds; + } + onAdd(map: Map) { this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group layer-select'; @@ -32,10 +39,8 @@ class LayerControl implements IControl { map .getLayersOrder() .map((l) => map.getLayer(l)) - .filter((s) => s?.type !== 'raster') - // the polylines of the routing result are put in map layers called jsx-1, jsx-2... - // we don't want them to show up in the debug layer selector - .filter((s) => !s?.id.startsWith('jsx')) + .filter((layer) => !!layer) + .filter((layer) => this.layerInteractive(layer)) .reverse() .forEach((layer) => { if (layer) { @@ -62,6 +67,17 @@ class LayerControl implements IControl { return this.container; } + private updateInteractiveLayerIds(map: Map) { + const visibleInteractiveLayerIds = map + .getLayersOrder() + .map((l) => map.getLayer(l)) + .filter((layer) => !!layer) + .filter((layer) => this.layerVisible(map, layer) && this.layerInteractive(layer)) + .map((layer) => layer.id); + + this.setInteractiveLayerIds(visibleInteractiveLayerIds); + } + private buildLayerDiv(layer: TypedStyleLayer, map: Map) { const layerDiv = document.createElement('div'); layerDiv.className = 'layer'; @@ -77,6 +93,7 @@ class LayerControl implements IControl { } else { map.setLayoutProperty(layer.id, 'visibility', 'none'); } + this.updateInteractiveLayerIds(map); }; input.checked = this.layerVisible(map, layer); input.className = 'layer'; @@ -118,13 +135,19 @@ class LayerControl implements IControl { return map.getLayoutProperty(layer.id, 'visibility') !== 'none'; } + private layerInteractive(layer: { id: string; type: string }) { + // the polylines of the routing result are put in map layers called jsx-1, jsx-2... + // we don't want them to show up in the debug layer selector + return layer?.type !== 'raster' && !layer?.id.startsWith('jsx'); + } + onRemove() { this.container.parentNode?.removeChild(this.container); } } export default function DebugLayerControl(props: LayerControlProps) { - useControl(() => new LayerControl(), { + useControl(() => new LayerControl(props.setInteractiveLayerIds), { position: props.position, }); diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index 4a6080a1b45..ec008853737 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -37,6 +37,7 @@ export function MapView({ const onMapDoubleClick = useMapDoubleClick({ tripQueryVariables, setTripQueryVariables }); const [showContextPopup, setShowContextPopup] = useState(null); const [showPropsPopup, setShowPropsPopup] = useState(null); + const [interactiveLayerIds, setInteractiveLayerIds] = useState([]); const [cursor, setCursor] = useState('auto'); const onMouseEnter = useCallback(() => setCursor('pointer'), []); const onMouseLeave = useCallback(() => setCursor('auto'), []); @@ -78,7 +79,7 @@ export function MapView({ }} // it's unfortunate that you have to list these layers here. // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 - interactiveLayerIds={['regular-stop', 'area-stop', 'group-stop', 'parking-vertex', 'vertex', 'edge', 'link']} + interactiveLayerIds={interactiveLayerIds} cursor={cursor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} @@ -97,7 +98,7 @@ export function MapView({ setTripQueryVariables={setTripQueryVariables} loading={loading} /> - + {tripQueryResult?.trip.tripPatterns.length && ( )} From 0b6fcacdbd969f100708571bee75305828da0d62 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Fri, 8 Nov 2024 08:36:29 +0100 Subject: [PATCH 5/5] debug client: remove obsolete comment ... since the list of interactive layer ids is updated when a layer's visibility is changed --- client/src/components/MapView/MapView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index ec008853737..8eb66f8c446 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -77,8 +77,6 @@ export function MapView({ onContextMenu={(e) => { setShowContextPopup(e.lngLat); }} - // it's unfortunate that you have to list these layers here. - // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 interactiveLayerIds={interactiveLayerIds} cursor={cursor} onMouseEnter={onMouseEnter}