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 3c8423c5270..289841a53d6 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; @@ -30,8 +32,8 @@ import org.opentripplanner.utils.collection.ListUtils; /** - * 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 { @@ -49,11 +51,15 @@ public class DebugStyleSpec { private static final String BLACK = "#140d0e"; 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 +76,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 +103,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) ) @@ -183,7 +197,7 @@ private static List edges(VectorSourceLayer edges) { .vectorSourceLayer(edges) .lineColor(MAGENTA) .edgeFilter(EDGES_TO_DISPLAY) - .lineWidth(LINE_WIDTH) + .lineWidth(LINE_HALF_WIDTH) .lineOffset(LINE_OFFSET) .minZoom(6) .maxZoom(MAX_ZOOM) @@ -222,26 +236,32 @@ 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( + "permission", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) + .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 +269,59 @@ private static List traversalPermissions(VectorSourceLayer edges) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(); + return ListUtils.combine(permissionStyles, List.of(textStyle)); } + 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( + "noThruTraffic", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) + .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), permissionColor(p))) + .toList(); + } + private static String permissionColor(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 6f81e7fd998..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,7 +2,9 @@ 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; import java.util.List; import java.util.Map; @@ -10,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; @@ -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; @@ -219,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. */ @@ -235,6 +258,16 @@ public final StyleBuilder vertexFilter(Class... classToFilter) return filterClasses(classToFilter); } + 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; + } + 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 5dc68fc335f..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 @@ -8,6 +8,7 @@ import java.util.List; import org.opentripplanner.apis.support.mapping.PropertyMapper; 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,22 @@ private static List mapStreetEdge(StreetEdge se) { } return props; } + + public static String streetPermissionAsString(StreetTraversalPermission permission) { + return permission.name().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..2a25a3722f9 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" : "#f21d52", "line-width" : [ "interpolate", [ @@ -45,9 +45,9 @@ "zoom" ], 13, - 0.2, + 0.1, 23, - 8.0 + 6.0 ], "line-offset" : [ "interpolate", @@ -58,33 +58,93 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "NONE" + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" ], "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 + }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], + "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" : "#22DD9E", "line-width" : [ "interpolate", [ @@ -107,33 +167,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", + "ALL", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -156,33 +244,76 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE" + "any", + [ + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "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", + "ALL", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -205,33 +336,76 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "PEDESTRIAN_AND_BICYCLE" + "any", + [ + "in", + "BICYCLE", + [ + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "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", + "ALL", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -254,47 +428,76 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "CAR" + "any", + [ + "in", + "CAR", + [ + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "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 +505,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", + "ALL", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -352,33 +578,76 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE_AND_CAR" + "any", + [ + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "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", + "ALL", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -401,91 +670,76 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "ALL" + "any", + [ + "in", + "BICYCLE", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "No-thru traffic" } }, { - "id" : "permission-text", + "id" : "no-thru-traffic CAR", "source" : "vectorSource", "source-layer" : "edges", - "type" : "symbol", - "minzoom" : 17, + "type" : "line", + "minzoom" : 13, "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" - ], - "layout" : { - "symbol-placement" : "line-center", - "symbol-spacing" : 1000, - "text-field" : "{permission}", - "text-font" : [ - "KlokanTech Noto Sans Regular" - ], - "text-size" : [ - "interpolate", - [ - "linear" - ], + "line-color" : [ + "match", [ - "zoom" + "get", + "noThruTraffic" ], - 10, - 6.0, - 24, - 12.0 + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "ALL", + "#adb2b0", + "#140d0e" ], - "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" - } - }, - { - "id" : "edge", - "type" : "line", - "source" : "vectorSource", - "source-layer" : "edges", - "minzoom" : 6, - "maxzoom" : 23, - "paint" : { - "line-color" : "#f21d52", "line-width" : [ "interpolate", [ @@ -508,35 +762,49 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "in", - "class", - "StreetEdge", - "AreaEdge", - "EscalatorEdge", - "PathwayEdge", - "ElevatorHopEdge", - "TemporaryPartialStreetEdge", - "TemporaryFreeEdge" + "any", + [ + "in", + "CAR", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "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 +827,7 @@ "layout" : { "symbol-placement" : "line-center", "symbol-spacing" : 1000, - "text-field" : "{name}", + "text-field" : "{noThruTraffic}", "text-font" : [ "KlokanTech Noto Sans Regular" ], @@ -580,64 +848,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" } }, { 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..8eb66f8c446 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'), []); @@ -76,9 +77,7 @@ 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={['regular-stop', 'area-stop', 'group-stop', 'parking-vertex', 'vertex', 'edge', 'link']} + interactiveLayerIds={interactiveLayerIds} cursor={cursor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} @@ -97,7 +96,7 @@ export function MapView({ setTripQueryVariables={setTripQueryVariables} loading={loading} /> - + {tripQueryResult?.trip.tripPatterns.length && ( )}