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 @@
+
+