From 5d5d6439b6325d5383be744cf5271715053d16ca Mon Sep 17 00:00:00 2001 From: Clay Smalley Date: Sun, 24 Sep 2023 18:34:10 -0400 Subject: [PATCH] add bridge rendering --- src/constants/color.js | 2 + src/js/util.js | 32 +++++++++++ src/layer/bridge.js | 123 +++++++++++++++++++++++++++++++++++++++++ src/layer/index.js | 4 +- src/layer/rail.js | 60 +------------------- 5 files changed, 163 insertions(+), 58 deletions(-) create mode 100644 src/layer/bridge.js diff --git a/src/constants/color.js b/src/constants/color.js index 4472ec6b7..eab1e5103 100644 --- a/src/constants/color.js +++ b/src/constants/color.js @@ -1,6 +1,8 @@ export const backgroundFill = `hsl(30, 44%, 96%)`; export const backgroundFillTranslucent = `hsla(30, 44%, 96%, 0.8)`; +export const bridgeFill = "hsl(0, 20%, 80%)"; + export const waterFill = "hsl(211, 50%, 85%)"; export const waterFillTranslucent = "hsla(211, 50%, 85%, 0.5)"; export const waterIntermittentFill = "hsla(211, 60%, 85%, 0.3)"; diff --git a/src/js/util.js b/src/js/util.js index 4c14a71a8..846165ea5 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -38,3 +38,35 @@ export function zoomMultiply(arr, multiplier) { } return transformedArray; } + +//Create a zoom interpolation expression, given width at zoom 20 +export function zoomInterpolate(widthZ20) { + return [ + "interpolate", + ["exponential", 1.2], + ["zoom"], + 8, + multiplyMatchExpression(widthZ20, 1 / 16), + 12, + multiplyMatchExpression(widthZ20, 1 / 4), + 20, + widthZ20, + ]; +} + +export function multiplyMatchExpression(value, factor) { + if (Array.isArray(value)) { + var result = [value[0], value[1]]; + for (let i = 2; i < value.length - 1; i++) { + if (i % 2 == 0) { + result.push(value[i]); + } else { + result.push(multiplyMatchExpression(value[i], factor)); + } + } + result.push(multiplyMatchExpression(value[value.length - 1], factor)); + return result; + } else { + return value * factor; + } +} diff --git a/src/layer/bridge.js b/src/layer/bridge.js new file mode 100644 index 000000000..0dbd9b716 --- /dev/null +++ b/src/layer/bridge.js @@ -0,0 +1,123 @@ +"use strict"; + +import * as Color from "../constants/color.js"; +import * as Util from "../js/util.js"; + +// Bridge areas +export const bridge = { + type: "fill-extrusion", + source: "openmaptiles", + "source-layer": "transportation", + id: "bridge", + minzoom: 13, + layout: { + visibility: "visible", + }, + paint: { + "fill-extrusion-color": Color.bridgeFill, + "fill-extrusion-height": ["+", 3, ["coalesce", ["get", "layer"], 0]], + "fill-extrusion-opacity": 0.6, + }, + filter: ["all", ["==", ["get", "class"], "bridge"]], +}; + +// Bridge casing for highways and railways +export const bridgeCasing = { + type: "line", + source: "openmaptiles", + "source-layer": "transportation", + id: "bridge_casing", + minzoom: 13, + layout: { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }, + paint: { + "line-color": Color.bridgeFill, + "line-opacity": 0.8, + "line-width": Util.zoomInterpolate([ + "match", + ["get", "class"], + ["rail", "transit"], + ["match", ["get", "service"], ["siding", "spur", "yard"], 10, 14], + "motorway", + ["match", ["get", "ramp"], 1, 18, 28], + "trunk", + [ + "match", + ["get", "expressway"], + 1, + 34, + ["match", ["get", "ramp"], 1, 16, 28], + ], + "primary", + [ + "match", + ["get", "expressway"], + 1, + 32, + ["match", ["get", "ramp"], 1, 15, 24], + ], + "secondary", + [ + "match", + ["get", "expressway"], + 1, + 26, + ["match", ["get", "ramp"], 1, 14, 20], + ], + ["tertiary", "busway", "bus_guideway"], + [ + "match", + ["get", "expressway"], + 1, + 20, + ["match", ["get", "ramp"], 1, 13, 18], + ], + "minor", + 14, + "service", + [ + "match", + ["get", "service"], + ["alley", "driveway", "drive-through", "parking_aisle"], + 9, + 11, + ], + 20, + ]), + }, + filter: [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "in", + ["get", "class"], + [ + "literal", + [ + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "busway", + "bus_guideway", + "minor", + "service", + "rail", + "transit", + ], + ], + ], + ], +}; + +export const legendEntries = [ + { + description: "Bridge", + layers: [bridge.id], + filter: ["==", ["get", "class"], "bridge"], + }, +]; diff --git a/src/layer/index.js b/src/layer/index.js index 95fada71d..814e36e04 100644 --- a/src/layer/index.js +++ b/src/layer/index.js @@ -6,6 +6,7 @@ import * as lyrAerialway from "./aerialway.js"; import * as lyrAeroway from "./aeroway.js"; import * as lyrBackground from "./background.js"; import * as lyrBoundary from "./boundary.js"; +import * as lyrBridge from "./bridge.js"; import * as lyrConstruction from "./construction.js"; import * as lyrHighwayShield from "./highway_shield.js"; import * as lyrLanduse from "./landuse.js"; @@ -143,7 +144,8 @@ export function build(locales) { layers.push(lyrBuilding.building); var bridgeLayers = [ - lyrRail.bridgeCasing, + lyrBridge.bridge, + lyrBridge.bridgeCasing, lyrRoad.trunkLinkBridge.casing(), lyrRoad.motorwayLinkBridge.casing(), diff --git a/src/layer/rail.js b/src/layer/rail.js index da96593a5..e3e43d953 100644 --- a/src/layer/rail.js +++ b/src/layer/rail.js @@ -6,38 +6,6 @@ import * as Util from "../js/util.js"; // Exponent base for inter-zoom interpolation let railExp = 1.2; -// Helper functions to create zoom interpolation expressions -function multiplyMatchExpression(value, factor) { - if (Array.isArray(value)) { - var result = [value[0], value[1]]; - for (let i = 2; i < value.length - 1; i++) { - if (i % 2 == 0) { - result.push(value[i]); - } else { - result.push(multiplyMatchExpression(value[i], factor)); - } - } - result.push(multiplyMatchExpression(value[value.length - 1], factor)); - return result; - } else { - return value * factor; - } -} - -function zoomInterpolate(widthZ20) { - return [ - "interpolate", - ["exponential", railExp], - ["zoom"], - 8, - multiplyMatchExpression(widthZ20, 1 / 16), - 12, - multiplyMatchExpression(widthZ20, 1 / 4), - 20, - widthZ20, - ]; -} - // Helper function to create a "filter" block for a particular railway class. function filterRail(brunnel) { return [ @@ -103,28 +71,6 @@ var opacity = [ 1, ]; -// Bridge casing layers -export const bridgeCasing = { - ...defRail, - id: "rail_bridge-casing", - filter: [ - "all", - ["==", ["get", "brunnel"], "bridge"], - ["in", ["get", "class"], ["literal", ["rail", "transit"]]], - ], - minzoom: 13, - layout: { - "line-cap": "butt", - "line-join": "bevel", - visibility: "visible", - }, - paint: { - "line-color": Color.backgroundFill, - "line-opacity": opacity, - "line-width": zoomInterpolate([...serviceSelector, 4, 6]), - }, -}; - // Generate a unique layer ID function uniqueLayerID(part, brunnel, constraints) { var layerID = ["rail", part, brunnel].join("_"); @@ -168,7 +114,7 @@ class Railway { layer.paint = { "line-color": lineColor, "line-opacity": opacity, - "line-width": zoomInterpolate(lineWidth), + "line-width": Util.zoomInterpolate(lineWidth), }; if (this.constraints != null) { layer.filter.push(this.constraints); @@ -191,8 +137,8 @@ class Railway { layer.paint = { "line-color": lineColor, "line-opacity": opacity, - "line-width": zoomInterpolate( - multiplyMatchExpression(lineWidth, this.dashWidthFactor) + "line-width": Util.zoomInterpolate( + Util.multiplyMatchExpression(lineWidth, this.dashWidthFactor) ), "line-dasharray": this.dashArray.map( (stop) => stop / 2 / this.dashWidthFactor