Skip to content

Commit

Permalink
Process either as way properties or discrete geoms
Browse files Browse the repository at this point in the history
  • Loading branch information
systemed committed Jul 22, 2023
1 parent 505fb7c commit 0869ed4
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 32 deletions.
4 changes: 3 additions & 1 deletion docs/RELATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
23 changes: 22 additions & 1 deletion resources/process-openmaptiles.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
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: 15 additions & 19 deletions src/read_pbf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,17 @@ bool PbfReader::ScanRelations(OsmLuaProcessing &output, PrimitiveGroup &pg, Prim

for (int j=0; j<pg.relations_size(); j++) {
Relation pbfRelation = pg.relations(j);
bool isMultiPolygon = RelationIsType(pbfRelation, typeKey, mpKey) || RelationIsType(pbfRelation, typeKey, boundaryKey);
bool isMultiPolygon = RelationIsType(pbfRelation, typeKey, mpKey);
bool isBoundary = RelationIsType(pbfRelation, typeKey, boundaryKey);
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 (!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++) {
Expand All @@ -162,37 +164,31 @@ 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<pg.relations_size(); j++) {
Relation pbfRelation = pg.relations(j);
bool isMultiPolygon = RelationIsType(pbfRelation, typeKey, mpKey) || RelationIsType(pbfRelation, typeKey, boundaryKey);
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

0 comments on commit 0869ed4

Please sign in to comment.