Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support type=boundary relations as native multipolygons #508

Merged
merged 3 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/RELATIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Relations

Tilemaker has (as yet not complete) support for reading relations in the Lua process scripts. This means you can support route and boundary relations when creating your vector tiles.
Tilemaker has (as yet not complete) support for reading relations in the Lua process scripts. This means you can support objects like route relations when creating your vector tiles.

Note that relation support is in its early stages and behaviour may change between point versions.

Expand All @@ -9,6 +9,8 @@ Note that relation support is in its early stages and behaviour may change betwe

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

Expand Down
2 changes: 1 addition & 1 deletion include/osm_lua_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions include/osm_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -429,7 +430,7 @@ class OSMStore
bool require_integrity = true;

WayStore ways;
RelationStore relations;
RelationStore relations; // unused
UsedWays used_ways;
RelationScanStore scanned_relations;

Expand Down
8 changes: 8 additions & 0 deletions include/read_pbf.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ class PbfReader
bool ScanRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, PrimitiveBlock const &pb);
bool ReadRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, PrimitiveBlock const &pb);

inline bool RelationIsType(Relation const &rel, int typeKey, int val) {
if (typeKey==-1 || val==-1) return false;
auto typeI = std::find(rel.keys().begin(), rel.keys().end(), typeKey);
if (typeI==rel.keys().end()) return false;
int typePos = typeI - rel.keys().begin();
return rel.vals().Get(typePos) == val;
}

/// Find a string in the dictionary
static int findStringPosition(PrimitiveBlock const &pb, char const *str);

Expand Down
5 changes: 4 additions & 1 deletion resources/process-openmaptiles.lua
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ function way_function(way)
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
Expand All @@ -261,10 +263,11 @@ function way_function(way)
admin_level = math.min(admin_level, tonumber(way:Find("admin_level")) or 11)
isBoundary = true
end

-- Administrative boundaries
-- https://openmaptiles.org/schema/#boundary
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
elseif admin_level>=5 and admin_level<7 then mz=8
Expand Down
15 changes: 7 additions & 8 deletions src/osm_lua_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 14 additions & 20 deletions src/read_pbf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,15 @@ bool PbfReader::ScanRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim

for (int j=0; j<pg.relations_size(); j++) {
Relation pbfRelation = pg.relations(j);
bool isMultiPolygon = (find(pbfRelation.keys().begin(), pbfRelation.keys().end(), typeKey) != pbfRelation.keys().end()) &&
(find(pbfRelation.vals().begin(), pbfRelation.vals().end(), mpKey ) != pbfRelation.vals().end());
bool isMultiPolygon = RelationIsType(pbfRelation, typeKey, mpKey);
bool isAccepted = false;
WayID relid = static_cast<WayID>(pbfRelation.id());
if (!isMultiPolygon) {
if (!output.canReadRelations()) continue;
tag_map_t tags;
readTags(pbfRelation, pb, tags);
isAccepted = output.scanRelation(relid, tags);
if (output.canReadRelations()) {
tag_map_t tags;
readTags(pbfRelation, pb, tags);
isAccepted = output.scanRelation(relid, tags);
}
if (!isAccepted) continue;
}
int64_t lastID = 0;
Expand All @@ -160,39 +160,33 @@ bool PbfReader::ReadRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim

int typeKey = findStringPosition(pb, "type");
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<pg.relations_size(); j++) {
Relation pbfRelation = pg.relations(j);
bool isMultiPolygon = (find(pbfRelation.keys().begin(), pbfRelation.keys().end(), typeKey) != pbfRelation.keys().end()) &&
(find(pbfRelation.vals().begin(), pbfRelation.vals().end(), mpKey ) != pbfRelation.vals().end());
if (!isMultiPolygon && !output.canWriteRelations()) continue;
bool isMultiPolygon = RelationIsType(pbfRelation, typeKey, mpKey);
bool isBoundary = RelationIsType(pbfRelation, typeKey, boundaryKey);
if (!isMultiPolygon && !isBoundary && !output.canWriteRelations()) continue;

// Read relation members
WayVec outerWayVec, innerWayVec;
int64_t lastID = 0;
bool isInnerOuter = isBoundary || isMultiPolygon;
for (int n=0; n < pbfRelation.memids_size(); n++) {
lastID += pbfRelation.memids(n);
if (pbfRelation.types(n) != Relation_MemberType_WAY) { continue; }
int32_t role = pbfRelation.roles_sid(n);
// if (role != innerKey && role != outerKey) { continue; }
// ^^^^ commented out so that we don't die horribly when a relation has no outer way
if (role==innerKey || role==outerKey) isInnerOuter=true;
WayID wayId = static_cast<WayID>(lastID);
(role == innerKey ? innerWayVec : outerWayVec).push_back(wayId);
}

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?
Expand Down
Loading