From 0869ed47bccbfb23a0a054dff9b31c4046fe75c2 Mon Sep 17 00:00:00 2001 From: systemed Date: Sat, 22 Jul 2023 21:45:04 +0100 Subject: [PATCH] Process either as way properties or discrete geoms --- docs/RELATIONS.md | 4 +++- include/osm_lua_processing.h | 2 +- include/osm_store.h | 5 +++-- resources/process-openmaptiles.lua | 23 +++++++++++++++++++- src/osm_lua_processing.cpp | 15 ++++++------- src/read_pbf.cpp | 34 +++++++++++++----------------- 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/docs/RELATIONS.md b/docs/RELATIONS.md index 5cfde73f..a30cfdcc 100644 --- a/docs/RELATIONS.md +++ b/docs/RELATIONS.md @@ -7,7 +7,9 @@ Note that relation support is in its early stages and behaviour may change betwe ### Multipolygon relations -Multipolygon and boundary relations are supported natively by tilemaker; you do not need to write special Lua code for them. When a multipolygon or boundary is read, tilemaker constructs the geometry as normal, and passes the tags to `way_function` just as it would a simple area. +Multipolygon relations are supported natively by tilemaker; you do not need to write special Lua code for them. When a multipolygon is read, tilemaker constructs the geometry as normal, and passes the tags to `way_function` just as it would a simple area. + +Boundary relations also have automatic handling of inner/outer ways, but are otherwise processed as relations. This means that you can choose to treat boundaries as properties on ways (see "Stage 2" below) or as complete geometries (see "Writing relation geometries"). You will typically want the properties-on-ways approach for administrative boundaries, and the complete-geometries approach for filled areas such as forests or nature reserves. ### Reading relation memberships diff --git a/include/osm_lua_processing.h b/include/osm_lua_processing.h index 3fc7c12d..150fc9a6 100644 --- a/include/osm_lua_processing.h +++ b/include/osm_lua_processing.h @@ -81,7 +81,7 @@ class OsmLuaProcessing { * (note that we store relations as ways with artificial IDs, and that * we use decrementing positive IDs to give a bit more space for way IDs) */ - void setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const tag_map_t &tags, bool isNativeMP); + void setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const tag_map_t &tags, bool isNativeMP, bool isInnerOuter); // ---- Metadata queries called from Lua diff --git a/include/osm_store.h b/include/osm_store.h index 311890f6..2b9cdf64 100644 --- a/include/osm_store.h +++ b/include/osm_store.h @@ -336,6 +336,7 @@ class WayStore { }; // relation store +// (this isn't currently used as we don't need to store relations for later processing, but may be needed for nested relations) class RelationStore { @@ -384,7 +385,7 @@ class RelationStore { OSMStore will be mainly used for geometry generation. Geometry generation logic is implemented in this class. These functions are used by osm_output, and can be used by OsmLuaProcessing to provide the geometry information to Lua. - Internal data structures are encapsulated in NodeStore, WayStore and RelationStore classes. + Internal data structures are encapsulated in NodeStore, WayStore and [unused] RelationStore classes. These store can be altered for efficient memory use without global code changes. Such data structures have to return const ForwardInputIterators (only *, ++ and == should be supported). @@ -429,7 +430,7 @@ class OSMStore bool require_integrity = true; WayStore ways; - RelationStore relations; + RelationStore relations; // unused UsedWays used_ways; RelationScanStore scanned_relations; diff --git a/resources/process-openmaptiles.lua b/resources/process-openmaptiles.lua index 97ab4e74..c121eb52 100644 --- a/resources/process-openmaptiles.lua +++ b/resources/process-openmaptiles.lua @@ -207,6 +207,9 @@ waterwayClasses = Set { "stream", "river", "canal", "drain", "ditch" } -- Scan relations for use in ways function relation_scan_function(relation) + if relation:Find("type")=="boundary" and relation:Find("boundary")=="administrative" then + relation:Accept() + end end -- Process way tags @@ -243,9 +246,27 @@ function way_function(way) if landuse == "field" then landuse = "farmland" end if landuse == "meadow" and way:Find("meadow")=="agricultural" then landuse="farmland" end + -- Boundaries within relations + -- note that we process administrative boundaries as properties on ways, rather than as single relation geometries, + -- because otherwise we get multiple renderings where boundaries are coterminous + local admin_level = 11 + local isBoundary = false + while true do + local rel = way:NextRelation() + if not rel then break end + isBoundary = true + admin_level = math.min(admin_level, tonumber(way:FindInRelation("admin_level")) or 11) + end + + -- Boundaries in ways + if boundary=="administrative" then + admin_level = math.min(admin_level, tonumber(way:Find("admin_level")) or 11) + isBoundary = true + end + -- Administrative boundaries -- https://openmaptiles.org/schema/#boundary - if boundary=="administrative" and isClosed and not (way:Find("maritime")=="yes") then + if isBoundary and not (way:Find("maritime")=="yes") then local admin_level = math.min(11, tonumber(way:Find("admin_level")) or 11) local mz = 0 if admin_level>=3 and admin_level<5 then mz=4 diff --git a/src/osm_lua_processing.cpp b/src/osm_lua_processing.cpp index 7d7081f5..3efd9be4 100644 --- a/src/osm_lua_processing.cpp +++ b/src/osm_lua_processing.cpp @@ -674,25 +674,24 @@ void OsmLuaProcessing::setWay(WayID wayId, LatpLonVec const &llVec, const tag_ma // We are now processing a relation // (note that we store relations as ways with artificial IDs, and that // we use decrementing positive IDs to give a bit more space for way IDs) -void OsmLuaProcessing::setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const tag_map_t &tags, bool isNativeMP) { +void OsmLuaProcessing::setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const tag_map_t &tags, + bool isNativeMP, // only OSM type=multipolygon + bool isInnerOuter) { // any OSM relation with "inner" and "outer" roles (e.g. type=multipolygon|boundary) reset(); osmID = (relationId & OSMID_MASK) | OSMID_RELATION; originalOsmID = relationId; isWay = true; isRelation = true; - isClosed = isNativeMP; + isClosed = isNativeMP || isInnerOuter; llVecPtr = nullptr; outerWayVecPtr = &outerWayVec; innerWayVecPtr = &innerWayVec; currentTags = tags; - bool ok = true; - if (ok) { - //Start Lua processing for relation - luaState[isNativeMP ? "way_function" : "relation_function"](this); - } - + // Start Lua processing for relation + if (!isNativeMP && !supportsWritingRelations) return; + luaState[isNativeMP ? "way_function" : "relation_function"](this); if (this->empty()) return; // Assemble multipolygon diff --git a/src/read_pbf.cpp b/src/read_pbf.cpp index 3c8a3638..7ac0d1da 100644 --- a/src/read_pbf.cpp +++ b/src/read_pbf.cpp @@ -131,15 +131,17 @@ bool PbfReader::ScanRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim for (int j=0; j(pbfRelation.id()); if (!isMultiPolygon) { - if (!output.canReadRelations()) continue; - tag_map_t tags; - readTags(pbfRelation, pb, tags); - isAccepted = output.scanRelation(relid, tags); - if (!isAccepted) continue; + if (output.canReadRelations()) { + tag_map_t tags; + readTags(pbfRelation, pb, tags); + isAccepted = output.scanRelation(relid, tags); + } + if (!isAccepted && !isBoundary) continue; } int64_t lastID = 0; for (int n=0; n < pbfRelation.memids_size(); n++) { @@ -162,22 +164,23 @@ bool PbfReader::ReadRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim int mpKey = findStringPosition(pb, "multipolygon"); int boundaryKey = findStringPosition(pb, "boundary"); int innerKey= findStringPosition(pb, "inner"); - //int outerKey= findStringPosition(pb, "outer"); + int outerKey= findStringPosition(pb, "outer"); if (typeKey >-1 && mpKey>-1) { for (int j=0; j(lastID); (role == innerKey ? innerWayVec : outerWayVec).push_back(wayId); } @@ -185,14 +188,7 @@ bool PbfReader::ReadRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim try { tag_map_t tags; readTags(pbfRelation, pb, tags); - - // Store the relation members in the global relation store - relations.push_back(std::make_pair(pbfRelation.id(), - std::make_pair( - RelationStore::wayid_vector_t(outerWayVec.begin(), outerWayVec.end()), - RelationStore::wayid_vector_t(innerWayVec.begin(), innerWayVec.end())))); - - output.setRelation(pbfRelation.id(), outerWayVec, innerWayVec, tags, isMultiPolygon); + output.setRelation(pbfRelation.id(), outerWayVec, innerWayVec, tags, isMultiPolygon, isInnerOuter); } catch (std::out_of_range &err) { // Relation is missing a member?