diff --git a/INSTALL.md b/INSTALL.md index 347a3112c8..ac68053021 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,11 +26,13 @@ osm2pgsql -G --hstore --style openstreetmap-carto.style --tag-transform-script o You can find a more detailed guide to setting up a database and loading data with osm2pgsql at [switch2osm.org](https://switch2osm.org/manually-building-a-tile-server-16-04-2-lts/). -### Custom indexes +### Custom indexes and functions Custom indexes are required for rendering performance and are essential on full planet databases. +`functions.sql` contains SQL functions used by the project. ``` psql -d gis -f indexes.sql +psql -d gis -f functions.sql ``` ## Scripted download diff --git a/functions.sql b/functions.sql new file mode 100644 index 0000000000..39ebb4ef08 --- /dev/null +++ b/functions.sql @@ -0,0 +1,13 @@ +-- This SQL file contains custom PostgreSQL functions used by Carto. + +-- Compute the scale factor to use when converting from metres to pixels. +-- This works by computing what the distance of a lateral translation of '1' +-- amounts to in WGS 84 coordinates for the specified object. +create or replace function scale_factor (geometry) + returns numeric + language sql + immutable + returns null on null input +as $func$ +select ST_DistanceSphere(ST_Transform(ST_Translate(geom, 0, 1), 4326), ST_Transform(geom, 4326))::numeric from (select ST_Centroid($1) as geom) as p +$func$; diff --git a/openstreetmap-carto.lua b/openstreetmap-carto.lua index 8dbca62a64..67ab0b1c1b 100644 --- a/openstreetmap-carto.lua +++ b/openstreetmap-carto.lua @@ -12,6 +12,7 @@ local polygon_keys = { 'allotments', 'amenity', 'area:highway', + 'area:aeroway', 'craft', 'building', 'building:part', diff --git a/project.mml b/project.mml index 22869c1979..2487444da4 100644 --- a/project.mml +++ b/project.mml @@ -780,6 +780,32 @@ Layer: properties: cache-features: true minzoom: 10 + - id: aeroway-area-fill + <<: *extents + Datasource: + <<: *osm2pgsql + table: |- + (SELECT + way, + way_area/NULLIF(POW(!scale_denominator!*0.001*0.28,2),0) AS way_pixels, + CASE + WHEN surface IN ('paved', 'asphalt', 'concrete', 'concrete:lanes', 'concrete:plates', 'metal') THEN 'paved' + WHEN surface IN ('grass') THEN 'grass' + ELSE 'unpaved' + END AS int_surface, + COALESCE( + (('aeroway_' || CASE + WHEN aeroway IN ('helipad', 'apron') THEN aeroway + WHEN tags->'area:aeroway' IN ('runway', 'taxiway', 'stopway') THEN tags->'area:aeroway' + END)) + ) AS feature + FROM planet_osm_polygon + WHERE aeroway IN ('helipad', 'apron') + OR tags->'area:aeroway' IN ('runway', 'taxiway', 'stopway') + ORDER BY COALESCE(layer,0), way_area desc + ) AS highway_area_fill + properties: + minzoom: 14 - id: highway-area-fill # FIXME: No geometry? <<: *extents @@ -795,8 +821,7 @@ Layer: AND (tags->'location' NOT IN ('underground') OR (tags->'location') IS NULL) AND (tunnel NOT IN ('yes', 'building_passage') OR tunnel IS NULL) AND (covered NOT IN ('yes') OR covered IS NULL)) - THEN railway END)), - (('aeroway_' || CASE WHEN aeroway IN ('runway', 'taxiway', 'helipad') THEN aeroway END)) + THEN railway END)) ) AS feature FROM planet_osm_polygon WHERE highway IN ('pedestrian', 'footway', 'service', 'living_street', 'platform', 'services') @@ -804,7 +829,6 @@ Layer: AND (tags->'location' NOT IN ('underground') OR (tags->'location') IS NULL) AND (tunnel NOT IN ('yes', 'building_passage') OR tunnel IS NULL) AND (covered NOT IN ('yes') OR covered IS NULL)) - OR aeroway IN ('runway', 'taxiway', 'helipad') ORDER BY COALESCE(layer,0), way_area desc ) AS highway_area_fill properties: @@ -1035,11 +1059,55 @@ Layer: (SELECT way, aeroway, - bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct') AS bridge - FROM planet_osm_line - WHERE aeroway IN ('runway', 'taxiway') - ORDER BY bridge NULLS FIRST, - CASE WHEN aeroway = 'runway' THEN 1 ELSE 0 END + runway, + bridge, + int_surface, + width, + GREATEST(width::real/sf, 2) AS area_width, + 1/sf AS line_width + FROM + (SELECT + way, + aeroway, + tags->'runway' AS runway, + bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct') AS bridge, + CASE + WHEN surface IN ('paved', 'asphalt', 'concrete', 'concrete:lanes', 'concrete:plates', 'metal') THEN 'paved' + WHEN surface IN ('grass') THEN 'grass' + ELSE 'unpaved' + END AS int_surface, + -- Convert valid length units into metres. Fall back to a sensible default if no valid value was set. + GREATEST( + CASE + -- Metric: + WHEN tags->'width' ~ '^[0-9]*[.]?[0-9]+ m$' THEN LEFT(tags->'width', -2)::real + WHEN tags->'width' ~ '^[0-9]*[.]?[0-9]+$' THEN (tags->'width')::real + -- Feet: + WHEN tags->'width' ~ '^[0-9]+''$' THEN LEFT(tags->'width', -1)::real * 12 * 0.0254 + -- The case-branches below work, but we assume that runway widths will always be + -- specified in metres or (rarely) feet, so for performance reasons these are left out: + -- Kilometres: + -- WHEN tags->'width' ~ '^[0-9]*[.]?[0-9]+ km$' THEN LEFT(tags->'width',-3)::real * 1000 + -- Miles and nautical miles: + -- WHEN tags->'width' ~ '^[0-9]*[.]?[0-9]+ mi$' THEN LEFT(tags->'width', -3)::real * 1609.344 + -- WHEN tags->'width' ~ '^[0-9]*[.]?[0-9]+ nmi$' THEN LEFT(tags->'width', -4)::real * 1852 + -- Remaining feet and inches: + -- WHEN tags->'width' ~ '^[0-9]+''[0-9]+"$' THEN (SPLIT_PART(tags->'width', '''', 1)::real * 12 + LEFT(SPLIT_PART(tags->'width', '''', 2), -1)::real) * 0.0254 + -- WHEN tags->'width' ~ '^[0-9]+"$' THEN LEFT(tags->'width', -1)::real * 0.0254 + ELSE NULL + END, + -- Default to a small standard width for runways and a matching taxiway width. + CASE + WHEN aeroway IN ('runway') THEN 18 + ELSE 7.5 + END + ) as width, + NULLIF(scale_factor(way)*!scale_denominator!*0.001*0.28, 0) AS sf + FROM planet_osm_line + WHERE aeroway IN ('runway', 'taxiway', 'taxilane', 'stopway', 'parking_position') + ORDER BY bridge NULLS FIRST, + CASE WHEN aeroway = 'runway' THEN 1 ELSE 0 END + ) _ ) AS aeroways properties: cache-features: true @@ -1772,13 +1840,20 @@ Layer: SELECT osm_id, way, - COALESCE( - CASE WHEN highway IN ('motorway', 'trunk', 'primary', 'secondary', 'tertiary') THEN highway END, - CASE WHEN aeroway IN ('runway', 'taxiway') THEN aeroway END - ) AS highway, + highway, + string_to_array(ref, ';') AS refs + FROM planet_osm_line + WHERE highway IN ('motorway', 'trunk', 'primary', 'secondary', 'tertiary') + AND ref IS NOT NULL + UNION ALL + SELECT + osm_id, + way, + aeroway AS highway, string_to_array(ref, ';') AS refs FROM planet_osm_line - WHERE (highway IN ('motorway', 'trunk', 'primary', 'secondary', 'tertiary') OR aeroway IN ('runway', 'taxiway')) + WHERE aeroway IN ('runway', 'taxiway', 'taxilane', 'parking_position') + AND NOT tags @> '"runway"=>"displaced_threshold"' AND ref IS NOT NULL ) AS p) AS q WHERE height <= 4 AND width <= 11 @@ -1791,6 +1866,8 @@ Layer: WHEN highway = 'tertiary' THEN 34 WHEN highway = 'runway' THEN 6 WHEN highway = 'taxiway' THEN 5 + WHEN highway = 'parking_position' THEN 4 + WHEN highway = 'taxilane' THEN 3 END DESC NULLS LAST, height DESC, width DESC, diff --git a/scripts/docker-startup.sh b/scripts/docker-startup.sh index c111b34145..c9aa470221 100644 --- a/scripts/docker-startup.sh +++ b/scripts/docker-startup.sh @@ -21,6 +21,7 @@ import) psql -c "SELECT 1 FROM pg_database WHERE datname = 'gis';" | grep -q 1 || createdb gis && \ psql -d gis -c 'CREATE EXTENSION IF NOT EXISTS postgis;' && \ psql -d gis -c 'CREATE EXTENSION IF NOT EXISTS hstore;' && \ + psql -d gis -f functions.sql && \ # Creating default import settings file editable by user and passing values for osm2pgsql if [ ! -e ".env" ]; then diff --git a/style/landcover.mss b/style/landcover.mss index e85f9c05a2..99f3be86f8 100644 --- a/style/landcover.mss +++ b/style/landcover.mss @@ -29,7 +29,7 @@ // --- Transport ---- @transportation-area: #e9e7e2; -@apron: #dadae0; +@apron: #cdcdda; @garages: #dfddce; @parking: #eeeeee; @parking-outline: saturate(darken(@parking, 40%), 20%); @@ -629,12 +629,6 @@ line-color: mix(@parking-outline, @parking, 50%); } - [feature = 'aeroway_apron'][zoom >= 10] { - polygon-fill: @apron; - [way_pixels >= 4] { polygon-gamma: 0.75; } - [way_pixels >= 64] { polygon-gamma: 0.3; } - } - [feature = 'aeroway_aerodrome'][zoom >= 10], [feature = 'amenity_ferry_terminal'][zoom >= 15], [feature = 'amenity_bus_station'][zoom >= 15] { diff --git a/style/roads.mss b/style/roads.mss index c750ba2b97..7156599b1a 100644 --- a/style/roads.mss +++ b/style/roads.mss @@ -17,10 +17,30 @@ @bridleway-fill-noaccess: #aaddaa; @track-fill: #996600; @track-fill-noaccess: #e2c5bb; + @aeroway-fill: #bbc; -@runway-fill: @aeroway-fill; -@taxiway-fill: @aeroway-fill; -@helipad-fill: @aeroway-fill; +@runway-fill: #9a9ab4; +@taxiway-fill: #aaaac0; +@stopway-fill: @taxiway-fill; +@helipad-fill: @runway-fill; +@aeroway-apron: #cdcdda; +@aeroway-runway-centreline: white; +@aeroway-taxiway-centreline: #f1fa4a; +@unpaved-aeroway-fill: #dcbeab; +@unpaved-runway-fill: #cb9e81; +@unpaved-taxiway-fill: #d3ae97; +@unpaved-stopway-fill: @unpaved-taxiway-fill; +@unpaved-helipad-fill: @unpaved-aeroway-fill; +@unpaved-aeroway-runway-centreline: #efdccf; +@unpaved-aeroway-taxiway-centreline: darken(@unpaved-aeroway-runway-centreline, 5%); +@grass-aeroway-fill: #dce3bd; +@grass-runway-fill: #d2dcab; +@grass-taxiway-fill: lighten(@grass-runway-fill, 2%); +@grass-stopway-fill: @grass-taxiway-fill; +@grass-helipad-fill: @grass-aeroway-fill; +@grass-aeroway-runway-centreline: @aeroway-fill; +@grass-aeroway-taxiway-centreline: @grass-aeroway-runway-centreline; + @access-marking: #eaeaea; @access-marking-living-street: #cccccc; @@ -2703,6 +2723,37 @@ tertiary is rendered from z10 and is not included in osm_planet_roads. */ } } +#aeroway-area-fill { + [feature = 'aeroway_runway'][zoom >= 14] { + polygon-fill: @aeroway-fill; + [int_surface = 'unpaved'] { polygon-fill: @unpaved-aeroway-fill; } + [int_surface = 'grass'] { polygon-fill: @grass-aeroway-fill; } + } + + [feature = 'aeroway_taxiway'][zoom >= 14] { + polygon-fill: @aeroway-fill; + [int_surface = 'unpaved'] { polygon-fill: @unpaved-aeroway-fill; } + [int_surface = 'grass'] { polygon-fill: @grass-aeroway-fill; } + } + + [feature = 'aeroway_stopway'][zoom >= 14] { + polygon-fill: @stopway-fill; + [int_surface = 'unpaved'] { polygon-fill: @unpaved-stopway-fill; } + [int_surface = 'grass'] { polygon-fill: @grass-stopway-fill; } + } + + [feature = 'aeroway_helipad'][zoom >= 16] { + polygon-fill: @helipad-fill; + } + + [feature = 'aeroway_apron'][zoom >= 14] { + polygon-fill: @apron; + [way_pixels >= 4] { polygon-gamma: 0.75; } + [way_pixels >= 64] { polygon-gamma: 0.3; } + } + +} + #highway-area-fill { [feature = 'highway_living_street'][zoom >= 14] { polygon-fill: @living-street-fill; @@ -2728,18 +2779,6 @@ tertiary is rendered from z10 and is not included in osm_planet_roads. */ polygon-gamma: 0.65; } } - - [feature = 'aeroway_runway'][zoom >= 11] { - polygon-fill: @runway-fill; - } - - [feature = 'aeroway_taxiway'][zoom >= 13] { - polygon-fill: @taxiway-fill; - } - - [feature = 'aeroway_helipad'][zoom >= 16] { - polygon-fill: @helipad-fill; - } } #junctions { @@ -2975,15 +3014,49 @@ tertiary is rendered from z10 and is not included in osm_planet_roads. */ } ::fill { line-color: @runway-fill; - line-width: 2; - [zoom >= 12] { line-width: 4; } - [zoom >= 13] { line-width: 6; } - [zoom >= 14] { line-width: 12; } - [zoom >= 15] { line-width: 18; } - [zoom >= 16] { line-width: 24; } + [int_surface = 'unpaved'] { line-color: @unpaved-runway-fill; } + [int_surface = 'grass'] { line-color: @grass-runway-fill; } + /* + Take the computed width from the `width` tagged on the runway, if present. + A default value is set if this tag is missing. + */ + line-width: [area_width]; + } + ::centerline[zoom >=15] { + line-width: [line_width]; + line-color: @aeroway-runway-centreline; + [int_surface = 'unpaved'] { line-color: @unpaved-aeroway-runway-centreline; } + [int_surface = 'grass'] { line-color: @grass-aeroway-runway-centreline; } + /* Keeps the dash-pattern roughly in sync with the line-width. */ + line-dasharray: 12,8; + [zoom >= 16] { line-dasharray: 21,14; } + [zoom >= 17] { line-dasharray: 42,28; } + [zoom >= 18] { line-dasharray: 84,56; } + [zoom >= 19] { line-dasharray: 168,112; } + [zoom >= 20] { line-dasharray: 336,224; } + [runway = "displaced_threshold"] { + line-width: 0; + marker-placement: line; + marker-fill: @aeroway-runway-centreline; + [int_surface = 'unpaved'] { marker-fill: @unpaved-aeroway-runway-centreline; } + /* Not likely with surface=grass, but for consistency's sake: */ + [int_surface = 'grass'] { marker-fill: @grass-aeroway-runway-centreline; } + marker-width: [line_width] * 16; + marker-spacing: [line_width] * 50; + marker-file: url('symbols/displaced_threshold.svg'); + } } } } + [aeroway = 'stopway'] { + ::fill { + line-color: @stopway-fill; + [int_surface = 'unpaved'] { line-color: @unpaved-stopway-fill; } + [int_surface = 'grass'] { line-color: @grass-stopway-fill; } + /* Same as aeroway=runway. */ + line-width: [area_width]; + } + } [aeroway = 'taxiway'] { [zoom >= 11] { ::casing[bridge = true][zoom >= 14] { @@ -2996,12 +3069,49 @@ tertiary is rendered from z10 and is not included in osm_planet_roads. */ [zoom >= 18] { line-width: 8 + 2*@secondary-casing-width-z18; } } ::fill { - line-color: @taxiway-fill ; - line-width: 1; - [zoom >= 13] { line-width: 2; } - [zoom >= 14] { line-width: 4; } + line-color: @taxiway-fill; + [int_surface = 'unpaved'] { line-color: @unpaved-taxiway-fill; } + [int_surface = 'grass'] { line-color: @grass-taxiway-fill; } + line-width: 2; + [zoom >= 14] { line-width: 3; } [zoom >= 15] { line-width: 6; } - [zoom >= 16] { line-width: 8; } + [zoom >= 16] { line-width: 7; } + [zoom >= 17] { line-width: 8; } + [zoom >= 18] { line-width: 16; } + line-cap: round; + } + ::centerline[zoom >= 15] { + line-color: @aeroway-taxiway-centreline; + [int_surface = 'unpaved'] { line-color: @unpaved-aeroway-taxiway-centreline; } + [int_surface = 'grass'] { line-color: @grass-aeroway-taxiway-centreline; } + line-width: 0.3; + [zoom >= 16] { line-width: 0.5; } + [zoom >= 17] { line-width: 1; } + [zoom >= 18] { line-width: 2; } + } + } + } + [aeroway = 'parking_position'], + [aeroway = 'taxilane'] { + [zoom >= 16] { + ::centerline[zoom >= 15] { + line-color: @aeroway-taxiway-centreline; + [int_surface = 'unpaved'] { line-color: @unpaved-aeroway-taxiway-centreline; } + [int_surface = 'grass'] { line-color: @grass-aeroway-taxiway-centreline; } + line-width: 0.5; + [zoom >= 17] { line-width: 1; } + [zoom >= 18] { line-width: 2; } + + [aeroway = 'parking_position'] { + marker-placement: vertex-last; + marker-fill: @aeroway-taxiway-centreline; + [int_surface = 'unpaved'] { marker-fill: @unpaved-aeroway-taxiway-centreline; } + [int_surface = 'grass'] { marker-fill: @grass-aeroway-taxiway-centreline; } + marker-height: 3; + [zoom >= 17] { marker-height: 6; } + [zoom >= 18] { marker-height: 12; } + marker-file: url('symbols/aeroway_parking_position_stop.svg'); + } } } } @@ -3128,19 +3238,62 @@ tertiary is rendered from z10 and is not included in osm_planet_roads. */ } } } - [highway = 'runway'], + [highway = 'runway'] { + [zoom >= 15] { + text-name: "[refs]"; + text-size: 12; + [zoom >= 16] { text-size: 18; } + [zoom >= 17] { text-size: 24; } + [zoom >= 18] { text-size: 32; } + [zoom >= 19] { text-size: 40; } + text-fill: white; + text-spacing: 0; + text-clip: false; + text-placement: line; + text-face-name: @bold-fonts; + text-halo-radius: @standard-halo-radius; + text-halo-fill: #666; + text-repeat-distance: @minor-highway-text-repeat-distance; + [height >= 2] { + /* + The vast majority of runway-refs is in a short form like 05/23 or 09L/27R, but for + multi-line exceptions we adjust the text. The query already leaves out any refs + longer than 11 characters. + */ + text-size: 10; + text-face-name: @book-fonts; + } + } + } [highway = 'taxiway'] { [zoom >= 15] { text-name: "[refs]"; text-size: 10; - text-fill: #333; - text-spacing: 750; + text-fill: white; + text-spacing: 0; text-clip: false; text-placement: line; - text-face-name: @oblique-fonts; + text-face-name: @bold-fonts; text-halo-radius: @standard-halo-radius; - text-halo-fill: @standard-halo-fill; + text-halo-fill: #666; + text-repeat-distance: @minor-highway-text-repeat-distance; + } + } + [highway = 'taxilane'], + [highway = 'parking_position'] { + [zoom >= 18] { + text-name: "[refs]"; + text-size: 10; + text-fill: black; + text-spacing: 0; + text-clip: false; + text-placement: line; + text-face-name: @book-fonts; + text-halo-radius: @standard-halo-radius * 0.7; + text-halo-fill: #fcf8a1; text-repeat-distance: @minor-highway-text-repeat-distance; + text-vertical-alignment: middle; + text-dy: 7; } } } diff --git a/symbols/aeroway_parking_position_stop.svg b/symbols/aeroway_parking_position_stop.svg new file mode 100644 index 0000000000..13fbdb1e51 --- /dev/null +++ b/symbols/aeroway_parking_position_stop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/symbols/displaced_threshold.svg b/symbols/displaced_threshold.svg new file mode 100644 index 0000000000..7d17ed088f --- /dev/null +++ b/symbols/displaced_threshold.svg @@ -0,0 +1,4 @@ + + + +