diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb8a37cabe..f0353be855 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,12 +59,12 @@ jobs: - name: Wait for database run : sudo pg_ctlcluster 14 main start; until pg_isready; do sleep 0.5; done - name: Setup database - run: sudo -i -u postgres createuser -s $USER && createdb -E utf8 gis && psql -Xq -d gis -c "CREATE EXTENSION postgis; CREATE EXTENSION hstore;" + run: sudo -i -u postgres createuser -s $USER && createdb -E utf8 flex && psql -Xq -d flex -c "CREATE EXTENSION postgis; CREATE EXTENSION hstore;" - name: Import empty file run: | - osm2pgsql -G --hstore --style openstreetmap-carto.style --tag-transform-script openstreetmap-carto.lua -d gis -r xml <(echo '') + osm2pgsql --output flex --style openstreetmap-carto.lua -d flex -r xml <(echo '') - name: Create indexes - run: psql -1Xq -v ON_ERROR_STOP=1 -d gis -f indexes.sql + run: psql -1Xq -v ON_ERROR_STOP=1 -d flex -f indexes.sql - name: Load empty shapefiles run: scripts/get-external-data.py --no-update --cache -D scripts/empty_files - name: Test queries are valid diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4252b692ee..0754250ad1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -134,6 +134,13 @@ Because SQL within JSON or YAML will not generally be syntax highlighted, indent * Hstore queries tested for NULL should be enclosed in parentheses, e.g. `(tags->'foo') IS NULL`. * To check if a tag is in the tags hstore, use `tags @> 'foo=>bar'`, relying on automatic conversion from `text` to `hstore`. +## Lua style guidelines + +* Four space indents, no tabs +* No external dependencies +* Use `a["name"]` where name is a tag key, and `a.name` otherwise. + + ## Map icon guidelines * All new icons must be SVG format only. The SVG must be saved as standards compliant SVG without any proprietary tags. In Inkscape software, you will need to "Save As..." and choose the format Optimized SVG (preferable) or Plain SVG. diff --git a/DOCKER.md b/DOCKER.md index b7b66731fe..375fc44470 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -99,5 +99,5 @@ Docker stores its disk image by default in the home directories of the user. If When working with the style's database tables after an import, it can be helpful to log in at the [console](https://www.postgresql.org/docs/current/app-psql.html) to inspect the table structure or view imported data. The following command will open a psql console on the database: ``` -docker-compose exec -e PGUSER=postgres -e PGDATABASE=gis db psql +docker-compose exec -e PGUSER=postgres -e PGDATABASE=flex db psql ``` diff --git a/INSTALL.md b/INSTALL.md index ca273eeaf6..ac7ee1823a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -3,25 +3,25 @@ This document describes how to manually configure your system for running OpenStreetMap Carto. If you prefer quick, platform independent setup for a development environment, without the need to install and configure tools by hand, follow a Docker installation guide in [DOCKER.md](DOCKER.md). ## OpenStreetMap data -You need OpenStreetMap data loaded into a PostGIS database (see below for [dependencies](#dependencies)). These stylesheets expect a database generated with osm2pgsql using the pgsql backend (table names of `planet_osm_point`, etc), the default database name (`gis`), and the [lua transforms](https://osm2pgsql.org/doc/manual.html#lua-tag-transformations) documented in the instructions below. +You need OpenStreetMap data loaded into a PostGIS database (see below for [dependencies](#dependencies)). These stylesheets expect a database generated with osm2pgsql using the flex backend with the supplied Lua scripts. This requires osm2pgsql 1.4.1 or later. Start by creating a database ```sh sudo -u postgres createuser -s $USER -createdb gis +createdb flex ``` Enable PostGIS and hstore extensions with ```sh -psql -d gis -c 'CREATE EXTENSION postgis; CREATE EXTENSION hstore;' +psql -d flex -c 'CREATE EXTENSION postgis; CREATE EXTENSION hstore;' ``` then grab some OSM data. It's probably easiest to grab an PBF of OSM data from [Geofabrik](https://download.geofabrik.de/). Once you've done that, import with osm2pgsql: ```sh -osm2pgsql -G --hstore --style openstreetmap-carto.style --tag-transform-script openstreetmap-carto.lua -d gis ~/path/to/data.osm.pbf +osm2pgsql --output flex --style openstreetmap-carto.lua -d flex ~/path/to/data.osm.pbf ``` You can find a more detailed guide to setting up a database and loading data with osm2pgsql at [switch2osm.org](https://switch2osm.org/serving-tiles/manually-building-a-tile-server-16-04-2-lts/). @@ -32,19 +32,19 @@ We do not recommend [PostgreSQL JIT](https://www.postgresql.org/docs/current/jit Disabling JIT is **essential** for use with Kosmtik and other style development tools. -JIT can be disabled with `psql -d gis -c 'ALTER SYSTEM SET jit=off;' -c 'SELECT pg_reload_conf();'` or any other means of adjusting the PostgreSQL config. +JIT can be disabled with `psql -d postgres -c 'ALTER SYSTEM SET jit=off;' -c 'SELECT pg_reload_conf();'` or any other means of adjusting the PostgreSQL config. ### Custom indexes Custom indexes are required for rendering performance and are essential on full planet databases. These are generated by the `scripts/indexes.py` script, see `scripts/indexes.py --help` for various advanced options, but the command below will work to create the indexes on a new installation: ```sh -psql -d gis -f indexes.sql +psql -d flex -f indexes.sql ``` The indexes can be created in parallel with ```sh -scripts/indexes.py -0 | xargs -0 -P0 -I{} psql -d gis -c "{}" +scripts/indexes.py -0 | xargs -0 -P0 -I{} psql -d flex -c "{}" ``` ## Scripted download @@ -81,9 +81,9 @@ For development, a style design studio is needed. To display any map a database containing OpenStreetMap data and some utilities are required * [PostgreSQL](https://www.postgresql.org/) -* [PostGIS](https://postgis.net/) -* [osm2pgsql](https://github.com/openstreetmap/osm2pgsql#installing) to [import your data](https://switch2osm.org/serving-tiles/updating-as-people-edit/) into a PostGIS database -* Python 3 with the psycopg2, yaml, and requests libraries (`python3-psycopg2`, `python3-yaml`, `python3-requests` packages on Debian-derived systems) +* [PostGIS](https://postgis.net/) 3.1.0 or later with GEOS 3.9.0 or later +* [osm2pgsql](https://github.com/openstreetmap/osm2pgsql#installing) 1.4.1 or later to import your data into a PostGIS database +* Python 3 with the psycopg2, yaml, and requests libraries (`python3-psycopg2` `python3-yaml` `python3-requests` packages on Debian-derived systems) * `ogr2ogr` for loading shapefiles into the database (`gdal-bin` on Debian-derived systems) ### Optional development dependencies diff --git a/external-data.yml b/external-data.yml index 6c566cba48..3ca60b75b7 100644 --- a/external-data.yml +++ b/external-data.yml @@ -2,7 +2,7 @@ settings: temp_schema: loading schema: public data_dir: data - database: gis + database: flex metadata_table: external_data sources: simplified_water_polygons: diff --git a/indexes.sql b/indexes.sql index af4886b385..5e6471e5e0 100644 --- a/indexes.sql +++ b/indexes.sql @@ -16,3 +16,4 @@ CREATE INDEX planet_osm_polygon_way_area_z6 ON planet_osm_polygon USING GIST (wa CREATE INDEX planet_osm_roads_admin ON planet_osm_roads USING GIST (way) WHERE boundary = 'administrative'; CREATE INDEX planet_osm_roads_admin_low ON planet_osm_roads USING GIST (way) WHERE boundary = 'administrative' AND admin_level IN ('0', '1', '2', '3', '4'); CREATE INDEX planet_osm_roads_roads_ref ON planet_osm_roads USING GIST (way) WHERE highway IS NOT NULL AND ref IS NOT NULL; +CREATE INDEX planet_osm_route_member_id ON planet_osm_route USING btree (member_id); diff --git a/indexes.yml b/indexes.yml index dd7179fe9b..127ee83a4b 100644 --- a/indexes.yml +++ b/indexes.yml @@ -45,3 +45,8 @@ roads: where: boundary = 'administrative' roads_ref: where: highway IS NOT NULL AND ref IS NOT NULL +route: + # The route table has no geospatial data, so it's index is a btree + member_id: + function: member_id + type: btree diff --git a/openstreetmap-carto.lua b/openstreetmap-carto.lua index 8fc2c85212..bb28d2c92d 100644 --- a/openstreetmap-carto.lua +++ b/openstreetmap-carto.lua @@ -1,5 +1,247 @@ -- For documentation of Lua tag transformations, see: --- https://github.com/openstreetmap/osm2pgsql/blob/master/docs/lua.md +-- https://osm2pgsql.org/doc/manual.html#the-flex-output + +local tables = {} + +-- A list of text columns per table, replacing the legacy osm2pgsql 'pgsql output' .style file. +-- These are inserted into the table list of columns in the same order as lists here, +-- and thus determine column order in the final tables. +-- Non-text columns are defined in col_definitions. +local pg_cols = { + point = { + 'access', + 'addr:housename', + 'addr:housenumber', + 'admin_level', + 'aerialway', + 'aeroway', + 'amenity', + 'barrier', + 'boundary', + 'building', + 'highway', + 'historic', + 'junction', + 'landuse', + 'leisure', + 'lock', + 'man_made', + 'military', + 'name', + 'natural', + 'oneway', + 'place', + 'power', + 'railway', + 'ref', + 'religion', + 'shop', + 'tourism', + 'water', + 'waterway' + }, + line = { + 'access', + 'addr:housename', + 'addr:housenumber', + 'addr:interpolation', + 'admin_level', + 'aerialway', + 'aeroway', + 'amenity', + 'barrier', + 'bicycle', + 'bridge', + 'boundary', + 'building', + 'construction', + 'covered', + 'foot', + 'highway', + 'historic', + 'horse', + 'junction', + 'landuse', + 'leisure', + 'lock', + 'man_made', + 'military', + 'name', + 'natural', + 'oneway', + 'place', + 'power', + 'railway', + 'ref', + 'religion', + 'route', + 'service', + 'shop', + 'surface', + 'tourism', + 'tracktype', + 'tunnel', + 'water', + 'waterway' + }, + transport_line = { + 'access', + 'aeroway', + 'bicycle', + 'bridge', + 'construction', + 'covered', + 'foot', + 'highway', + 'horse', + 'name', + 'oneway', + 'railway', + 'ref', + 'service', + 'surface', + 'tracktype', + 'tunnel' + }, + transport_polygon = { + 'access', + 'bicycle', + 'bridge', + 'construction', + 'covered', + 'foot', + 'highway', + 'horse', + 'name', + 'oneway', + 'railway', + 'ref', + 'service', + 'surface', + 'tracktype', + 'tunnel' + }, + route = { + 'route', + 'ref', + 'network' + }, + admin = {} +} + +-- The roads and polygon columns are the same as the line columns +pg_cols.roads = pg_cols.line +pg_cols.polygon = pg_cols.line + +-- These columns aren't text columns +col_definitions = { + point = { + { column = 'way', type = 'point' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' } + }, + line = { + { column = 'way', type = 'linestring' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' }, + { column = 'z_order', type = 'int4' } + }, + transport_line = { + { column = 'way', type = 'linestring' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' }, + { column = 'z_order', type = 'int4' } + }, + transport_polygon = { + { column = 'way', type = 'geometry' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' }, + { column = 'z_order', type = 'int4' }, + { column = 'way_area', type = 'area' } + }, + roads = { + { column = 'way', type = 'linestring' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' }, + { column = 'z_order', type = 'int4' } + }, + polygon = { + { column = 'way', type = 'geometry' }, + { column = 'tags', type = 'hstore' }, + { column = 'layer', type = 'int4' }, + { column = 'z_order', type = 'int4' }, + { column = 'way_area', type = 'area' } + }, + route = { + { column = 'member_id', type = 'int8' }, + { column = 'member_position', type = 'int4' }, + { column = 'tags', type = 'hstore' } + }, + admin = { + { column = 'way', type = 'linestring' }, + { column = 'admin_level', type = 'int2' }, + { column = 'multiple_relations', type = 'boolean' } + } +} + +-- Combine the two sets of columns and create a map with column names. +-- The latter is needed for quick lookup to see if a tag has a column. +local columns_map = {} +for tablename, columns in pairs(pg_cols) do + columns_map[tablename] = {} + for _, key in ipairs(columns) do + table.insert(col_definitions[tablename], {column = key, type = "text"}) + columns_map[tablename][key] = true + end +end + +tables.point = osm2pgsql.define_table{ + name = 'planet_osm_point', + ids = { type = 'node', id_column = 'osm_id' }, + columns = col_definitions.point +} + +tables.line = osm2pgsql.define_table{ + name = 'planet_osm_line', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.line +} + +tables.transport_line = osm2pgsql.define_table{ + name = 'planet_osm_transport_line', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.transport_line +} + +tables.roads = osm2pgsql.define_table{ + name = 'planet_osm_roads', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.roads +} + +tables.polygon = osm2pgsql.define_table{ + name = 'planet_osm_polygon', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.polygon +} + +tables.transport_polygon = osm2pgsql.define_table{ + name = 'planet_osm_transport_polygon', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.transport_polygon +} + +tables.route = osm2pgsql.define_table{ + name = 'planet_osm_route', + ids = { type = 'relation', id_column = 'osm_id' }, + columns = col_definitions.route +} + +tables.admin = osm2pgsql.define_table{ + name = 'planet_osm_admin', + ids = { type = 'way', id_column = 'osm_id' }, + columns = col_definitions.admin +} -- Objects with any of the following keys will be treated as polygon local polygon_keys = { @@ -33,7 +275,6 @@ local polygon_keys = { 'shop', 'tourism', 'water', - 'waterway', 'wetland' } @@ -46,8 +287,7 @@ local linestring_values = { man_made = {breakwater = true, cutline = true, embankment = true, groyne = true, pipeline = true}, natural = {cliff = true, earth_bank = true, tree_row = true, ridge = true, arete = true}, power = {cable = true, line = true, minor_line = true}, - tourism = {yes = true}, - waterway = {canal = true, derelict_canal = true, ditch = true, drain = true, river = true, stream = true, tidal_channel = true, wadi = true, weir = true} + tourism = {yes = true} } -- Objects with any of the following key/value combinations will be treated as polygon @@ -56,7 +296,8 @@ local polygon_values = { boundary = {aboriginal_lands = true, national_park = true, protected_area= true}, highway = {services = true, rest_area = true}, junction = {yes = true}, - railway = {station = true} + railway = {station = true}, + waterway = {dock = true, boatyard = true, fuel = true, riverbank = true} } -- The following keys will be deleted @@ -183,8 +424,12 @@ delete_prefixes = { 'mvdgis:' } --- Big table for z_order and roads status for certain tags. z=0 is turned into --- nil by the z_order function +-- Big table for z_order and roads status for certain tags. +-- The road status (true/false) determines whether or not the feature will be +-- included in the legacy 'roads' table. +-- z=0 is turned into nil by the z_order function. +-- Road z values are divided by 10 for objects tagged as highway=construction, +-- construction=[HIGHWAY_CLASS], so must be multiples of 10. local roads_info = { highway = { motorway = {z = 380, roads = true}, @@ -203,6 +448,7 @@ local roads_info = { primary_link = {z = 220, roads = true}, secondary_link = {z = 210, roads = true}, tertiary_link = {z = 200, roads = false}, + busway = {z = 170, roads = false}, service = {z = 150, roads = false}, track = {z = 110, roads = false}, path = {z = 100, roads = false}, @@ -256,7 +502,8 @@ function z_order(tags) if tags["construction"] and roads_info["highway"][tags["construction"]] then z = math.max(z, roads_info["highway"][tags["construction"]].z/10) else - z = math.max(z, 33) + -- For unknown roads, assume highway=road + z = math.max(z, roads_info["highway"]["road"].z/10) end end @@ -265,34 +512,78 @@ end --- Gets the roads table status for a set of tags -- @param tags OSM tags --- @return 1 if it belongs in the roads table, 0 otherwise +-- @return true if it belongs in the roads table, false otherwise function roads(tags) for k, v in pairs(tags) do if roads_info[k] and roads_info[k][v] and roads_info[k][v].roads then if not (k ~= 'railway' or tags.service) then - return 1 + return true elseif not excluded_railway_service[tags.service] then - return 1 + return true + end + end + end + return false +end + +--- Check if an object with given tags should be treated as polygon +-- @param tags OSM tags +-- @return true if area, false if linear +function isarea (tags) + -- Treat objects tagged as area=yes polygon, other area as no + if tags["area"] then + return tags["area"] == "yes" and true or false + end + + -- Search through object's tags + for k, v in pairs(tags) do + -- Check if it has a polygon key and not a linestring override, or a polygon k=v + for _, ptag in ipairs(polygon_keys) do + if k == ptag and v ~= "no" and not (linestring_values[k] and linestring_values[k][v]) then + return true end end + + if (polygon_values[k] and polygon_values[k][v]) then + return true + end + end + return false +end + +--- Normalizes layer tags to integers +-- @param v The layer tag value +-- @return The input value if it is an integer between -100 and 100, or nil otherwise +function layer (v) + if v and string.find(v, "^-?%d+$") and tonumber(v) < 100 and tonumber(v) > -100 then -- check if value exists, is numeric, and is in range + return v + end + return nil +end + +--- Normalizes admin_level tags +-- @param v The admin_level tag value +-- @return The input value if it is an integer between 0 and 100, or nil otherwise +function admin_level (v) + if v and string.find(v, "^%d+$") and tonumber(v) < 100 and tonumber(v) > 0 then + return v end - return 0 + return nil end ---- Generic filtering of OSM tags --- @param tags Raw OSM tags --- @return Filtered OSM tags -function filter_tags_generic(tags) +--- Clean tags of deleted tags +-- @param tags OSM tags +-- @return true if no tags are left after cleaning +function clean_tags(tags) -- Short-circuit for untagged objects if next(tags) == nil then - return 1, {} + return true end -- Delete tags listed in delete_tags for _, d in ipairs(delete_tags) do tags[d] = nil end - -- By using a second loop for wildcards we avoid checking already deleted tags for tag, _ in pairs (tags) do for _, d in ipairs(delete_prefixes) do @@ -303,134 +594,198 @@ function filter_tags_generic(tags) end end - -- Filter out objects that have no tags after deleting - if next(tags) == nil then - return 1, {} - end - - -- Convert layer to an integer - tags['layer'] = layer(tags['layer']) - return 0, tags + return next(tags) == nil end --- Filtering on nodes -function filter_tags_node (keyvalues, numberofkeys) - return filter_tags_generic(keyvalues) -end - --- Filtering on relations -function filter_basic_tags_rel (keyvalues, numberofkeys) - -- Filter out objects that are filtered out by filter_tags_generic - local filter, keyvalues = filter_tags_generic(keyvalues) - if filter == 1 then - return 1, keyvalues +--- Splits a tag into tags and hstore tags +-- @param tags OSM tags +-- @param tag_map Lua table that contains the OSM tags that will get a dedicated column +-- @return columns, hstore tags +function split_tags(tags, tag_map) + local cols = {tags = {}} + + for key, value in pairs(tags) do + if tag_map[key] then + cols[key] = value + else + cols.tags[key] = value + end end - -- Filter out all relations except route, multipolygon and boundary relations - if ((keyvalues["type"] ~= "route") and (keyvalues["type"] ~= "multipolygon") and (keyvalues["type"] ~= "boundary")) then - return 1, keyvalues - end + -- layer is a special tag. After setting the columns, we have to go back and set it after making sure it's an integer, and remove it from tags + cols['layer'] = layer(tags['layer']) + cols.tags['layer'] = nil - return 0, keyvalues + return cols end --- Filtering on ways -function filter_tags_way (keyvalues, numberofkeys) - local filter = 0 -- Will object be filtered out? - local polygon = 0 -- Will object be treated as polygon? +phase2_admin_ways_level = {} +phase2_admin_ways_parents = {} - -- Filter out objects that are filtered out by filter_tags_generic - filter, keyvalues = filter_tags_generic(keyvalues) - if filter == 1 then - return filter, keyvalues, polygon, roads - end +-- Processing callbacks (https://osm2pgsql.org/doc/manual.html#processing-callbacks) and functions that directly support them - polygon = isarea(keyvalues) +--- Add an object to the point table +-- @param object parameter table supplied by osm2pgsql +function add_point(object) + local cols = split_tags(object.tags, columns_map.point) + tables.point:add_row(cols) +end - -- Add z_order column - keyvalues["z_order"] = z_order(keyvalues) +--- Add an object to the line table +-- @param object parameter table supplied by osm2pgsql +function add_line(object) + local cols = split_tags(object.tags, columns_map.line) + cols['z_order'] = z_order(object.tags) + cols.way = { create = 'line', split_at = 100000 } + tables.line:add_row(cols) +end - return filter, keyvalues, polygon, roads(keyvalues) +--- Add an object to the transport_line table +-- @param object parameter table supplied by osm2pgsql +function add_transport_line(object) + local cols = split_tags(object.tags, columns_map.transport_line) + cols['z_order'] = z_order(object.tags) + cols.way = { create = 'line', split_at = 100000 } + tables.transport_line:add_row(cols) end ---- Handling for relation members and multipolygon generation --- @param keyvalues OSM tags, after processing by relation transform --- @param keyvaluemembers OSM tags of relation members, after processing by way transform --- @param roles OSM roles of relation members --- @param membercount number of members --- @return filter, cols, member_superseded, boundary, polygon, roads -function filter_tags_relation_member (keyvalues, keyvaluemembers, roles, membercount) - local members_superseded = {} - - -- Start by assuming that this not an old-style MP - for i = 1, membercount do - members_superseded[i] = 0 - end +--- Add an object to the roads table +-- @param object parameter table supplied by osm2pgsql +function add_roads(object) + local cols = split_tags(object.tags, columns_map.roads) + cols['z_order'] = z_order(object.tags) + cols.way = { create = 'line', split_at = 100000 } + tables.roads:add_row(cols) +end - local type = keyvalues["type"] +--- Add an object to the polygon table +-- @param object parameter table supplied by osm2pgsql +function add_polygon(object) + local cols = split_tags(object.tags, columns_map.polygon) + cols['z_order'] = z_order(object.tags) + cols.way = { create = 'area', split_at = nil } + tables.polygon:add_row(cols) +end - -- Remove type key - keyvalues["type"] = nil +--- Add an object to the transport_polygon table +-- @param object parameter table supplied by osm2pgsql +function add_transport_polygon(object) + local cols = split_tags(object.tags, columns_map.transport_polygon) + cols['z_order'] = z_order(object.tags) + cols.way = { create = 'area', split_at = nil } + tables.transport_polygon:add_row(cols) +end - -- Filter out relations with just a type tag or no tags - if next(keyvalues) == nil then - return 1, keyvalues, members_superseded, 0, 0, 0 +--- Add an object to the route table +-- @param object parameter table supplied by osm2pgsql +function add_route(object) + for i, member in ipairs(object.members) do + if member.type == 'w' then + local cols = split_tags(object.tags, columns_map.route) + cols.member_id = member.ref + cols.member_position = i + tables.route:add_row(cols) + end end +end - if type == "boundary" or (type == "multipolygon" and keyvalues["boundary"]) then - keyvalues.z_order = z_order(keyvalues) - return 0, keyvalues, members_superseded, 1, 0, roads(keyvalues) - -- For multipolygons... - elseif (type == "multipolygon") then - -- Multipolygons by definition are polygons, so we know roads = linestring = 0, polygon = 1 - keyvalues.z_order = z_order(keyvalues) - return 0, keyvalues, members_superseded, 0, 1, 0 - elseif type == "route" then - keyvalues.z_order = z_order(keyvalues) - return 0, keyvalues, members_superseded, 1, 0, roads(keyvalues) +function osm2pgsql.process_node(object) + if clean_tags(object.tags) then + return end - -- Unknown type of relation or no type tag - return 1, keyvalues, members_superseded, 0, 0, 0 + add_point(object) end ---- Check if an object with given tags should be treated as polygon --- @param tags OSM tags --- @return 1 if area, 0 if linear -function isarea (tags) - -- Treat objects tagged as area=yes polygon, other area as no - if tags["area"] then - return tags["area"] == "yes" and 1 or 0 +function osm2pgsql.process_way(object) + if osm2pgsql.stage == 2 then + -- Stage two processing is called on ways that are part of admin boundary relations + if object.tags.closure_segment ~= 'yes' and phase2_admin_ways_level[object.id] then + tables.admin:add_row({admin_level = phase2_admin_ways_level[object.id], + multiple_relations = (phase2_admin_ways_parents[object.id] > 1), + way = { create = 'line' }}) + end + end + if clean_tags(object.tags) then + return end - -- Search through object's tags - for k, v in pairs(tags) do - -- Check if it has a polygon key and not a linestring override, or a polygon k=v - for _, ptag in ipairs(polygon_keys) do - if k == ptag and v ~= "no" and not (linestring_values[k] and linestring_values[k][v]) then - return 1 - end + local area_tags = isarea(object.tags) + if object.is_closed and area_tags then + add_polygon(object) + + if z_order(object.tags) ~= nil then + add_transport_polygon(object) end + else + add_line(object) - if (polygon_values[k] and polygon_values[k][v]) then - return 1 + if z_order(object.tags) ~= nil then + add_transport_line(object) + end + + if roads(object.tags) then + add_roads(object) end end - return 0 end -function is_in (needle, haystack) - for index, value in ipairs (haystack) do - if value == needle then - return true +function osm2pgsql.process_relation(object) + -- grab the type tag before filtering tags + local type = object.tags.type + object.tags.type = nil + + if clean_tags(object.tags) then + return + end + + if type == "boundary" then + if object.tags.boundary == 'administrative' then + local admin = tonumber(admin_level(object.tags.admin_level)) + if admin ~= nil then + for _, ref in ipairs(osm2pgsql.way_member_ids(object)) do + -- Store the lowest admin_level, and how many relations it used in + if not phase2_admin_ways_level[ref] then + phase2_admin_ways_level[ref] = admin + phase2_admin_ways_parents[ref] = 1 + else + if phase2_admin_ways_level[ref] == admin then + phase2_admin_ways_parents[ref] = phase2_admin_ways_parents[ref] + 1 + elseif admin < phase2_admin_ways_level[ref] then + phase2_admin_ways_level[ref] = admin + phase2_admin_ways_parents[ref] = 1 + end + end + end + end + end + + add_line(object) + + if roads(object.tags) then + add_roads(object) end + + add_polygon(object) + + elseif type == "multipolygon" then + add_polygon(object) + + if z_order(object.tags) ~= nil then + add_transport_polygon(object) + end + elseif type == "route" then + add_route(object) end - return false + end ---- Normalizes layer tags --- @param v The layer tag value --- @return An integer for the layer tag -function layer (v) - return v and string.find(v, "^-?%d+$") and tonumber(v) < 100 and tonumber(v) > -100 and v or nil +function osm2pgsql.select_relation_members(relation) + if relation.tags.type == 'boundary' + and relation.tags.boundary == 'administrative' then + local admin = tonumber(admin_level(relation.tags.admin_level)) + if admin ~= nil then + return { ways = osm2pgsql.way_member_ids(relation) } + end + end end diff --git a/openstreetmap-carto.style b/openstreetmap-carto.style deleted file mode 100644 index 0a30add22d..0000000000 --- a/openstreetmap-carto.style +++ /dev/null @@ -1,55 +0,0 @@ -# This is the osm2pgsql .style file for openstreetmap-carto. -# It is intended to be used with openstreetmap-carto.lua and osm2pgsql Lua -# transforms. Full usage details are in INSTALL.md -# Among things, this means that the linear vs polygon distinction in this file -# doesn't matter, because that is set in the Lua and this file is only used for -# column names and types. - -# OsmType Tag DataType Flags -node,way access text linear -node,way addr:housename text linear -node,way addr:housenumber text linear -way addr:interpolation text linear -node,way admin_level text linear -node,way aerialway text linear -node,way aeroway text polygon -node,way amenity text polygon -node,way barrier text linear -way bicycle text linear -way bridge text linear -node,way boundary text linear -node,way building text polygon -way construction text linear -way covered text linear -way foot text linear -node,way highway text linear -node,way historic text polygon -way horse text linear -node,way junction text linear -node,way landuse text polygon -node,way layer int4 linear -node,way leisure text polygon -node,way lock text linear -node,way man_made text polygon -node,way military text polygon -node,way name text linear -node,way natural text polygon -node,way oneway text linear -node,way place text polygon -node,way power text polygon -node,way railway text linear -node,way ref text linear -node,way religion text linear -way route text linear -way service text linear -node,way shop text polygon -way surface text linear -node,way tourism text polygon -way tracktype text linear -way tunnel text linear -node,way water text polygon -node,way waterway text polygon -way way_area real linear # This is calculated during import - -# Columns defined in openstreetmap-carto.lua file -way z_order int4 linear diff --git a/project.mml b/project.mml index fdeef517d6..f54ce58126 100644 --- a/project.mml +++ b/project.mml @@ -14,7 +14,7 @@ _parts: srs: *srs osm2pgsql: &osm2pgsql type: "postgis" - dbname: "gis" + dbname: "flex" key_field: "" geometry_field: "way" srid: *srid @@ -480,7 +480,7 @@ Layer: END AS link, COALESCE(layer,0) AS layernotnull, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE (tunnel = 'yes' OR tunnel = 'building_passage' OR covered = 'yes') AND highway IS NOT NULL -- end of road select UNION ALL @@ -504,7 +504,7 @@ Layer: 'no' AS link, COALESCE(layer,0) AS layernotnull, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE (tunnel = 'yes' OR tunnel = 'building_passage' OR covered = 'yes') AND (railway NOT IN ('platform') AND railway IS NOT NULL) -- end of rail select ) AS features @@ -709,7 +709,7 @@ Layer: END AS access, construction, CASE - WHEN service IN ('parking_aisle', 'drive-through', 'driveway') OR leisure IN ('slipway') THEN 'INT-minor'::text + WHEN service IN ('parking_aisle', 'drive-through', 'driveway') OR tags->'leisure' IN ('slipway') THEN 'INT-minor'::text ELSE 'INT-normal'::text END AS service, CASE @@ -719,7 +719,7 @@ Layer: COALESCE(layer,0) AS layernotnull, osm_id, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE (tunnel IS NULL OR NOT tunnel IN ('yes', 'building_passage')) AND (covered IS NULL OR NOT covered = 'yes') AND (bridge IS NULL OR NOT bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct')) @@ -746,12 +746,12 @@ Layer: WHEN access IN ('no', 'private') THEN 'no'::text END AS access, construction, - CASE WHEN service IN ('parking_aisle', 'drive-through', 'driveway') OR leisure IN ('slipway') THEN 'INT-minor'::text ELSE 'INT-normal'::text END AS service, + CASE WHEN service IN ('parking_aisle', 'drive-through', 'driveway') OR tags->'leisure' IN ('slipway') THEN 'INT-minor'::text ELSE 'INT-normal'::text END AS service, 'no' AS link, COALESCE(layer,0) AS layernotnull, osm_id, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE (tunnel IS NULL OR NOT tunnel IN ('yes', 'building_passage')) AND (covered IS NULL OR NOT covered = 'yes') AND (bridge IS NULL OR NOT bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct')) @@ -871,7 +871,7 @@ Layer: WHEN surface IN ('paved', 'asphalt', 'cobblestone', 'cobblestone:flattened', 'sett', 'concrete', 'concrete:lanes', 'concrete:plates', 'paving_stones', 'metal', 'wood', 'unhewn_cobblestone') THEN 'paved' END AS int_surface - FROM planet_osm_roads + FROM planet_osm_transport_line WHERE highway IS NOT NULL OR (railway IS NOT NULL AND railway != 'preserved' AND (service IS NULL OR service NOT IN ('spur', 'siding', 'yard'))) @@ -951,7 +951,7 @@ Layer: END AS link, COALESCE(layer,0) AS layernotnull, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct') AND highway IS NOT NULL -- end of road select UNION ALL @@ -975,7 +975,7 @@ Layer: 'no' AS link, COALESCE(layer,0) AS layernotnull, z_order - FROM planet_osm_line + FROM planet_osm_transport_line WHERE bridge IN ('yes', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle', 'viaduct') AND railway IS NOT NULL -- end of rail select ) AS features @@ -1081,12 +1081,12 @@ Layer: <<: *osm2pgsql table: |- (SELECT - way, + ST_LineMerge(ST_Collect(way)) AS way, admin_level - FROM planet_osm_roads - WHERE boundary = 'administrative' - AND admin_level IN ('0', '1', '2', '3', '4') - AND osm_id < 0 + FROM planet_osm_admin + WHERE admin_level BETWEEN 0 AND 4 + AND way && !bbox! + GROUP BY admin_level ORDER BY admin_level DESC ) AS admin_low_zoom properties: @@ -1099,12 +1099,12 @@ Layer: <<: *osm2pgsql table: |- (SELECT - way, + ST_LineMerge(ST_Collect(way)) AS way, admin_level - FROM planet_osm_roads - WHERE boundary = 'administrative' - AND admin_level IN ('0', '1', '2', '3', '4', '5', '6', '7', '8') - AND osm_id < 0 + FROM planet_osm_admin + WHERE admin_level BETWEEN 0 AND 8 + AND way && !bbox! + GROUP BY admin_level ORDER BY admin_level DESC ) AS admin_mid_zoom properties: @@ -1117,13 +1117,13 @@ Layer: <<: *osm2pgsql table: |- (SELECT - way, + ST_LineMerge(ST_Collect(way)) AS way, admin_level - FROM planet_osm_roads - WHERE boundary = 'administrative' - AND admin_level IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10') - AND osm_id < 0 - ORDER BY admin_level::integer DESC -- With 10 as a valid value, we need to do a numeric ordering, not a text ordering + FROM planet_osm_admin + WHERE admin_level BETWEEN 0 AND 10 + AND way && !bbox! + GROUP BY admin_level + ORDER BY admin_level DESC ) AS admin_high_zoom properties: minzoom: 13 @@ -1779,7 +1779,7 @@ Layer: osm_id, highway, string_to_array(ref, ';') AS refs - FROM planet_osm_roads + FROM planet_osm_transport_line WHERE highway IN ('motorway', 'trunk', 'primary', 'secondary') AND ref IS NOT NULL ) AS p) AS q @@ -1889,21 +1889,21 @@ Layer: name, CASE WHEN oneway IN ('yes', '-1') THEN oneway - WHEN junction IN ('roundabout') AND (oneway IS NULL OR NOT oneway IN ('no', 'reversible')) THEN 'yes' + WHEN tags->'junction' IN ('roundabout') AND (oneway IS NULL OR NOT oneway IN ('no', 'reversible')) THEN 'yes' END AS oneway, horse, bicycle - FROM planet_osm_line l + FROM planet_osm_transport_line WHERE highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link', 'tertiary', 'tertiary_link', 'residential', 'unclassified', 'road', 'service', 'pedestrian', 'raceway', 'living_street', 'construction') AND (name IS NOT NULL OR oneway IN ('yes', '-1') - OR junction IN ('roundabout')) + OR tags->'junction' IN ('roundabout')) ORDER BY z_order DESC, -- put important roads first COALESCE(layer, 0), -- put top layered roads first length(name) DESC, -- Try to fit big labels in first name DESC, -- Force a consistent ordering between differently named streets - l.osm_id DESC -- Force an ordering for streets of the same name, e.g. dualized roads + osm_id DESC -- Force an ordering for streets of the same name, e.g. dualized roads ) AS roads_text_name properties: cache-features: true @@ -1921,15 +1921,15 @@ Layer: name, CASE WHEN oneway IN ('yes', '-1') THEN oneway - WHEN junction IN ('roundabout') AND (oneway IS NULL OR NOT oneway IN ('no', 'reversible')) THEN 'yes' + WHEN tags->'junction' IN ('roundabout') AND (oneway IS NULL OR NOT oneway IN ('no', 'reversible')) THEN 'yes' END AS oneway, horse, bicycle - FROM planet_osm_line + FROM planet_osm_transport_line WHERE highway IN ('bridleway', 'footway', 'cycleway', 'path', 'track', 'steps', 'construction') AND (name IS NOT NULL OR oneway IN ('yes', '-1') - OR junction IN ('roundabout')) + OR tags->'junction' IN ('roundabout')) ) AS paths_text_name properties: cache-features: true @@ -1950,7 +1950,7 @@ Layer: tags->'usage' as usage, construction, name - FROM planet_osm_line l + FROM planet_osm_transport_line WHERE railway IN ('rail', 'subway', 'narrow_gauge', 'light_rail', 'preserved', 'funicular', 'monorail', 'miniature', 'tram', 'disused', 'construction') AND (tunnel IS NULL OR NOT tunnel IN ('yes', 'building_passage')) @@ -1961,7 +1961,7 @@ Layer: COALESCE(layer, 0), -- put top layered rails first length(name) DESC, -- Try to fit big labels in first name DESC, -- Force a consistent ordering between differently named railways - l.osm_id DESC -- Force an ordering for railways of the same name, e.g. dualized rails + osm_id DESC -- Force an ordering for railways of the same name, e.g. dualized rails ) AS railways_text_name properties: minzoom: 11 @@ -1991,7 +1991,7 @@ Layer: way, CASE WHEN highway IN ('unclassified', 'residential', 'track') THEN highway END AS highway, string_to_array(ref, ';') AS refs - FROM planet_osm_line + FROM planet_osm_transport_line WHERE highway IN ('unclassified', 'residential', 'track') AND ref IS NOT NULL ) AS p) AS q diff --git a/scripts/docker-startup.sh b/scripts/docker-startup.sh index c3177ee9d1..bfe9e6c0e5 100644 --- a/scripts/docker-startup.sh +++ b/scripts/docker-startup.sh @@ -18,9 +18,9 @@ test $i -gt $MAXCOUNT && echo "Timeout while waiting for PostgreSQL to be runnin case "$1" in import) # Creating default database - 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 -c "SELECT 1 FROM pg_database WHERE datname = 'flex';" | grep -q 1 || createdb flex && \ + psql -d flex -c 'CREATE EXTENSION IF NOT EXISTS postgis;' && \ + psql -d flex -c 'CREATE EXTENSION IF NOT EXISTS hstore;' && \ # Creating default import settings file editable by user and passing values for osm2pgsql if [ ! -e ".env" ]; then @@ -45,7 +45,7 @@ EOF --number-processes $OSM2PGSQL_NUMPROC \ --hstore \ --multi-geometry \ - --database gis \ + --database flex \ --slim \ --drop \ --style openstreetmap-carto.style \ diff --git a/scripts/indexes.py b/scripts/indexes.py index 4e51ac082d..64f33f01fe 100755 --- a/scripts/indexes.py +++ b/scripts/indexes.py @@ -19,13 +19,13 @@ separator = '\0' if args.null else '\n' -def index_statement(table, name, function, conditions=None, concurrent=False,notexist=False, fillfactor=None): +def index_statement(table, name, function, type, conditions=None, concurrent=False,notexist=False, fillfactor=None): options = ' CONCURRENTLY' if concurrent else '' options += ' IF NOT EXISTS' if notexist else '' storage = '' if fillfactor is None else f' WITH (fillfactor={fillfactor})' where = '' if conditions is None else f' WHERE {conditions}' - return f'CREATE INDEX{options} planet_osm_{table}_{name} ON planet_osm_{table} USING GIST ({function}){storage}{where};'.replace('\n', ' ') + return f'CREATE INDEX{options} planet_osm_{table}_{name} ON planet_osm_{table} USING {type} ({function}){storage}{where};'.replace('\n', ' ') def parse(index_function): with open(os.path.join(os.path.dirname(__file__), '../indexes.yml')) as yaml_file: @@ -33,7 +33,7 @@ def parse(index_function): for table, data in sorted(indexes.items()): for name, definition in sorted(data.items()): - print(index_function(table, name, definition.get("function", "way"), definition["where"]), end=separator) + print(index_function(table, name, definition.get("function", "way"), definition.get("type", "GIST"), definition.get("where")), end=separator) # The same as parse, but for osm2pgsql-built indexes def osm2pgsql_parse(index_function): @@ -42,15 +42,16 @@ def osm2pgsql_parse(index_function): print(index_function('polygon', 'way_idx', 'way', None), end=separator) print(index_function('roads', 'way_idx', 'way', None), end=separator) -def generate_statement(table, name, function, where): - return index_statement(table, name, function, where, args.concurrent, args.notexist, args.fillfactor) +def generate_statement(table, name, function, type, where): + return index_statement(table, name, function, type, where, args.concurrent, args.notexist, args.fillfactor) -def generate_reindex_statement(table, name, function, where): +def generate_reindex_statement(table, name, function, type, where): if not args.concurrent: return f'REINDEX planet_osm_{table}_{name};' else: # Rebuilding indexes concurrently requires making a new index, dropping the old one, and renaming. - return f'ALTER INDEX planet_osm_{table}_{name} RENAME TO planet_osm_{table}_{name}_old; {generate_statement(table, name, function, where)} + DROP INDEX planet_osm_{table}_{name}_old;' + return (f'ALTER INDEX planet_osm_{table}_{name} RENAME TO planet_osm_{table}_{name}_old; {generate_statement(table, name, function, type, where)} ' + f'DROP INDEX planet_osm_{table}_{name}_old;') print('-- These are indexes for rendering performance with OpenStreetMap Carto.', end=separator) print('-- This file is generated with {}'.format(' '.join(sys.argv)), end=separator) diff --git a/scripts/lua/test.lua b/scripts/lua/test.lua index 9f7962fc48..4ae1b79c48 100644 --- a/scripts/lua/test.lua +++ b/scripts/lua/test.lua @@ -4,22 +4,124 @@ This file is part of OpenStreetMap Carto and used for validating the Lua tag tra Run it with lua test.lua ]] -require ("openstreetmap-carto") +--- Utility function to do a deep compare +-- (C) Anonymous on snippets.luacode.org, MIT license +function deepcompare(t1,t2) + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false end + -- non-table types can be directly compared + if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end ---- compare two tables. --- @param t1 A table --- @param t2 A table --- @return true or false -function equaltables (t1,t2) - for k, v in pairs(t1) do - if t2[k] ~= v then return false end + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not deepcompare(v1,v2) then return false end end - for k, v in pairs(t2) do - if t1[k] ~= v then return false end + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if v1 == nil or not deepcompare(v1,v2) then return false end end return true end +--- See http://lua-users.org/wiki/CopyTable +function deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key)] = deepcopy(orig_value) + end + setmetatable(copy, deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + + +-- Before testing we need to mock the supplied osm2pgsql object +osm2pgsql = { srid = 3857, stage = 1 } +-- +local table_definitions = {} +local table_contents = {} + +function osm2pgsql.define_table(definition) + table_definitions[definition.name] = definition + table_contents[definition.name] = {} + return {add_row = function(self, obj) table.insert(table_contents[definition.name], obj) end} +end + +function osm2pgsql.way_member_ids(relation) + local members = {} + if relation.members ~= nil then + for _, member in ipairs(relation.members) do + if member.type == 'w' then + table.insert(members, member.ref) + end + end + end + return members +end +require ("openstreetmap-carto") + +print("TESTING: define_table") + +assert(deepcompare(table_definitions.planet_osm_point.ids, { type = 'node', id_column = 'osm_id' }), "planet_osm_point id column") +assert(deepcompare(table_definitions.planet_osm_point.columns[1], { column = 'way', type = 'point' }), "planet_osm_point way column") +assert(deepcompare(table_definitions.planet_osm_point.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_point tags column") +assert(deepcompare(table_definitions.planet_osm_point.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_point layer column") +assert(deepcompare(table_definitions.planet_osm_point.columns[4], { column = 'access', type = 'text' }), "planet_osm_point access column") + +assert(deepcompare(table_definitions.planet_osm_line.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_line id column") +assert(deepcompare(table_definitions.planet_osm_line.columns[1], { column = 'way', type = 'linestring' }), "planet_osm_line way column") +assert(deepcompare(table_definitions.planet_osm_line.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_line tags column") +assert(deepcompare(table_definitions.planet_osm_line.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_line layer column") +assert(deepcompare(table_definitions.planet_osm_line.columns[4], { column = 'z_order', type = 'int4' }), "planet_osm_line z_order column") +assert(deepcompare(table_definitions.planet_osm_line.columns[5], { column = 'access', type = 'text' }), "planet_osm_line access column") + +assert(deepcompare(table_definitions.planet_osm_transport_line.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_transport_line id column") +assert(deepcompare(table_definitions.planet_osm_transport_line.columns[1], { column = 'way', type = 'linestring' }), "planet_osm_transport_line way column") +assert(deepcompare(table_definitions.planet_osm_transport_line.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_transport_line tags column") +assert(deepcompare(table_definitions.planet_osm_transport_line.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_transport_line layer column") +assert(deepcompare(table_definitions.planet_osm_transport_line.columns[4], { column = 'z_order', type = 'int4' }), "planet_osm_transport_line z_order column") +assert(deepcompare(table_definitions.planet_osm_transport_line.columns[5], { column = 'access', type = 'text' }), "planet_osm_transport_line access column") + +assert(deepcompare(table_definitions.planet_osm_roads.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_roads id column") +assert(deepcompare(table_definitions.planet_osm_roads.columns[1], { column = 'way', type = 'linestring' }), "planet_osm_roads way column") +assert(deepcompare(table_definitions.planet_osm_roads.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_roads tags column") +assert(deepcompare(table_definitions.planet_osm_roads.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_roads layer column") +assert(deepcompare(table_definitions.planet_osm_roads.columns[4], { column = 'z_order', type = 'int4' }), "planet_osm_roads z_order column") +assert(deepcompare(table_definitions.planet_osm_roads.columns[5], { column = 'access', type = 'text' }), "planet_osm_roads access column") + +assert(deepcompare(table_definitions.planet_osm_polygon.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_polygon id column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[1], { column = 'way', type = 'geometry' }), "planet_osm_polygon way column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_polygon tags column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_polygon layer column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[4], { column = 'z_order', type = 'int4' }), "planet_osm_polygon z_order column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[5], { column = 'way_area', type = 'area' }), "planet_osm_polygon way_area column") +assert(deepcompare(table_definitions.planet_osm_polygon.columns[6], { column = 'access', type = 'text' }), "planet_osm_polygon access column") + +assert(deepcompare(table_definitions.planet_osm_transport_polygon.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_transport_polygon id column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[1], { column = 'way', type = 'geometry' }), "planet_osm_transport_polygon way column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[2], { column = 'tags', type = 'hstore' }), "planet_osm_transport_polygon tags column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[3], { column = 'layer', type = 'int4' }), "planet_osm_transport_polygon layer column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[4], { column = 'z_order', type = 'int4' }), "planet_osm_transport_polygon z_order column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[5], { column = 'way_area', type = 'area' }), "planet_osm_transport_polygon way_area column") +assert(deepcompare(table_definitions.planet_osm_transport_polygon.columns[6], { column = 'access', type = 'text' }), "planet_osm_transport_polygon access column") + +assert(deepcompare(table_definitions.planet_osm_route.ids, { type = 'relation', id_column = 'osm_id' }), "planet_osm_route id column") +assert(deepcompare(table_definitions.planet_osm_route.columns[1], { column = 'member_id', type = 'int8' }), "planet_osm_route member_id column") +assert(deepcompare(table_definitions.planet_osm_route.columns[2], { column = 'member_position', type = 'int4' }), "planet_osm_route member_position column") +assert(deepcompare(table_definitions.planet_osm_route.columns[3], { column = 'tags', type = 'hstore' }), "planet_osm_route tags column") +assert(deepcompare(table_definitions.planet_osm_route.columns[4], { column = 'route', type = 'text' }), "planet_osm_route route column") + +assert(deepcompare(table_definitions.planet_osm_admin.ids, { type = 'way', id_column = 'osm_id' }), "planet_osm_admin id column") +assert(deepcompare(table_definitions.planet_osm_admin.columns[1], { column = 'way', type = 'linestring' }), "planet_osm_admin way column") +assert(deepcompare(table_definitions.planet_osm_admin.columns[2], { column = 'admin_level', type = 'int2' }), "planet_osm_admin admin_level column") +assert(deepcompare(table_definitions.planet_osm_admin.columns[3], { column = 'multiple_relations', type = 'boolean' }), "planet_osm_admin multiple_relations column") + print("TESTING: z_order") assert(z_order({}) == nil, "test failed: no tags") @@ -38,6 +140,9 @@ assert(z_order({highway="trunk"}) > z_order({highway="primary"}) , "test failed: assert(z_order({highway="primary"}) > z_order({highway="secondary"}) , "test failed: primary > secondary") assert(z_order({highway="secondary"}) > z_order({highway="tertiary"}) , "test failed: secondary > tertiary") +assert(z_order({highway="tertiary_link"}) > z_order({highway="busway"}), "test failed: tertiary_link > busway") +assert(z_order({highway="busway"}) > z_order({highway="service"}), "test failed: busway > service") + assert(z_order({highway="construction"}) == 33 , "test failed: highway=construction") assert(z_order({highway="construction", construction="motorway"}) == 38 , "test failed: highway=construction construction=motorway") assert(z_order({highway="construction", construction="motorway", railway="rail"}) == 440, "test failed: construction motorway + rail") @@ -47,142 +152,423 @@ assert(z_order({highway="construction", construction="foo"}) == 33 , "test faile assert(z_order({highway="motorway", construction="service"}) == 380 , "test failed: highway=construction + construction=service") print("TESTING: roads") -assert(roads({}) == 0, "test failed: no tags") -assert(roads({foo="bar"}) == 0, "test failed: other tags") -assert(roads({highway="motorway"}) == 1, "test failed: motorway") -assert(roads({railway="rail"}) == 1, "test failed: rail") -assert(roads({highway="residential", railway="rail"}) == 1, "test failed: rail+residential") -assert(roads({railway="turntable"}) == 0, "test failed: rail=turntable") -assert(roads({railway="rail", service="spur"}) == 0, "test failed: rail SSY") -assert(roads({railway="rail", service="main"}) == 1, "test failed: rail non-SSY") -assert(roads({boundary="administrative"}) == 1, "test failed: boundary administrative") +assert(not roads({}), "test failed: no tags") +assert(not roads({foo="bar"}), "test failed: other tags") +assert(roads({highway="motorway"}), "test failed: motorway") +assert(roads({railway="rail"}), "test failed: rail") +assert(roads({highway="residential", railway="rail"}), "test failed: rail+residential") +assert(not roads({railway="turntable"}), "test failed: rail=turntable") +assert(not roads({railway="rail", service="spur"}), "test failed: rail SSY") +assert(roads({railway="rail", service="main"}), "test failed: rail non-SSY") +assert(roads({boundary="administrative"}), "test failed: boundary administrative") print("TESTING: isarea") -assert(isarea({}) == 0, "test failed: no tags") -assert(isarea({foo = "bar"}) == 0, "test failed: random tag") -assert(isarea({area = "yes"}) == 1, "test failed: explicit area") -assert(isarea({area = "no"}) == 0, "test failed: explicit not area") -assert(isarea({area = "no", landuse = "forest"}) == 0, "test failed: explicit not area with polygon tag") -assert(isarea({leisure = "track"}) == 0, "test failed: leisure=track") -assert(isarea({area = "yes", leisure = "track"}) == 1, "test failed: leisure=track with area tag") -assert(isarea({waterway = "river"}) == 0, "test failed: river") -assert(isarea({waterway = "riverbank"}) == 1, "test failed: river") -assert(isarea({highway = "services"}) == 1, "test failed: river") -assert(isarea({natural="cliff"}) == 0, "test failed: cliff") -- issue #3084 -assert(isarea({building = "no"}) == 0, "test failed: building=no") -assert(isarea({building = "no", area = "yes"}) == 1, "test failed: building=no with area tag") -assert(isarea({building = "no", landuse = "forest"}) == 1, "test failed: building=no with other area tag") - -print("TESTING: filter_tags_generic") -assert(({filter_tags_generic({})})[1] == 1, "Untagged filter") -assert(equaltables(({filter_tags_generic({})})[2], {}), "Untagged tags") -assert(({filter_tags_generic({note="foo"})})[1] == 1, "deleted filter") -assert(equaltables(({filter_tags_generic({note="foo"})})[2], {}), "deleted tags") -assert(({filter_tags_generic({foo="bar"})})[1] == 0, "single tag filter") -assert(equaltables(({filter_tags_generic({foo="bar"})})[2], {foo="bar"}), "single tag tags") -assert(({filter_tags_generic({foo="bar", note="baz"})})[1] == 0, "tag + deleted tag filter") -assert(equaltables(({filter_tags_generic({foo="bar", note="baz"})})[2], {foo="bar"}), "tag + deleted tags") -assert(({filter_tags_generic({["note:xx"]="foo"})})[1] == 1, "wildcard deleted filter") -assert(equaltables(({filter_tags_generic({["note:xx"]="foo"})})[2], {}), "wildcard deleted tags") -assert(({filter_tags_generic({["note:xx"]="foo", foo="bar"})})[1] == 0, "wildcard deleted + tag filter") -assert(equaltables(({filter_tags_generic({["note:xx"]="foo", foo="bar"})})[2], {foo="bar"}), "wildcard deleted + tag tags") - -assert(({filter_tags_generic({["foo:note:xx"]="foo"})})[1] == 0, "prefix later in tag filter") -assert(equaltables(({filter_tags_generic({["foo:note:xx"]="foo"})})[2], {["foo:note:xx"]="foo"}), "prefix later in tag tags") - -print("TESTING: filter_tags_relation_member") - ---- Tests filter_tags_relation_member against expected values --- @param keyvalues OSM tags, after processing by relation transform --- @param keyvaluemembers OSM tags of relation members, after processing by way transform --- @param filter expected filter result --- @param cols expected cols result --- @param member_superseded expected member_superseded result --- @param boundary expected boundary result --- @param polygon expected polygon result --- @param roads expected roads result -local function check_rel_member(keyvalues, keyvaluemembers, filter, cols, member_superseded, boundary, polygon, roads) - - local i = 0 - for _ in pairs(keyvaluemembers) do - i = i + 1 - end +assert(not isarea({}), "test failed: no tags") +assert(not isarea({foo = "bar"}), "test failed: random tag") +assert(isarea({area = "yes"}), "test failed: explicit area") +assert(not isarea({area = "no"}), "test failed: explicit not area") +assert(not isarea({area = "no", landuse = "forest"}), "test failed: explicit not area with polygon tag") +assert(not isarea({leisure = "track"}), "test failed: leisure=track") +assert(isarea({area = "yes", leisure = "track"}), "test failed: leisure=track with area tag") +assert(not isarea({waterway = "river"}), "test failed: river") +assert(isarea({waterway = "riverbank"}), "test failed: river") +assert(isarea({highway = "services"}), "test failed: river") +assert(not isarea({natural="cliff"}), "test failed: cliff") -- issue #3084 +assert(not isarea({building = "no"}), "test failed: building=no") +assert(isarea({building = "no", area = "yes"}), "test failed: building=no with area tag") +assert(isarea({building = "no", landuse = "forest"}), "test failed: building=no with other area tag") - local actual_filter, actual_cols, actual_member_superseded, actual_boundary, actual_polygon, actual_roads - = filter_tags_relation_member(keyvalues, keyvaluemembers, nil, i) +print("TESTING: layer") +assert(layer("foo") == nil, "non-numeric layer") +assert(layer("0") == "0", "0 layer") +assert(layer("3") == "3", "3 layer") +assert(layer("-3") == "-3", "-3 layer") +assert(layer("300") == nil, "large layer") +assert(layer("3.5") == nil, "non-integer layer") - if actual_filter ~= filter then - print("filter mismatch") - return false - end - if not equaltables(actual_cols, cols) then - print("cols mismatch") - return false - end - if not equaltables(actual_member_superseded, member_superseded) then - print("member_superseded mismatch, actual table was") - for i, v in ipairs(actual_member_superseded) do - print(i, v) +print("TESTING: admin_level") +assert(admin_level("foo") == nil, "non-numeric admin_level") +assert(admin_level("0") == nil, "0 admin_level") +assert(admin_level("3") == "3", "3 admin_level") +assert(admin_level("-3") == nil, "-3 admin_level") +assert(admin_level("300") == nil, "large admin_level") +assert(admin_level("3.5") == nil, "non-integer admin_level") + +print("TESTING: clean_tags") +assert(clean_tags({}), "Untagged") + +tags={odbl = "yes"} +assert(clean_tags(tags), "delete tag return") +assert(deepcompare(tags, {}), "delete tags") + +tags={['source:note'] = "yes"} +assert(clean_tags(tags), "delete wildcard tag return") +assert(deepcompare(tags, {}), "delete wildcard tags") + +tags={natural = "tree"} +assert(not clean_tags(tags), "no delete tags return") +assert(deepcompare(tags, {natural = "tree"}), "no delete tags") + +tags={natural = "tree", odbl = "yes"} +assert(not clean_tags(tags), "mixed tags return") +assert(deepcompare(tags, {natural = "tree"}), "mixed tags") + +print("TESTING: add_point") +table_contents.planet_osm_point = {} +add_point({tags = {natural = "tree"}}) +assert(deepcompare(table_contents.planet_osm_point[1], {natural = "tree", tags = {}}), "Tag with column") + +table_contents.planet_osm_point = {} +add_point({tags = {natural = "tree", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_point[1], {natural = "tree", tags = {foo = "bar"}}), "Tag with column + hstore") + +table_contents.planet_osm_point = {} +add_point({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_point[1], {tags = {foo = "bar"}}), "hstore only") + +print("TESTING: add_line") +table_contents.planet_osm_line = {} +add_line({tags = {highway = "road"}}) +assert(deepcompare(table_contents.planet_osm_line[1], {highway = "road", z_order = 330, tags = {}, way = { create = 'line', split_at = 100000 }}), "Tag with column") + +table_contents.planet_osm_line = {} +add_line({tags = {highway = "road", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_line[1], {highway = "road", z_order = 330, tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore") + +table_contents.planet_osm_line = {} +add_line({tags = {highway = "road", foo = "bar", layer = "5"}}) +assert(deepcompare(table_contents.planet_osm_line[1], {highway = "road", z_order = 330, layer = "5", tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore + layer") + +table_contents.planet_osm_line = {} +add_line({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_line[1], {tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "hstore only") + +print("TESTING: add_transport_line") +table_contents.planet_osm_transport_line = {} +add_transport_line({tags = {highway = "road"}}) +assert(deepcompare(table_contents.planet_osm_transport_line[1], {highway = "road", z_order = 330, tags = {}, way = { create = 'line', split_at = 100000 }}), "Tag with column") + +table_contents.planet_osm_transport_line = {} +add_transport_line({tags = {highway = "road", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_transport_line[1], {highway = "road", z_order = 330, tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore") + +-- The z_order check where add_transport_line is called prevents this case from being hit in practice +table_contents.planet_osm_transport_line = {} +add_transport_line({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_transport_line[1], {tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "hstore only") + +table_contents.planet_osm_transport_line = {} +add_transport_line({tags = {highway = "road", foo = "bar", layer = "5"}}) +assert(deepcompare(table_contents.planet_osm_transport_line[1], {highway = "road", z_order = 330, layer = "5", tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore + layer") + +print("TESTING: add_roads") +table_contents.planet_osm_roads = {} +add_roads({tags = {highway = "road"}}) +assert(deepcompare(table_contents.planet_osm_roads[1], {highway = "road", z_order = 330, tags = {}, way = { create = 'line', split_at = 100000 }}), "Tag with column") + +table_contents.planet_osm_roads = {} +add_roads({tags = {highway = "road", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_roads[1], {highway = "road", z_order = 330, tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore") + +table_contents.planet_osm_roads = {} +add_roads({tags = {highway = "road", layer = "5", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_roads[1], {highway = "road", z_order = 330, layer = "5", tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "Tag with column + hstore") + +table_contents.planet_osm_roads = {} +add_roads({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_roads[1], {tags = {foo = "bar"}, way = { create = 'line', split_at = 100000 }}), "hstore only") + +print("TESTING: add_polygon") +table_contents.planet_osm_polygon = {} +add_polygon({tags = {natural = "wood"}}) +assert(deepcompare(table_contents.planet_osm_polygon[1], {natural = "wood", tags = {}, way = { create = 'area', split_at = nil }}), "Tag with column") + +table_contents.planet_osm_polygon = {} +add_polygon({tags = {natural = "wood", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_polygon[1], {natural = "wood", tags = {foo = "bar"}, way = { create = 'area', split_at = nil }}), "Tag with column + hstore") + +table_contents.planet_osm_polygon = {} +add_polygon({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_polygon[1], {tags = {foo = "bar"}, way = { create = 'area', split_at = nil }}), "hstore only") + +print("TESTING: add_transport_polygon") +table_contents.planet_osm_transport_polygon = {} +add_transport_polygon({tags = {highway = "service"}}) +assert(deepcompare(table_contents.planet_osm_transport_polygon[1], {highway = "service", z_order = 150, tags = {}, way = { create = 'area' }}), "Tag with column") + +table_contents.planet_osm_transport_polygon = {} +add_transport_polygon({tags = {highway = "service", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_transport_polygon[1], {highway = "service", z_order = 150, tags = {foo = "bar"}, way = { create = 'area' }}), "Tag with column + hstore") + +table_contents.planet_osm_transport_polygon = {} +add_transport_polygon({tags = {foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_transport_polygon[1], {tags = {foo = "bar"}, way = { create = 'area', split_at = nil }}), "hstore only") + +print("TESTING: add_route") +table_contents.planet_osm_route = {} +add_route({tags = {route = "unicycle"}, members = {{type = 'w', ref = 1234, role = 'foo'}}}) +assert(deepcompare(table_contents.planet_osm_route[1], {route = "unicycle", member_id = 1234, member_position = 1, tags = {}}), "Route with 1 way") + +table_contents.planet_osm_route = {} +add_route({tags = {route = "unicycle"}, members = {{type = 'w', ref = 1234, role = 'foo'}, {type = 'n', ref = 1235, role = 'foo'}, }}) +assert(deepcompare(table_contents.planet_osm_route[1], {route = "unicycle", member_id = 1234, member_position = 1, tags = {}}), "Route with 1 way + 1 node") + +table_contents.planet_osm_route = {} +add_route({tags = {route = "unicycle"}, members = {{type = 'w', ref = 1234, role = 'foo'}, {type = 'w', ref = 1235, role = 'foo'}}}) +assert(deepcompare(table_contents.planet_osm_route[1], {route = "unicycle", member_id = 1234, member_position = 1, tags = {}}), "Route with 2 ways") +assert(deepcompare(table_contents.planet_osm_route[2], {route = "unicycle", member_id = 1235, member_position = 2, tags = {}}), "Route with 2 ways") + +table_contents.planet_osm_route = {} +add_route({tags = {route = "unicycle", network = "clown"}, members = {{type = 'w', ref = 1234, role = 'foo'}}}) +assert(deepcompare(table_contents.planet_osm_route[1], {route = "unicycle", network="clown", member_id = 1234, member_position = 1, tags = {}}), "Route with network") + +print("TESTING: osm2pgsql.process_node") +table_contents.planet_osm_point = {} +osm2pgsql.process_node({tags = {}}) +assert(deepcompare(table_contents.planet_osm_point, {}), "Untagged") + +osm2pgsql.process_node({tags = {odbl = "yes"}}) +assert(deepcompare(table_contents.planet_osm_point, {}), "Deleted tag") + +-- By running process_node then manually running add_point the first and second entry +-- in the output tables should be the same. A cleaner way would be to mock the add_* functions +-- and then check if they are called. +osm2pgsql.process_node({tags = {natural = "tree", foo = "bar"}}) +add_point({tags = {natural = "tree", foo = "bar"}}) +assert(deepcompare(table_contents.planet_osm_point[1], table_contents.planet_osm_point[2]), "Accepted tag") + +print("TESTING: osm2pgsql.process_way") +table_contents.planet_osm_line = {} +table_contents.planet_osm_transport_line = {} +table_contents.planet_osm_roads = {} +table_contents.planet_osm_polygon = {} +table_contents.planet_osm_transport_polygon = {} + +osm2pgsql.process_way({tags = {}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "Untagged line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Untagged transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Untagged roads") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Untagged polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Untagged transport_polygon") + +osm2pgsql.process_way({tags = {odbl = "yes"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "Deleted tag line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Deleted tag transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Deleted tag roads") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Deleted tag polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Deleted tag transport_polygon") + +osm2pgsql.process_way({is_closed = false, tags = { highway = "road"}}) +-- Add something to compare against. We know add_line and add_transport_line are okay since we tested them above +add_line({tags = { highway = "road"}}) +add_transport_line({tags = { highway = "road"}}) +assert(deepcompare(table_contents.planet_osm_line[1], table_contents.planet_osm_line[2]), "Line tag, open way, line table") +assert(deepcompare(table_contents.planet_osm_transport_line[1], table_contents.planet_osm_transport_line[2]), "Line tag, open way, transport_line table") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Line tag, open way, roads table") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Line tag, open way, polygon table") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Line tag, open way, transport_polygon table") +table_contents.planet_osm_line = {} +table_contents.planet_osm_transport_line = {} + +osm2pgsql.process_way({is_closed = true, tags = { highway = "motorway"}}) +add_line({tags = {highway = "motorway"}}) +add_transport_line({tags = { highway = "motorway"}}) +add_roads({tags = {highway = "motorway"}}) +assert(deepcompare(table_contents.planet_osm_line[1], table_contents.planet_osm_line[2]), "Line tag, closed way, line table") +assert(deepcompare(table_contents.planet_osm_transport_line[1], table_contents.planet_osm_transport_line[2]), "Line tag, closed way, transport_line table") +assert(deepcompare(table_contents.planet_osm_roads[1], table_contents.planet_osm_roads[2]), "Line tag, closed way, roads table") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Line tag, closed way, polygon table") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Line tag, closed way, transport_polygon table") +table_contents.planet_osm_line = {} +table_contents.planet_osm_transport_line = {} +table_contents.planet_osm_roads = {} + +osm2pgsql.process_way({is_closed = false, tags = { natural = "wood"}}) +add_line({tags = {natural = "wood"}}) +assert(deepcompare(table_contents.planet_osm_line[1], table_contents.planet_osm_line[1]), "area tag, open way, line table") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "area tag, open way, transport_line table") +assert(deepcompare(table_contents.planet_osm_roads, {}), "area tag, open way, roads table") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "area tag, open way, polygon table") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "area tag, open way, transport_polygon table") +table_contents.planet_osm_line = {} + +osm2pgsql.process_way({is_closed = true, tags = { natural = "wood"}}) +add_polygon({tags = {natural = "wood"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "area tag, closed way, line table") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "area tag, closed way, transport_line table") +assert(deepcompare(table_contents.planet_osm_roads, {}), "area tag, closed way, roads table") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "area tag, closed way, polygon table") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "area tag, closed way, transport_polygon table") +table_contents.planet_osm_polygon = {} + +osm2pgsql.process_way({is_closed = true, tags = { railway = "platform", area = "yes"}}) +add_polygon({tags = {railway = "platform", area = "yes"}}) +add_transport_polygon({tags = {railway = "platform", area = "yes"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "area tag, closed way, line table") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "area tag, closed way, transport_line table") +assert(deepcompare(table_contents.planet_osm_roads, {}), "area tag, closed way, roads table") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "area tag, closed way, polygon table") +assert(deepcompare(table_contents.planet_osm_transport_polygon[1], table_contents.planet_osm_transport_polygon[2]), "area tag, closed way, transport_polygon table") + +print("TESTING: osm2pgsql.process_relation") +table_contents.planet_osm_line = {} +table_contents.planet_osm_transport_line = {} +table_contents.planet_osm_roads = {} +table_contents.planet_osm_polygon = {} +table_contents.planet_osm_transport_polygon = {} +table_contents.planet_osm_route = {} + +osm2pgsql.process_relation({tags = {}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "Untagged line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Untagged transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Untagged roads") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Untagged polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Untagged transport_polygon") + +osm2pgsql.process_relation({tags = {odbl = "yes"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "Deleted tag line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Deleted tag transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Deleted tag roads") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Deleted tag polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Deleted tag transport_polygon") + +osm2pgsql.process_relation({tags = {type = "multipolygon", odbl = "yes"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "Deleted tag line with type") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Deleted tag transport_line with type") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Deleted tag roads with type") +assert(deepcompare(table_contents.planet_osm_polygon, {}), "Deleted tag polygon with type") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Deleted tag transport_polygon with type") + +osm2pgsql.process_relation({tags = {type = "boundary", boundary = "foo"}}) +add_line({tags = {boundary = "foo"}}) +add_polygon({tags = {boundary = "foo"}}) +assert(deepcompare(table_contents.planet_osm_line[1], table_contents.planet_osm_line[2]), "Boundary line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "Boundary transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "Boundary roads") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "Boundary polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "Boundary transport_polygon") +table_contents.planet_osm_line = {} +table_contents.planet_osm_polygon = {} + +osm2pgsql.process_relation({tags = {type = "boundary", boundary = "administrative"}}) +add_line({tags = {boundary = "administrative"}}) +add_roads({tags = {boundary = "administrative"}}) +add_polygon({tags = {boundary = "administrative"}}) +assert(deepcompare(table_contents.planet_osm_line[1], table_contents.planet_osm_line[2]), "admin boundary line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "admin boundary transport_line") +assert(deepcompare(table_contents.planet_osm_roads[1], table_contents.planet_osm_roads[2]), "admin boundary roads") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "admin boundary polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "admin boundary transport_polygon") +table_contents.planet_osm_line = {} +table_contents.planet_osm_polygon = {} +table_contents.planet_osm_roads = {} + +osm2pgsql.process_relation({tags = {type = "multipolygon", natural = "tree"}}) +add_polygon({tags = {natural = "tree"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "MP line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "MP transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "MP roads") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "MP boundary polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon, {}), "MP transport_polygon") +table_contents.planet_osm_polygon = {} + +osm2pgsql.process_relation({tags = {type = "multipolygon", highway = "motorway"}}) +add_polygon({tags = {highway = "motorway"}}) +add_transport_polygon({tags = {highway = "motorway"}}) +assert(deepcompare(table_contents.planet_osm_line, {}), "MP highway line") +assert(deepcompare(table_contents.planet_osm_transport_line, {}), "MP highway transport_line") +assert(deepcompare(table_contents.planet_osm_roads, {}), "MP highway roads") +assert(deepcompare(table_contents.planet_osm_polygon[1], table_contents.planet_osm_polygon[2]), "MP highway polygon") +assert(deepcompare(table_contents.planet_osm_transport_polygon[1], table_contents.planet_osm_transport_polygon[2]), "MP highway transport_polygon") +table_contents.planet_osm_polygon = {} +table_contents.planet_osm_transport_polygon = {} + +-- Testing of phase 2 + +--[[ + This sets up an + 1. admin_level=2 relation with ways 1, 2, 3, 5, 7, 8, 9, 10 + 2. admin_level=2 relation with ways 1, 2, 6, 7, 8 + 3. admin_level=4 relation with ways 1, 3, 4 + + The ways have correct tags except for 7 and 8. Way 9 also has road tags. +]] + +-- Currently in phase 1 + +local test_ways = { + { id = 1, tags = { boundary = "administrative", admin_level = "2"} }, + { id = 2, tags = { boundary = "administrative", admin_level = "2"} }, + { id = 3, tags = { boundary = "administrative", admin_level = "2"} }, + { id = 4, tags = { boundary = "administrative", admin_level = "4"} }, + { id = 5, tags = { boundary = "administrative", admin_level = "2"} }, + { id = 6, tags = { boundary = "administrative", admin_level = "2"} }, + { id = 7, tags = { boundary = "administrative", admin_level = "3"} }, -- incorrect tags + { id = 8, tags = { } }, -- incorrect tags + { id = 9, tags = { boundary = "administrative", admin_level = "2", highway = "road"} }, + { id = 10, tags = { boundary = "administrative", admin_level = "2", closure_segment = "yes"} } -- closure segments are used on the antimeridian +} +-- add another way that isn't part of a relation +local test_relations = { + { id = 1, + tags = {type = "boundary", boundary = "administrative", admin_level = "2"}, + members = { {type = 'w', ref = 1, role = "outer"}, + {type = 'w', ref = 2, role = "outer"}, + {type = 'w', ref = 3, role = "outer"}, + {type = 'w', ref = 5, role = "outer"}, + {type = 'w', ref = 7, role = "outer"}, + {type = 'w', ref = 8, role = "outer"}, + {type = 'w', ref = 9, role = "outer"}, + {type = 'w', ref = 10, role = "outer"} } }, + { id = 2, tags = {type = "boundary", boundary = "administrative", admin_level = "2"}, + members = { {type = 'w', ref = 1, role = "outer"}, + {type = 'w', ref = 2, role = "outer"}, + {type = 'w', ref = 6, role = "outer"}, + {type = 'w', ref = 7, role = "outer"}, + {type = 'w', ref = 8, role = "outer"} } }, + { id = 3, tags = {type = "boundary", boundary = "administrative", admin_level = "4"}, + members = { {type = 'w', ref = 1, role = "outer"}, + {type = 'w', ref = 3, role = "outer"}, + {type = 'w', ref = 4, role = "outer"} } } +} +for _, way in ipairs(test_ways) do + osm2pgsql.process_way(way) +end + +local pending_ways = {} + +function table.clone(org) + return {table.unpack(org)} +end + +for _, relation in ipairs(test_relations) do + osm2pgsql.process_relation(deepcopy(relation)) + local ret = osm2pgsql.select_relation_members(relation) + if ret ~= nil then + for _, ref in ipairs(ret.ways) do + pending_ways[ref] = true end - return false - end - if actual_boundary ~= boundary then - print("boundary mismatch") - return false - end - if actual_polygon ~= polygon then - print("polygon mismatch") - return false end - if actual_roads ~= roads then - print("roads mismatch") - return false +end + +osm2pgsql.stage = 2 + +-- This process should really delete from the planet_osm_line table, but there's no way to do that with the data we're collecting. +-- osm2pgsql itself has access to more data +for _, way in ipairs(test_ways) do + if pending_ways[way.id] then + osm2pgsql.process_way(way) end - return true end -assert(check_rel_member({}, {}, 1, {}, {}, 0, 0, 0), "test failed: untagged memberless relation") -assert(check_rel_member({}, {{}}, 1, {}, {0}, 0, 0, 0), "test failed: untagged relation") - -assert(check_rel_member({type="multipolygon"}, {{}}, 1, {}, {0}, 0, 0, 0), - "test failed: untagged MP") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{}}, 0, {foo="bar"}, {0}, 0, 1, 0), - "test failed: MP with tag") - --- New-style MPs -assert(check_rel_member({type="multipolygon", foo="bar"}, {{},{}}, 0, {foo="bar"}, {0,0}, 0, 1, 0), - "test failed: MP with tag, two ways") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{baz="qax"}}, 0, {foo="bar"}, {0}, 0, 1, 0), - "test failed: MP with tag, way with different tag") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{baz="qax"}, {}}, 0, {foo="bar"}, {0,0}, 0, 1, 0), - "test failed: MP with tag, way with different tag + untagged way") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{foo="bar"}}, 0, {foo="bar"}, {0}, 0, 1, 0), - "test failed: MP with tag, way with same tag") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{foo="bar"},{}}, 0, {foo="bar"}, {0,0}, 0, 1, 0), - "test failed: MP with tag, way with same tag + untagged way") -assert(check_rel_member({type="multipolygon", foo="bar"}, {{foo="bar"}, {baz="qax"}}, 0, {foo="bar"}, {0,0}, 0, 1, 0), - "test failed: MP with tag, way with same tag") - --- Old-style MPs -assert(check_rel_member({type="multipolygon"}, {{foo="bar"}}, 1, {}, {0}, 0, 0, 0), - "test failed: MP w/o tag, way with tag") -assert(check_rel_member({type="multipolygon"}, {{foo="bar"}, {}}, 1, {}, {0,0}, 0, 0, 0), - "test failed: MP w/o tag, way with tag + untagged way") -assert(check_rel_member({type="multipolygon"}, {{foo="bar"}, {baz="qax"}}, 1, {}, {0,0}, 0, 0, 0), - "test failed: MP w/o tag, way with tag + way with other tag") - --- Boundary relations -assert(check_rel_member({type="boundary"}, {{}}, 1, {}, {0}, 0, 0, 0), - "test failed: untagged boundary") -assert(check_rel_member({type="boundary", boundary="administrative"}, {{}}, 0, {boundary="administrative"}, {0}, 1, 0, 1), - "test failed: untagged boundary") -assert(check_rel_member({type="boundary", boundary="administrative"}, {{}}, 0, {boundary="administrative"}, {0}, 1, 0, 1), - "test failed: untagged boundary") -assert(check_rel_member({type="boundary", boundary="administrative"}, {{foo="bar"}}, 0, {boundary="administrative"}, {0}, 1, 0, 1), - "test failed: untagged boundary, tagged way") - --- Route relations -assert(check_rel_member({type="route"}, {{}}, 1, {}, {0}, 0, 0, 0), - "test failed: untagged route") -assert(check_rel_member({type="route", route="road"}, {{}}, 0, {route="road"}, {0}, 1, 0, 0), - "test failed: tagged route") +-- Because everything is done in a fixed order we can use that to figure out which row is which. This is important because the add_row method doesn't take in the osm_id, and osm2pgsql tracks it separately + +assert(deepcompare(table_contents.planet_osm_admin[1], {admin_level = 2, multiple_relations = true, way = {create = "line" } }), "row 1") +assert(deepcompare(table_contents.planet_osm_admin[2], {admin_level = 2, multiple_relations = true, way = {create = "line" } }), "row 2") +assert(deepcompare(table_contents.planet_osm_admin[3], {admin_level = 2, multiple_relations = false, way = {create = "line" } }), "row 3") +assert(deepcompare(table_contents.planet_osm_admin[4], {admin_level = 4, multiple_relations = false, way = {create = "line" } }), "row 4") +assert(deepcompare(table_contents.planet_osm_admin[5], {admin_level = 2, multiple_relations = false, way = {create = "line" } }), "row 5") +assert(deepcompare(table_contents.planet_osm_admin[6], {admin_level = 2, multiple_relations = false, way = {create = "line" } }), "row 6") +assert(deepcompare(table_contents.planet_osm_admin[7], {admin_level = 2, multiple_relations = true, way = {create = "line" } }), "row 7") +assert(deepcompare(table_contents.planet_osm_admin[8], {admin_level = 2, multiple_relations = true, way = {create = "line" } }), "row 8") +assert(deepcompare(table_contents.planet_osm_admin[9], {admin_level = 2, multiple_relations = false, way = {create = "line" } }), "row 9") diff --git a/scripts/test-queries.py b/scripts/test-queries.py index 64432987f8..8747e1004c 100755 --- a/scripts/test-queries.py +++ b/scripts/test-queries.py @@ -41,7 +41,7 @@ def main(): description="Test CartoCSS project queries against a database") - parser.add_argument("-d", "--database", action="store", default="gis", + parser.add_argument("-d", "--database", action="store", default="flex", help="Override database name to connect to") parser.add_argument("-H", "--host", action="store", help="Override database server host or socket directory") diff --git a/style/admin.mss b/style/admin.mss index 5645a070e9..e3941115ef 100644 --- a/style/admin.mss +++ b/style/admin.mss @@ -2,443 +2,256 @@ @admin-boundaries-narrow: #845283; // Lch(42,35,327) @admin-boundaries-wide: #a37da1; // Lch(57,25,327) -/* For performance reasons, the admin border layers are split into three groups -for low, middle and high zoom levels. -Three attachments are used, with minor borders before major ones, and the thin centerline last, to handle -overlapping borders correctly and allow each type to have a different level of opacity. -Overlapping borders are hidden by a white background line, rendered before each line. -Then all three layers are added to the rendering with comp-op: darken, so that the white lines will not show -*/ - #admin-low-zoom[zoom < 8], #admin-mid-zoom[zoom >= 8][zoom < 13], #admin-high-zoom[zoom >= 13] { - [admin_level = '2']::firstline { - [zoom >= 8] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 3; - } - [zoom >= 9] { background/line-width: 3.5; } - [zoom >= 10] { background/line-width: 4.5; } - [zoom >= 11] { background/line-width: 5.5; } - [zoom >= 12] { background/line-width: 6; } - [zoom >= 13] { background/line-width: 7; } - [zoom >= 14] { background/line-width: 8; } - } - [admin_level = '2']::wideline { - [zoom >= 4] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.2; - line-join: bevel; - line-color: @admin-boundaries; - line-width: 1.2; - } + [admin_level = 2][zoom >= 4] { + wide/line-join: bevel; + wide/line-color: @admin-boundaries; + wide/line-opacity: 0.5; + wide/line-width: 1.2; [zoom >= 5] { - background/line-width: 1.5; - line-width: 1.5; + wide/line-width: 1.5; } [zoom >= 6] { - background/line-width: 1.8; - line-width: 1.8; + wide/line-width: 1.8; } [zoom >= 7] { - background/line-width: 2.2; - line-width: 2.2; + wide/line-width: 2.2; } [zoom >= 8] { - background/line-width: 3; - line-width: 3; - } - [zoom >= 9] { - background/line-width: 3.5; - line-width: 3.5; - } - [zoom >= 10] { - background/line-width: 4.5; - line-color: @admin-boundaries-wide; - line-width: 4.5; - } - [zoom >= 11] { - background/line-width: 5.5; - line-width: 5; - } - [zoom >= 12] { - background/line-width: 6; - line-width: 6; - } - [zoom >= 13] { - background/line-width: 7; - line-width: 7; - } - [zoom >= 14] { - background/line-width: 8; - line-width: 8; - } - } - [admin_level = '2']::narrowline { - [zoom >= 8] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.6; + wide/line-width: 3; thin/line-join: bevel; thin/line-color: @admin-boundaries-narrow; - thin/line-width: 0.6; + thin/line-opacity: 0.6; + thin/line-width: 1.2; } [zoom >= 9] { - background/line-width: 0.8; + wide/line-width: 3.5; thin/line-width: 0.8; } [zoom >= 10] { - background/line-width: 1; + wide/line-color: @admin-boundaries-wide; + wide/line-width: 4.5; thin/line-width: 1; thin/line-dasharray: 18,1,4,1; + thin/line-clip: false; } [zoom >= 11] { - background/line-width: 1.2; + wide/line-width: 5; thin/line-width: 1.2; } [zoom >= 12] { - background/line-width: 1.4; + wide/line-width: 6; thin/line-width: 1.4; thin/line-dasharray: 27,1.5,6,1.5; } [zoom >= 13] { - background/line-width: 1.6; + wide/line-width: 7; thin/line-width: 1.6; } [zoom >= 14] { - background/line-width: 1.8; + wide/line-width: 8; thin/line-width: 1.8; thin/line-dasharray: 36,2,8,2; } } - [admin_level = '3']::firstline { - [zoom >= 8] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.8; - } - [zoom >= 9] { background/line-width: 2.5; } - [zoom >= 10] { background/line-width: 3.2; } - [zoom >= 11] { background/line-width: 4; } - [zoom >= 12] { background/line-width: 4.5; } - [zoom >= 13] { background/line-width: 5; } - [zoom >= 14] { background/line-width: 5.5; } - } - [admin_level = '3']::wideline { - [zoom >= 4] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.6; - line-join: bevel; - line-color: @admin-boundaries; - line-width: 0.6; - } + [admin_level = 3][zoom >= 4] { + wide/line-join: bevel; + wide/line-color: @admin-boundaries; + wide/line-opacity: 0.5; + wide/line-width: 0.6; [zoom >= 5] { - background/line-width: 0.8; - line-width: 0.8; + wide/line-width: 0.8; } [zoom >= 6] { - background/line-width: 1; - line-width: 1; + wide/line-width: 1.0; } [zoom >= 7] { - background/line-width: 1.2; - line-width: 1.2; + wide/line-width: 1.2; } [zoom >= 8] { - background/line-width: 1.8; - line-width: 1.8; + wide/line-width: 1.8; } [zoom >= 9] { - background/line-width: 2.5; - line-width: 2.5; + wide/line-width: 2.5; } [zoom >= 10] { - background/line-width: 3.2; - line-color: @admin-boundaries-wide; - line-width: 3.2; - } - [zoom >= 11] { - background/line-width: 4; - line-width: 4; - } - [zoom >= 12] { - background/line-width: 4.5; - line-width: 4.5; - } - [zoom >= 13] { - background/line-width: 5; - line-width: 5; - } - [zoom >= 14] { - background/line-width: 5.5; - line-width: 5.5; - } - } - [admin_level = '3']::narrowline { - [zoom >= 10] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.8; + wide/line-color: @admin-boundaries-wide; + wide/line-width: 3.2; thin/line-join: bevel; thin/line-color: @admin-boundaries-narrow; + thin/line-opacity: 0.6; thin/line-width: 0.8; thin/line-dasharray: 12,2,1.5,2; + thin/line-clip: false; } [zoom >= 11] { - background/line-width: 1; - thin/line-width: 1; + wide/line-width: 4; + thin/line-width: 1.0; } [zoom >= 12] { - background/line-width: 1.2; + wide/line-width: 4.5; thin/line-width: 1.2; thin/line-dasharray: 17,3,2,3; } [zoom >= 13] { - background/line-width: 1.4; + wide/line-width: 5; thin/line-width: 1.4; } [zoom >= 14] { - background/line-width: 1.6; + wide/line-width: 5.5; thin/line-width: 1.6; thin/line-dasharray: 23,4,3,4; } } - [admin_level = '4']::firstline { - [zoom >= 8] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1; - } - [zoom >= 9] { background/line-width: 1.5; } - [zoom >= 10] { background/line-width: 2; } - [zoom >= 11] { background/line-width: 2.5; } - [zoom >= 12] { background/line-width: 3; } - [zoom >= 13] { background/line-width: 3.5; } - [zoom >= 14] { background/line-width: 4; } - } - [admin_level = '4']::wideline { - [zoom >= 4] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.4; - line-color: @admin-boundaries; - line-join: bevel; - line-width: 0.4; - line-clip: false; - } + [admin_level = 4][zoom >= 4] { + wide/line-join: bevel; + wide/line-color: @admin-boundaries; + wide/line-opacity: 0.5; + wide/line-width: 0.4; [zoom >= 5] { - background/line-width: 0.5; - line-width: 0.5; + wide/line-width: 0.5; } [zoom >= 6] { - background/line-width: 0.6; - line-width: 0.6; + wide/line-width: 0.6; } [zoom >= 7] { - background/line-width: 0.8; - line-width: 0.8; + wide/line-width: 0.8; } [zoom >= 8] { - background/line-width: 1; - line-width: 1; + wide/line-width: 1.0; } [zoom >= 9] { - background/line-width: 1.5; - line-width: 1.5; - } - [zoom >= 10] { - background/line-width: 2; - line-color: @admin-boundaries-wide; - line-width: 2; + wide/line-width: 1.5; } - [zoom >= 11] { - background/line-width: 2.5; - line-width: 2.8; - } - [zoom >= 12] { - background/line-width: 3; - line-width: 3; - } - [zoom >= 13] { - background/line-width: 3.5; - line-width: 3.5; - } - [zoom >= 14] { - background/line-width: 4; - line-width: 4; - } - } - [admin_level = '4']::narrowline { [zoom >= 10] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.6; + wide/line-color: @admin-boundaries-wide; + wide/line-width: 2.0; + thin/line-join: bevel; thin/line-color: @admin-boundaries-narrow; + thin/line-opacity: 0.6; thin/line-width: 0.6; thin/line-dasharray: 8,2,1.5,2,1.5,2; + thin/line-clip: false; } [zoom >= 11] { - background/line-width: 0.8; + wide/line-width: 2.8; thin/line-width: 0.8; } [zoom >= 12] { - background/line-width: 1; - thin/line-width: 1; + wide/line-width: 3.0; + thin/line-width: 1.0; thin/line-dasharray: 12,3,2,3,2,3; } [zoom >= 13] { - background/line-width: 1.2; + wide/line-width: 3.5; thin/line-width: 1.2; } [zoom >= 14] { - background/line-width: 1.4; + wide/line-width: 4; thin/line-width: 1.4; thin/line-dasharray: 16,4,3,4,3,4; } } - ::firstline { opacity: 0.5; } - ::wideline { opacity: 0.5; } - ::narrowline { opacity: 0.6; } - /* - The following code prevents admin boundaries from being rendered on top of - each other. Comp-op works on the entire attachment, not on the individual - border. Therefore, this code generates an attachment containing a set of - @admin-boundaries/white dashed lines (of which only the top one is visible), - and with `comp-op: darken` the white part is ignored, while the - @admin-boundaries colored part is rendered (as long as the background is not - darker than @admin-boundaries). - The SQL has `ORDER BY admin_level`, so the boundary with the lowest - admin_level is rendered on top, and therefore the only visible boundary. - */ - ::firstline, - ::wideline, - ::narrowline { comp-op: darken; } - [admin_level = '5'][zoom >= 8]::firstline { - background/line-join: bevel; - background/line-color: white; - background/line-width: 0.6; + [admin_level = 5][zoom >= 8] { line-join: bevel; line-color: @admin-boundaries; + line-opacity: 0.5; line-width: 0.6; line-dasharray: 4,0.6,2,0.6; line-clip: false; [zoom >= 9] { - background/line-width: 0.8; line-width: 0.8; line-dasharray: 6,1,3,1; } [zoom >= 10] { - background/line-width: 1.2; line-width: 1.2; line-dasharray: 10,1.5,4.5,1.5; } [zoom >= 11] { - background/line-width: 1.7; line-width: 1.7; } [zoom >= 12] { - background/line-width: 2.1; line-width: 2.1; line-dasharray: 16,2,6,2; } [zoom >= 14] { - background/line-width: 2.4; line-width: 2.4; line-dasharray: 20,2,8,2; } } - [admin_level = '6'][zoom >= 10]::firstline { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1; + + [admin_level = 6][zoom >= 10] { line-join: bevel; line-color: @admin-boundaries; - line-width: 1; + line-opacity: 0.5; + line-width: 1.0; line-dasharray: 8,1.5,1.5,1.5; line-clip: false; [zoom >= 11] { - background/line-width: 1.4; line-width: 1.4; } [zoom >= 12] { - background/line-width: 1.8; line-width: 1.8; - line-dasharray: 12,1.5,2,1.5; + line-dasharray: 12,1.5,2,1.5; } [zoom >= 14] { - background/line-width: 2.1; line-width: 2.1; line-dasharray: 16,2,3,2; } } - [admin_level = '7']::firstline { - [zoom >= 11] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.2; - line-join: bevel; - line-color: @admin-boundaries; - line-width: 1.2; - line-dasharray: 6,1.5,1.5,1.5,1.5,1.5; - line-clip: false; - } + + [admin_level = 7][zoom >= 11] { + line-join: bevel; + line-color: @admin-boundaries; + line-opacity: 0.5; + line-width: 1.2; + line-dasharray: 6,1.5,1.5,1.5,1.5,1.5; + line-clip: false; [zoom >= 12] { - background/line-width: 1.5; line-width: 1.5; line-dasharray: 9,2,2,2,2,2; } [zoom >= 14] { - background/line-width: 1.8; line-width: 1.8; line-dasharray: 12,2,3,2,3,2; } } - [admin_level = '8']::firstline { - [zoom >= 12] { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.4; - line-join: bevel; - line-color: @admin-boundaries; - line-width: 1.4; - line-dasharray: 8,2,2,2,2.5,2,2,2; - line-clip: false; - } + + [admin_level = 8][zoom >= 12] { + line-join: bevel; + line-color: @admin-boundaries; + line-opacity: 0.5; + line-width: 1.4; + line-dasharray: 8,2,2,2,2.5,2,2,2; + line-clip: false; [zoom >= 14] { - background/line-width: 1.6; line-width: 1.6; line-dasharray: 10,2,2,2,3,2,2,2; } } - [admin_level = '9'][zoom >= 13]::firstline { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.2; + [admin_level = 9][zoom >= 13] { line-join: bevel; line-color: @admin-boundaries; + line-opacity: 0.5; line-width: 1.2; line-dasharray: 0,3,2,2,2,2,2,3; line-clip: false; [zoom >= 14] { - background/line-width: 1.4; line-width: 1.4; line-dasharray: 0,4,2,2,3,2,2,4; } } - [admin_level = '10'][zoom >= 14]::firstline { - background/line-join: bevel; - background/line-color: white; - background/line-width: 1.2; + + [admin_level = 10][zoom >= 14] { line-join: bevel; line-color: @admin-boundaries; + line-opacity: 0.5; line-width: 1.2; line-dasharray: 0,3,2,2,2,3; line-clip: false; @@ -446,20 +259,20 @@ Then all three layers are added to the rendering with comp-op: darken, so that t } #admin-text[zoom >= 11][way_pixels >= 196000] { - [admin_level = '1'][way_pixels >= 360000], - [admin_level = '2'][way_pixels >= 360000], - [zoom >= 11][admin_level = '3'], - [zoom >= 11][admin_level = '4'], - [zoom >= 11][admin_level = '5'], - [zoom >= 12][admin_level = '6'], - [zoom >= 13][admin_level = '7'], - [zoom >= 14][admin_level = '8'], - [zoom >= 15][admin_level = '9'], + [admin_level = 1][way_pixels >= 360000], + [admin_level = 2][way_pixels >= 360000], + [zoom >= 11][admin_level = 3], + [zoom >= 11][admin_level = 4], + [zoom >= 11][admin_level = 5], + [zoom >= 12][admin_level = 6], + [zoom >= 13][admin_level = 7], + [zoom >= 14][admin_level = 8], + [zoom >= 15][admin_level = 9], [zoom >= 16] { text-name: "[name]"; text-face-name: @book-fonts; text-fill: @state-labels; - [admin_level = '6'] { text-fill: @county-labels; } + [admin_level = 6] { text-fill: @county-labels; } text-halo-radius: @standard-halo-radius; text-halo-fill: @standard-halo-fill; text-largest-bbox-only: false;