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() })