diff --git a/src/constants/color.js b/src/constants/color.js index cf6851c19..1035a08a2 100644 --- a/src/constants/color.js +++ b/src/constants/color.js @@ -1,6 +1,9 @@ export const backgroundFill = `hsl(30, 44%, 96%)`; export const backgroundFillTranslucent = `hsla(30, 44%, 96%, 0.8)`; +export const bridgeFill = "hsl(30, 44%, 90%)"; +export const bridgeBackgroundFill = "hsl(30, 44%, 26%)"; + 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..e4594832f --- /dev/null +++ b/src/layer/bridge.js @@ -0,0 +1,183 @@ +"use strict"; + +import * as Color from "../constants/color.js"; +import * as Util from "../js/util.js"; + +const width = [ + "match", + ["get", "class"], + ["rail", "transit"], + ["match", ["get", "service"], ["siding", "spur", "yard"], 12, 16], + "motorway", + ["match", ["get", "ramp"], 1, 19, 29], + "trunk", + [ + "match", + ["get", "expressway"], + 1, + 35, + ["match", ["get", "ramp"], 1, 17, 29], + ], + "primary", + [ + "match", + ["get", "expressway"], + 1, + 33, + ["match", ["get", "ramp"], 1, 16, 26], + ], + "secondary", + [ + "match", + ["get", "expressway"], + 1, + 27, + ["match", ["get", "ramp"], 1, 15, 21], + ], + ["tertiary", "busway", "bus_guideway"], + [ + "match", + ["get", "expressway"], + 1, + 21, + ["match", ["get", "ramp"], 1, 14, 19], + ], + "minor", + 15, + "service", + [ + "match", + ["get", "service"], + ["alley", "driveway", "drive-through", "parking_aisle"], + 10, + 12, + ], + 0, +]; + +// Bridge areas +export const bridge = { + type: "fill", + source: "openmaptiles", + "source-layer": "transportation", + id: "bridge", + minzoom: 15, + layout: { + visibility: "visible", + }, + paint: { + "fill-color": Color.bridgeFill, + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 16, 0.9, 19, 0.8], + }, + filter: ["all", ["==", ["get", "class"], "bridge"]], +}; + +export const bridgeOutline = { + ...bridge, + type: "line", + id: "bridge-outline", + layout: { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }, + paint: { + "line-color": Color.bridgeBackgroundFill, + "line-opacity": ["interpolate", ["linear"], ["zoom"], 16, 1, 19, 0.4], + "line-width": 0.5, + }, +}; + +// 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-width": Util.zoomInterpolate(width), + }, + filter: [ + "all", + ["==", ["get", "brunnel"], "bridge"], + [ + "in", + ["get", "class"], + [ + "literal", + [ + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "busway", + "bus_guideway", + "minor", + "service", + "rail", + "transit", + ], + ], + ], + ], +}; + +export const bridgeCasingBackground = { + type: "line", + source: "openmaptiles", + "source-layer": "transportation", + id: "bridge_casing-background", + minzoom: 13, + layout: { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }, + paint: { + "line-color": Color.bridgeBackgroundFill, + "line-opacity": ["interpolate", ["linear"], ["zoom"], 16, 1, 19, 0.4], + "line-width": Util.zoomInterpolate( + Util.multiplyMatchExpression(width, 1.1) + ), + }, + 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 e23a4c422..5134ece2a 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"; @@ -145,7 +146,10 @@ export function build(locales) { layers.push(lyrBuilding.building); var bridgeLayers = [ - lyrRail.bridgeCasing, + lyrBridge.bridgeCasingBackground, + lyrBridge.bridge, + lyrBridge.bridgeOutline, + 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