diff --git a/api/features.mjs b/api/features.mjs index bc2343b8e..e2b42c5d5 100644 --- a/api/features.mjs +++ b/api/features.mjs @@ -1340,10 +1340,13 @@ const features = { operator: { name: 'Operator', }, + conversion: { + name: 'Conversion', + }, voltage: { name: 'Voltage', format: { - template: '%s V', + template: '%d V', }, }, frequency: { diff --git a/api/test/api.hurl b/api/test/api.hurl index 06763e7fc..05b398c94 100644 --- a/api/test/api.hurl +++ b/api/test/api.hurl @@ -490,6 +490,19 @@ jsonpath "$.position" count == 1 jsonpath "$.position[0]" == "11.6 (km)" jsonpath "$.structure" == "lattice" +# Substation +GET {{base_url}}/feature/openrailwaymap_electrification/electrification_substation/62032242 +HTTP 200 +Content-Type: application/json +[Asserts] +jsonpath "$.note" == null +jsonpath "$.description" == null +jsonpath "$.osm_id" == 62032242 +jsonpath "$.osm_type" == "W" +jsonpath "$.voltage" count == 2 +jsonpath "$.voltage[0]" == 30000 +jsonpath "$.voltage[1]" == 750 + # Substation GET {{base_url}}/feature/openrailwaymap_electrification/electrification_substation/320522867 HTTP 200 @@ -499,9 +512,7 @@ jsonpath "$.note" == null jsonpath "$.description" == null jsonpath "$.osm_id" == 320522867 jsonpath "$.osm_type" == "W" -jsonpath "$.voltage" count == 2 -jsonpath "$.voltage[0]" == "30000" -jsonpath "$.voltage[1]" == "750" +jsonpath "$.conversion" == "30kV 50 Hz ⇒ 750V =" # Railway crossing GET {{base_url}}/feature/openrailwaymap_standard/standard_railway_switch_ref/2101295628 diff --git a/import/openrailwaymap.lua b/import/openrailwaymap.lua index 7d564904a..d12563e5d 100644 --- a/import/openrailwaymap.lua +++ b/import/openrailwaymap.lua @@ -650,8 +650,9 @@ local substation = osm2pgsql.define_table({ { column = 'name', type = 'text' }, { column = 'location', type = 'text' }, { column = 'operator', type = 'text' }, - { column = 'voltage', sql_type = 'text[]' }, - { column = 'frequency', sql_type = 'text[]' }, + { column = 'voltage', sql_type = 'integer[]' }, + { column = 'frequency', sql_type = 'real[]' }, + { column = 'conversion', type = 'text' }, { column = 'wikidata', type = 'text' }, { column = 'wikimedia_commons', type = 'text' }, { column = 'wikimedia_commons_file', type = 'text' }, @@ -765,8 +766,31 @@ function electrification_state(tags) return nil, nil, nil, nil, nil end +-- Split a value and trim the parts +function split_semicolon(value) + if not value then + return nil + end + + local items = {} + local has_items = false + for part in string.gmatch(value, '[^;]+') do + local stripped_part = strip_prefix(part, ' ') + if stripped_part then + table.insert(items, stripped_part) + has_items = true + end + end + + if has_items then + return items + else + return nil + end +end + +-- Put the items in a table into a raw SQL array string (quoted and comma-delimited) function to_sql_array(items) - -- Put the items in a table into a raw SQL array string (quoted and comma-delimited) if not items then return nil end @@ -778,8 +802,12 @@ function to_sql_array(items) result = result .. ',' end - -- Raw SQL array syntax - result = result .. "\"" .. item:gsub("\\", "\\\\"):gsub("\"", "\\\"") .. "\"" + if type(item) == "number" then + result = result .. tostring(item) + else + -- Raw SQL array syntax + result = result .. "\"" .. item:gsub("\\", "\\\\"):gsub("\"", "\\\"") .. "\"" + end end return result .. '}' @@ -787,22 +815,7 @@ end -- Split a value and turn it into a raw SQL array (quoted and comma-delimited) function split_semicolon_to_sql_array(value) - if not value then - return nil - end - - local items = {} - - if value then - for part in string.gmatch(value, '[^;]+') do - local stripped_part = strip_prefix(part, ' ') - if stripped_part then - table.insert(items, stripped_part) - end - end - end - - return to_sql_array(items) + return to_sql_array(split_semicolon(value)) end local railway_state_tags = { @@ -1086,6 +1099,29 @@ function stop_position_type(tags) end end +function format_voltage_frequency(voltage, frequency) + local voltage_text = voltage < 1000.0 and string.format('%.0dV', voltage) or string.format('%.1dkV', voltage / 1000.0) + + if frequency == 0 then + return string.format("%s =", voltage_text) + else + return string.format("%s %.2d Hz", voltage_text, frequency) + end +end + +function substation_voltage_frequency(voltage, frequency) + local voltages = map(split_semicolon(voltage), function(it) return tonumber(it) end) + local frequencies = map(split_semicolon(frequency), function(it) return tonumber(it) end) + + if voltages and frequencies and #voltages == 2 and #frequencies == 2 then + -- conversion between source and destination + local conversion = string.format('%s ⇒ %s', format_voltage_frequency(voltages[1], frequencies[1]), format_voltage_frequency(voltages[2], frequencies[2])) + return nil, nil, conversion + else + return voltages, frequencies, nil + end +end + local railway_station_values = osm2pgsql.make_check_values_func({'station', 'halt', 'tram_stop', 'service_station', 'yard', 'junction', 'spur_junction', 'crossover', 'site'}) local railway_poi_values = osm2pgsql.make_check_values_func(tag_functions.poi_railway_values) local railway_signal_values = osm2pgsql.make_check_values_func({'signal', 'buffer_stop', 'derail', 'vacancy_detection'}) @@ -1582,6 +1618,8 @@ function osm2pgsql.process_way(object) end if tags.power == 'substation' and tags.substation == 'traction' then + local voltages, frequencies, conversion = substation_voltage_frequency(tags.voltage, tags.frequency) + substation:insert({ way = object:as_polygon(), feature = 'traction', @@ -1589,8 +1627,9 @@ function osm2pgsql.process_way(object) ref = tags.ref, location = tags.location, operator = tags.operator, - voltage = split_semicolon_to_sql_array(tags.voltage), - frequency = split_semicolon_to_sql_array(tags.frequency), + voltage = to_sql_array(voltages), + frequency = to_sql_array(frequencies), + conversion = conversion, wikidata = tags.wikidata, wikimedia_commons = wikimedia_commons, wikimedia_commons_file = wikimedia_commons_file, diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql index b19e87c95..69982bdee 100644 --- a/import/sql/tile_views.sql +++ b/import/sql/tile_views.sql @@ -1616,6 +1616,7 @@ CREATE OR REPLACE VIEW electrification_substation_view AS operator, voltage, frequency, + conversion, wikidata, wikimedia_commons, wikimedia_commons_file, diff --git a/import/test/test_import_substation.lua b/import/test/test_import_substation.lua index 18b4a440f..bfc1b3927 100644 --- a/import/test/test_import_substation.lua +++ b/import/test/test_import_substation.lua @@ -40,6 +40,23 @@ osm2pgsql.process_way({ }) assert.eq(osm2pgsql.get_and_clear_imported_data(), { substation = { - { feature = 'traction', location = 'indoor', voltage = '{"400000","225000","63000"}', frequency = '{"50","0"}', name = 'name', ref = 'ref', operator = 'operator', way = way }, + { feature = 'traction', location = 'indoor', voltage = '{400000,225000,63000}', frequency = '{50,0}', name = 'name', ref = 'ref', operator = 'operator', way = way }, + }, +}) + +osm2pgsql.process_way({ + tags = { + ['power'] = 'substation', + ['substation'] = 'traction', + ['voltage'] = '400000;750', + ['frequency'] = '50;0', + }, + as_polygon = function () + return way + end, +}) +assert.eq(osm2pgsql.get_and_clear_imported_data(), { + substation = { + { feature = 'traction', conversion = '400kV 50 Hz ⇒ 750V =', way = way }, }, }) diff --git a/proxy/js/ui.js b/proxy/js/ui.js index 02ae12e53..bc7779195 100644 --- a/proxy/js/ui.js +++ b/proxy/js/ui.js @@ -2398,7 +2398,7 @@ function popupContent(feature, abortController) { if (!format) { return stringValue; } else if (format.template) { - return format.template.replace('%s', () => stringValue).replace(/%(\.(\d+))?d/, (_1, _2, decimals) => Number(value).toFixed(Number(decimals))); + return format.template.replace('%s', () => stringValue).replace(/%(\.(\d+))?d/, (_1, _2, decimals) => Number(stringValue).toFixed(Number(decimals))); } else if (format.lookup) { const lookupCatalog = features && features[format.lookup]; if (!lookupCatalog) {