diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fc134605..30e12cbc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -143,6 +143,21 @@ loading_gauges:
Open a pull request where you provide details about the new loading gauge. Ensure the pull request contains references to documentation and places on the map where the loading gauge exists.
+## I want to visualize a new track class on the map
+
+Edit the file [`features/track_class.yaml`](https://github.com/hiddewie/OpenRailwayMap-vector/edit/master/features/track_class.yaml).
+
+The file contains a list of track classes, each with a value (the value of the `railway:track_class` tag) and a color.
+
+Add a new entry at a certain place in the list. The value is the `railway:track_class` tag value. For example:
+```yaml
+track_classes:
+ - { value: 'Z', color: 'blue' }
+ # ...
+```
+
+Open a pull request where you provide details about the new track class. Ensure the pull request contains references to documentation and places on the map where the track class exists.
+
## I want to improve the user interface
The HTML, Javascript and CSS of the user interface are located in the [proxy](https://github.com/hiddewie/OpenRailwayMap-vector/tree/master/proxy) directory.
diff --git a/features/track_class.yaml b/features/track_class.yaml
new file mode 100644
index 00000000..36eaffdc
--- /dev/null
+++ b/features/track_class.yaml
@@ -0,0 +1,32 @@
+track_classes:
+ - { value: 'A', color: 'hsl(248, 100%, 40%)' }
+ - { value: 'B1', color: 'hsl(220, 100%, 40%)' }
+ - { value: 'B2', color: 'hsl(200, 100%, 40%)' }
+ - { value: 'C2', color: 'hsl(180, 100%, 40%)' }
+ - { value: 'C3', color: 'hsl(160, 100%, 40%)' }
+ - { value: 'C3L', color: 'hsl(140, 100%, 40%)' }
+ - { value: 'C4', color: 'hsl(120, 100%, 40%)' }
+ - { value: 'CE', color: 'hsl(100, 100%, 40%)' }
+ - { value: 'CM2', color: 'hsl(80, 100%, 40%)' }
+ - { value: 'CM3', color: 'hsl(60, 100%, 40%)' }
+ - { value: 'CM', color: 'hsl(40, 100%, 40%)' }
+ - { value: 'D2', color: 'hsl(30, 100%, 40%)' }
+ - { value: 'D3', color: 'hsl(20, 100%, 40%)' }
+ - { value: 'D4', color: 'hsl(10, 100%, 40%)' }
+ - { value: 'DL', color: 'hsl(0, 100%, 40%)' }
+ - { value: 'E4', color: 'hsl(340, 100%, 40%)' }
+ - { value: 'E5', color: 'hsl(320, 100%, 40%)' }
+ - { value: 'F', color: 'hsl(300, 100%, 40%)' }
+ - { value: 'G', color: 'hsl(280, 100%, 40%)' }
+ - { value: '1', color: 'hsl(240, 100%, 40%)' }
+ - { value: '2', color: 'hsl(210, 100%, 40%)' }
+ - { value: '3', color: 'hsl(180, 100%, 40%)' }
+ - { value: '4', color: 'hsl(150, 100%, 40%)' }
+ - { value: '5', color: 'hsl(120, 100%, 40%)' }
+ - { value: '6', color: 'hsl(90, 100%, 40%)' }
+ - { value: '7', color: 'hsl(60, 100%, 40%)' }
+ - { value: '8', color: 'hsl(30, 100%, 40%)' }
+ - { value: '9', color: 'hsl(0, 100%, 40%)' }
+ - { value: '10', color: 'hsl(330, 100%, 40%)' }
+ - { value: '11', color: 'hsl(300, 100%, 40%)' }
+ - { value: 'excepted', color: 'black' }
diff --git a/import/openrailwaymap.lua b/import/openrailwaymap.lua
index e94aef0a..6043b48e 100644
--- a/import/openrailwaymap.lua
+++ b/import/openrailwaymap.lua
@@ -139,6 +139,7 @@ local railway_line = osm2pgsql.define_table({
{ column = 'future_voltage', type = 'integer' },
{ column = 'gauges', sql_type = 'text[]' },
{ column = 'loading_gauge', sql_type = 'text' },
+ { column = 'track_class', sql_type = 'text' },
{ column = 'reporting_marks', sql_type = 'text[]' },
{ column = 'construction_railway', type = 'text' },
{ column = 'proposed_railway', type = 'text' },
@@ -591,6 +592,7 @@ function osm2pgsql.process_way(object)
future_voltage = future_voltage,
gauges = '{' .. table.concat(gauges, ',') .. '}',
loading_gauge = tags['loading_gauge'],
+ track_class = tags['railway:track_class'],
reporting_marks = '{' .. table.concat(reporting_marks, ',') .. '}',
construction_railway = tags['construction:railway'],
proposed_railway = tags['proposed:railway'],
diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql
index 03acf1ba..870a6d2c 100644
--- a/import/sql/tile_views.sql
+++ b/import/sql/tile_views.sql
@@ -26,6 +26,7 @@ CREATE OR REPLACE VIEW railway_line_high AS
END AS standard_label,
ref,
track_ref,
+ track_class,
array_to_string(reporting_marks, ', ') as reporting_marks,
preferred_direction,
CASE
@@ -85,6 +86,7 @@ CREATE OR REPLACE VIEW railway_line_high AS
bridge,
tunnel,
track_ref,
+ track_class,
ref,
CASE
WHEN railway = 'abandoned' THEN railway_label_name(COALESCE(abandoned_name, name), tunnel, tunnel_name, bridge, bridge_name)
diff --git a/martin/configuration.yml b/martin/configuration.yml
index 036d2ebb..ce7717a5 100644
--- a/martin/configuration.yml
+++ b/martin/configuration.yml
@@ -64,6 +64,7 @@ postgres:
gaugeint2: number
gauge_label: string
loading_gauge: string
+ track_class: string
reporting_marks: string
railway_line_med:
@@ -104,6 +105,7 @@ postgres:
gaugeint2: number
gauge_label: string
loading_gauge: string
+ track_class: string
reporting_marks: string
railway_line_high:
@@ -145,6 +147,7 @@ postgres:
gaugeint2: number
gauge_label: string
loading_gauge: string
+ track_class: string
reporting_marks: string
# --- Standard --- #
diff --git a/proxy.Dockerfile b/proxy.Dockerfile
index e392d8bd..1561ff57 100644
--- a/proxy.Dockerfile
+++ b/proxy.Dockerfile
@@ -13,6 +13,7 @@ RUN --mount=type=bind,source=proxy/js/styles.mjs,target=styles.mjs \
--mount=type=bind,source=features/electrification_signals.yaml,target=electrification_signals.yaml \
--mount=type=bind,source=features/signals_railway_signals.yaml,target=signals_railway_signals.yaml \
--mount=type=bind,source=features/loading_gauge.yaml,target=loading_gauge.yaml \
+ --mount=type=bind,source=features/track_class.yaml,target=track_class.yaml \
node /build/styles.mjs
FROM nginx:1-alpine
diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs
index 6f69df5d..fb810997 100644
--- a/proxy/js/styles.mjs
+++ b/proxy/js/styles.mjs
@@ -6,6 +6,7 @@ const speed_railway_signals = yaml.parse(fs.readFileSync('speed_railway_signals.
const signals_railway_signals = yaml.parse(fs.readFileSync('signals_railway_signals.yaml', 'utf8')).signals_railway_signals
const electrification_signals = yaml.parse(fs.readFileSync('electrification_signals.yaml', 'utf8')).electrification_signals
const loading_gauges = yaml.parse(fs.readFileSync('loading_gauge.yaml', 'utf8')).loading_gauges
+const track_classes = yaml.parse(fs.readFileSync('track_class.yaml', 'utf8')).track_classes
const origin = `${process.env.PUBLIC_PROTOCOL}://${process.env.PUBLIC_HOST}`
@@ -16,6 +17,7 @@ const knownStyles = [
'electrification',
'gauge',
'loading_gauge',
+ 'track_class',
];
const globalMinZoom = 1;
@@ -988,6 +990,26 @@ const loadingGaugeLayout = {
'line-cap': 'round',
};
+const trackClassCasingPaint = {
+ 'line-color': 'white',
+ 'line-width': railwayLineWidth,
+ 'line-gap-width': 0.75,
+};
+const trackClassFillPaint = (dashArray) => ({
+ 'line-color': ['match', ['get', 'track_class'],
+ ...track_classes.flatMap(track_class =>
+ [track_class.value, track_class.color]
+ ),
+ 'gray',
+ ],
+ 'line-width': railwayLineWidth,
+ 'line-dasharray': dashArray,
+});
+const trackClassLayout = {
+ 'line-join': 'round',
+ 'line-cap': 'round',
+};
+
const attribution = '© OpenRailwayMap contributors | © OpenStreetMap contributors';
const sources = {
@@ -3409,6 +3431,239 @@ const layers = {
},
searchResults,
],
+
+ track_class: [
+ {
+ id: 'track_class_railway_line_low_casing',
+ type: 'line',
+ maxzoom: 7,
+ source: 'openrailwaymap_low',
+ 'source-layer': 'railway_line_low',
+ paint: trackClassCasingPaint,
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_low_fill',
+ type: 'line',
+ maxzoom: 7,
+ source: 'openrailwaymap_low',
+ 'source-layer': 'railway_line_low',
+ paint: trackClassFillPaint([1]),
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_med_casing',
+ type: 'line',
+ minzoom: 7,
+ maxzoom: 8,
+ source: 'openrailwaymap_med',
+ 'source-layer': 'railway_line_med',
+ paint: trackClassCasingPaint,
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_med_fill',
+ type: 'line',
+ minzoom: 7,
+ maxzoom: 8,
+ source: 'openrailwaymap_med',
+ 'source-layer': 'railway_line_med',
+ paint: trackClassFillPaint([1]),
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_casing',
+ type: 'line',
+ minzoom: 8,
+ source: 'high',
+ 'source-layer': 'railway_line_high',
+ filter: ['!=', ['get', 'railway'], 'construction'],
+ paint: trackClassCasingPaint,
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_casing_construction',
+ type: 'line',
+ minzoom: 8,
+ source: 'high',
+ 'source-layer': 'railway_line_high',
+ filter: ['==', ['get', 'railway'], 'construction'],
+ paint: {
+ 'line-color': 'white',
+ 'line-width': railwayLineWidth,
+ 'line-gap-width': 0.75,
+ 'line-dasharray': gauge_construction_dashes,
+ },
+ },
+ {
+ id: 'track_class_railway_line_fill',
+ type: 'line',
+ minzoom: 8,
+ source: 'high',
+ 'source-layer': 'railway_line_high',
+ filter: ['!=', ['get', 'railway'], 'construction'],
+ paint: trackClassFillPaint([1]),
+ layout: trackClassLayout,
+ },
+ {
+ id: 'track_class_railway_line_fill_construction',
+ type: 'line',
+ minzoom: 8,
+ source: 'high',
+ 'source-layer': 'railway_line_high',
+ filter: ['==', ['get', 'railway'], 'construction'],
+ paint: trackClassFillPaint(gauge_construction_dashes),
+ layout: loadingGaugeLayout,
+ },
+ preferredDirectionLayer('railway_preferred_direction', ['any',
+ ['==', ['get', 'preferred_direction'], 'forward'],
+ ['==', ['get', 'preferred_direction'], 'backward'],
+ ['==', ['get', 'preferred_direction'], 'both'],
+ ]),
+ railwayKmText,
+ {
+ id: 'track_class_railway_text_high',
+ type: 'symbol',
+ minzoom: 8,
+ source: 'high',
+ 'source-layer': 'railway_line_high',
+ filter: ['step', ['zoom'],
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ 9,
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ['==', ['get', 'usage'], 'industrial'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ 10,
+ ['any',
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['==', ['get', 'usage'], 'industrial'],
+ ],
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['==', ['get', 'usage'], null],
+ ['any',
+ ['==', ['get', 'service'], 'siding'],
+ ['==', ['get', 'service'], 'crossover'],
+ ['==', ['get', 'service'], 'spur'],
+ ],
+ ],
+ ['all',
+ ['==', ['get', 'feature'], 'narrow_gauge'],
+ ['any',
+ ['==', ['get', 'service'], null],
+ ['==', ['get', 'service'], 'siding'],
+ ['==', ['get', 'service'], 'crossover'],
+ ['==', ['get', 'service'], 'spur'],
+ ],
+ ],
+ ['all',
+ ['==', ['get', 'railway'], 'construction'],
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ ],
+ 11,
+ ['any',
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ ['all',
+ ['==', ['get', 'railway'], 'rail'],
+ ['==', ['get', 'usage'], 'industrial'],
+ ],
+ ['all',
+ ['==', ['get', 'feature'], 'rail'],
+ ['==', ['get', 'usage'], null],
+ ['any',
+ ['==', ['get', 'service'], 'siding'],
+ ['==', ['get', 'service'], 'crossover'],
+ ['==', ['get', 'service'], 'spur'],
+ ['==', ['get', 'service'], 'yard'],
+ ],
+ ],
+ ['all',
+ ['==', ['get', 'feature'], 'narrow_gauge'],
+ ['any',
+ ['==', ['get', 'service'], null],
+ ['==', ['get', 'service'], 'siding'],
+ ['==', ['get', 'service'], 'crossover'],
+ ['==', ['get', 'service'], 'spur'],
+ ['==', ['get', 'service'], 'yard'],
+ ],
+ ],
+ ['all',
+ ['==', ['get', 'railway'], 'construction'],
+ ['==', ['get', 'feature'], 'rail'],
+ ['any',
+ ['==', ['get', 'usage'], 'main'],
+ ['==', ['get', 'usage'], 'branch'],
+ ['==', ['get', 'usage'], 'subway'],
+ ['==', ['get', 'usage'], 'light_rail'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ ['all',
+ ['any',
+ ['==', ['get', 'feature'], 'subway'],
+ ['==', ['get', 'feature'], 'light_rail'],
+ ],
+ ['==', ['get', 'service'], null],
+ ],
+ ],
+ 12,
+ true,
+ ],
+ paint: {
+ 'text-halo-color': ['case',
+ ['boolean', ['feature-state', 'hover'], false], colors.hover.textHalo,
+ 'white'
+ ],
+ 'text-halo-width': 1.5,
+ },
+ layout: {
+ 'symbol-z-order': 'source',
+ 'symbol-placement': 'line',
+ 'text-field': '{loading_gauge}',
+ // TODO not present: oblique font
+ 'text-font': ['Noto Sans Bold'],
+ 'text-size': 11,
+ 'text-padding': 30,
+ 'symbol-spacing': 250,
+ },
+ },
+ searchResults,
+ ],
};
const makeStyle = selectedStyle => ({
@@ -4933,6 +5188,91 @@ const legendData = {
},
],
},
+ track_class: {
+ 'openrailwaymap_low-railway_line_low': [
+ ...track_classes.map(track_class => ({
+ legend: track_class.value,
+ type: 'line',
+ properties: {
+ track_class: track_class.value,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ })),
+ {
+ legend: '(unknown)',
+ type: 'line',
+ properties: {
+ track_class: null,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ },
+ ],
+ 'openrailwaymap_med-railway_line_med': [
+ ...track_classes.map(track_class => ({
+ legend: track_class.value,
+ type: 'line',
+ properties: {
+ track_class: track_class.value,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ })),
+ {
+ legend: '(unknown)',
+ type: 'line',
+ properties: {
+ track_class: null,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ },
+ ],
+ 'high-railway_line_high': [
+ ...track_classes.map(track_class => ({
+ legend: track_class.value,
+ type: 'line',
+ properties: {
+ track_class: track_class.value,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ })),
+ {
+ legend: '(unknown)',
+ type: 'line',
+ properties: {
+ track_class: null,
+ railway: 'rail',
+ feature: 'rail',
+ usage: 'main',
+ service: null,
+ },
+ },
+ ],
+ "high-railway_text_km": [
+ {
+ legend: 'Milestone',
+ type: 'point',
+ properties: {
+ zero: true,
+ pos_int: '47',
+ pos: '47.0',
+ },
+ },
+ ],
+ },
}
const coordinateFactor = legendZoom => Math.pow(2, 5 - legendZoom);
diff --git a/proxy/js/ui.js b/proxy/js/ui.js
index ed67d643..0ef0bc76 100644
--- a/proxy/js/ui.js
+++ b/proxy/js/ui.js
@@ -271,6 +271,7 @@ const knownStyles = {
electrification: 'Electrification',
gauge: 'Gauge',
loading_gauge: 'Loading gauge',
+ track_class: 'Track class',
};
function hashToObject(hash) {
@@ -668,6 +669,7 @@ function popupContent(properties) {
${properties.bridge === true ? `bridge` : ''}
${properties.railway_local_operated === true ? `operated locally` : ''}
${properties.maxspeed ? `maximum speed: ${properties.maxspeed} km/h` : ''}
+ ${properties.track_class ? `track class: ${properties.track_class}` : ''}
${properties.direction_both ? `both directions` : ''}
${properties.train_protection ? `train protection: ${properties.train_protection}` : ''}
${properties.deactivated === true ? `deactivated` : ''}
diff --git a/proxy/test/ui/cypress/e2e/home.cy.js b/proxy/test/ui/cypress/e2e/home.cy.js
index e3a31a54..3c8c286c 100644
--- a/proxy/test/ui/cypress/e2e/home.cy.js
+++ b/proxy/test/ui/cypress/e2e/home.cy.js
@@ -39,6 +39,9 @@ describe('home page', () => {
cy.contains('Loading gauge').click()
cy.url().should('include', 'style=loading_gauge')
+ cy.contains('Track class').click()
+ cy.url().should('include', 'style=track_class')
+
cy.wait(3000)
cy.screenshot()
})