diff --git a/scripts/taginfo_template.json b/scripts/taginfo_template.json index 3674f9042..177abfd69 100644 --- a/scripts/taginfo_template.json +++ b/scripts/taginfo_template.json @@ -200,6 +200,83 @@ "object_types": ["way"], "description": "Major roads under construction have a dotted line pattern and a more prominent color.", "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "rail", + "object_types": ["way"], + "description": "Conventional railroads are represented by a thick light gray line with a sparse tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "narrow_gauge", + "object_types": ["way"], + "description": "Narrow gauge railroads are represented by a thick light gray line with a sparse doubled tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "subway", + "object_types": ["way"], + "description": "Subways are represented by a thick dark gray line with no tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "monorail", + "object_types": ["way"], + "description": "Monorails are represented by a thin dark gray line with no tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "light_rail", + "object_types": ["way"], + "description": "Light rail is represented by a thin dark gray line with a dense tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "tram", + "object_types": ["way"], + "description": "Trams are represented by a thin light gray line with a dense tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "railway", + "value": "funicular", + "object_types": ["way"], + "description": "Funiculars are represented by a thin dark gray line with a very dense tie pattern.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "service", + "value": "crossover", + "object_types": ["way"], + "description": "Displays railroads with normal line width but ties removed.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "service", + "value": "siding", + "object_types": ["way"], + "description": "Displays railroads with a thinner line width but normal tie spacing.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "service", + "value": "spur", + "object_types": ["way"], + "description": "Displays railroads with a thinner line width but normal tie spacing.", + "doc_url": "https://openmaptiles.org/schema/#transportation" + }, + { + "key": "service", + "value": "yard", + "object_types": ["way"], + "description": "Displays railroads with a thinner line width but normal tie spacing.", + "doc_url": "https://openmaptiles.org/schema/#transportation" } ] } diff --git a/src/americana.js b/src/americana.js index fdf378999..a74bc933d 100644 --- a/src/americana.js +++ b/src/americana.js @@ -14,6 +14,7 @@ import * as lyrHighwayShield from "./layer/highway_shield.js"; import * as lyrOneway from "./layer/oneway.js"; import * as lyrPark from "./layer/park.js"; import * as lyrPlace from "./layer/place.js"; +import * as lyrRail from "./layer/rail.js"; import * as lyrRoad from "./layer/road.js"; import * as lyrTransportationLabel from "./layer/transportation_label.js"; import * as lyrWater from "./layer/water.js"; @@ -59,6 +60,19 @@ americanaLayers.push( lyrBackground.pierArea, lyrBackground.pierLine, + lyrRail.railTunnel.dashes(), + lyrRail.railServiceTunnel.dashes(), + + lyrRail.narrowGaugeTunnel.dashes(), + lyrRail.narrowGaugeServiceTunnel.dashes(), + + lyrRail.lightRailTramTunnel.dashes(), + lyrRail.lightRailTramServiceTunnel.dashes(), + + lyrRail.funicularTunnel.dashes(), + + lyrRail.railwayTunnel.fill(), + lyrConstruction.road, lyrRoad.motorwayLinkTunnel.casing(), @@ -222,6 +236,19 @@ americanaLayers.push( lyrRoad.secondaryToll.surface(), lyrRoad.primaryToll.surface(), + lyrRail.rail.dashes(), + lyrRail.railService.dashes(), + + lyrRail.narrowGauge.dashes(), + lyrRail.narrowGaugeService.dashes(), + + lyrRail.lightRailTram.dashes(), + lyrRail.lightRailTramService.dashes(), + + lyrRail.funicular.dashes(), + + lyrRail.railway.fill(), + lyrOneway.road, lyrOneway.link ); @@ -229,6 +256,8 @@ americanaLayers.push( americanaLayers.push(lyrBuilding.building); var bridgeLayers = [ + lyrRail.bridgeCasing, + lyrRoad.tertiaryLinkBridge.casing(), lyrRoad.secondaryLinkBridge.casing(), lyrRoad.primaryLinkBridge.casing(), @@ -310,6 +339,19 @@ var bridgeLayers = [ lyrRoad.secondaryTollBridge.surface(), lyrRoad.primaryTollBridge.surface(), + lyrRail.railBridge.dashes(), + lyrRail.railServiceBridge.dashes(), + + lyrRail.narrowGaugeBridge.dashes(), + lyrRail.narrowGaugeServiceBridge.dashes(), + + lyrRail.lightRailTramBridge.dashes(), + lyrRail.lightRailTramServiceBridge.dashes(), + + lyrRail.funicularBridge.dashes(), + + lyrRail.railwayBridge.fill(), + lyrOneway.bridge, lyrOneway.bridgeLink, ]; diff --git a/src/constants/color.js b/src/constants/color.js index c2bdcb7b9..88009eeb2 100644 --- a/src/constants/color.js +++ b/src/constants/color.js @@ -33,3 +33,13 @@ export const shields = { yellow: "#ffcd00", // Pantone 116 yellow_green: "#c4d600", // Pantone 382 }; + +export const railwayTunnelFill = "hsl(0, 0%, 90%)"; + +export const railFill = "hsl(0, 0%, 60%)"; +export const narrowGaugeFill = "hsl(0, 0%, 60%)"; +export const subwayFill = "hsl(0, 0%, 50%)"; +export const lightRailFill = "hsl(0, 0%, 50%)"; +export const tramFill = "hsl(0, 0%, 60%)"; +export const monorailFill = "hsl(0, 0%, 50%)"; +export const funicularFill = "hsl(0, 0%, 50%)"; diff --git a/src/layer/rail.js b/src/layer/rail.js new file mode 100644 index 000000000..0e64199eb --- /dev/null +++ b/src/layer/rail.js @@ -0,0 +1,453 @@ +"use strict"; + +import * as Color from "../constants/color.js"; +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 [ + "all", + brunnel === "surface" + ? ["!in", "brunnel", "bridge", "tunnel"] + : ["==", "brunnel", brunnel], + ["in", "class", "rail", "transit"], + ]; +} + +// Base definition that applies to all railways +var defRail = { + type: "line", + source: "openmaptiles", + "source-layer": "transportation", +}; + +var serviceSelector = ["match", ["get", "service"], ["siding", "spur", "yard"]]; +var isService = ["in", "service", "siding", "spur", "yard"]; +var isNotService = ["!in", "service", "siding", "spur", "yard"]; + +var lineColor = [ + "match", + ["get", "brunnel"], + "tunnel", + Color.railwayTunnelFill, + [ + "match", + ["get", "subclass"], + "subway", + Color.subwayFill, + "light_rail", + Color.lightRailFill, + "tram", + Color.tramFill, + "monorail", + Color.monorailFill, + "funicular", + Color.funicularFill, + "narrow_gauge", + Color.narrowGaugeFill, + Color.railFill, + ], +]; + +var lineWidth = [ + "match", + ["get", "subclass"], + ["rail", "preserved", "narrow_gauge", "subway"], + [...serviceSelector, 2, 4], + "monorail", + [...serviceSelector, 1.6, 3.2], + [...serviceSelector, 1.25, 2.5], +]; + +// Bridge casing layers +export const bridgeCasing = { + ...defRail, + id: "railway-bridge-casing", + filter: [ + "all", + ["==", "brunnel", "bridge"], + ["in", "class", "rail", "transit"], + ], + minzoom: 13, + layout: { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }, + paint: { + "line-color": Color.backgroundFill, + "line-opacity": [ + "step", + ["zoom"], + [ + "match", + ["get", "subclass"], + ["rail", "preserved", "narrow_gauge"], + 1, + 0, + ], + 14, + 1, + ], + "line-width": zoomInterpolate([...serviceSelector, 4, 6]), + }, +}; + +// Generate a unique layer ID +function uniqueLayerID(part, brunnel, constraints) { + var layerID = ["rail", part, brunnel].join("_"); + if (constraints != null) { + layerID += + "_" + constraints.join("_").replaceAll("=", "").replaceAll("-", "_"); + } + return layerID; +} + +function baseRailLayer(id, brunnel, minzoom, maxzoom, constraints) { + var layer = Util.layerClone(defRail, uniqueLayerID(id, brunnel, constraints)); + layer.filter = filterRail(brunnel); + layer.minzoom = minzoom; + if (maxzoom) { + layer.maxzoom = maxzoom; + } + return layer; +} + +// Base railway class + +class Railway { + constructor() { + this.brunnel = "surface"; + this.minZoom = 10; + } + fill = function () { + var layer = baseRailLayer( + "fill", + this.brunnel, + this.minZoom, + null, + this.constraints + ); + layer.layout = { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }; + layer.paint = { + "line-color": lineColor, + "line-width": zoomInterpolate(lineWidth), + }; + if (this.constraints != null) { + layer.filter.push(this.constraints); + } + return layer; + }; + dashes = function () { + var layer = baseRailLayer( + "dashes", + this.brunnel, + this.minZoom, + null, + this.constraints + ); + layer.layout = { + "line-cap": "butt", + "line-join": "bevel", + visibility: "visible", + }; + layer.paint = { + "line-color": lineColor, + "line-width": zoomInterpolate( + multiplyMatchExpression(lineWidth, this.dashWidthFactor) + ), + "line-dasharray": this.dashArray.map( + (stop) => stop / 2 / this.dashWidthFactor + ), + }; + if (this.constraints != null) { + layer.filter.push(this.constraints); + } + layer.filter.push(["!=", "service", "crossover"]); + return layer; + }; +} + +class RailwayBridge extends Railway { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class RailwayTunnel extends Railway { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +// Railway class styles + +class Rail extends Railway { + constructor() { + super(); + this.constraints = [ + "all", + ["in", "subclass", "rail", "preserved"], + isNotService, + ]; + this.brunnel = "surface"; + + this.dashWidthFactor = 3; + this.dashArray = [1, 25]; + } +} + +class RailService extends Rail { + constructor() { + super(); + this.constraints = [ + "all", + ["in", "subclass", "rail", "preserved"], + isService, + ]; + + this.dashWidthFactor = 4; + this.dashArray = [1, 50]; + } +} + +class NarrowGauge extends Rail { + constructor() { + super(); + this.constraints = [ + "all", + ["==", "subclass", "narrow_gauge"], + isNotService, + ]; + + this.dashWidthFactor = 2; + this.dashArray = [1, 1, 1, 15]; + } +} + +class NarrowGaugeService extends NarrowGauge { + constructor() { + super(); + this.constraints = ["all", ["==", "subclass", "narrow_gauge"], isService]; + + this.dashWidthFactor = 3; + this.dashArray = [1, 2, 1, 30]; + } +} + +class LightRailTram extends Railway { + constructor() { + super(); + this.constraints = [ + "all", + ["in", "subclass", "light_rail", "tram"], + isNotService, + ]; + this.brunnel = "surface"; + + this.minZoom = 14; + this.dashWidthFactor = 2; + this.dashArray = [1, 6]; + } +} + +class LightRailTramService extends LightRailTram { + constructor() { + super(); + this.constraints = [ + "all", + ["in", "subclass", "light_rail", "tram"], + isService, + ]; + + this.dashWidthFactor = 3; + this.dashArray = [1, 12]; + } +} + +class Funicular extends Railway { + constructor() { + super(); + this.constraints = ["==", "subclass", "funicular"]; + this.brunnel = "surface"; + + this.minZoom = 14; + this.dashWidthFactor = 2.3; + this.dashArray = [1, 2]; + } +} + +// Bridges + +class RailBridge extends Rail { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class RailServiceBridge extends RailService { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class NarrowGaugeBridge extends NarrowGauge { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class NarrowGaugeServiceBridge extends NarrowGaugeService { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class FunicularBridge extends Funicular { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class LightRailTramBridge extends LightRailTram { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +class LightRailTramServiceBridge extends LightRailTramService { + constructor() { + super(); + this.brunnel = "bridge"; + } +} + +// Tunnels + +class RailTunnel extends Rail { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class RailServiceTunnel extends RailService { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class NarrowGaugeTunnel extends NarrowGauge { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class NarrowGaugeServiceTunnel extends NarrowGaugeService { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class FunicularTunnel extends Funicular { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class LightRailTramTunnel extends LightRailTram { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +class LightRailTramServiceTunnel extends LightRailTramService { + constructor() { + super(); + this.brunnel = "tunnel"; + } +} + +export const railway = new Railway(); +export const railwayBridge = new RailwayBridge(); +export const railwayTunnel = new RailwayTunnel(); + +export const rail = new Rail(); +export const railBridge = new RailBridge(); +export const railTunnel = new RailTunnel(); + +export const railService = new RailService(); +export const railServiceBridge = new RailServiceBridge(); +export const railServiceTunnel = new RailServiceTunnel(); + +export const narrowGauge = new NarrowGauge(); +export const narrowGaugeBridge = new NarrowGaugeBridge(); +export const narrowGaugeTunnel = new NarrowGaugeTunnel(); + +export const narrowGaugeService = new NarrowGaugeService(); +export const narrowGaugeServiceBridge = new NarrowGaugeServiceBridge(); +export const narrowGaugeServiceTunnel = new NarrowGaugeServiceTunnel(); + +export const lightRailTram = new LightRailTram(); +export const lightRailTramBridge = new LightRailTramBridge(); +export const lightRailTramTunnel = new LightRailTramTunnel(); + +export const lightRailTramService = new LightRailTramService(); +export const lightRailTramServiceBridge = new LightRailTramServiceBridge(); +export const lightRailTramServiceTunnel = new LightRailTramServiceTunnel(); + +export const funicular = new Funicular(); +export const funicularBridge = new FunicularBridge(); +export const funicularTunnel = new FunicularTunnel();