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

generalize node_keys; add way_keys #629

Closed
wants to merge 9 commits into from
Closed
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ file(GLOB tilemaker_src_files
src/sharded_way_store.cpp
src/shared_data.cpp
src/shp_mem_tiles.cpp
src/significant_tags.cpp
src/sorted_node_store.cpp
src/sorted_way_store.cpp
src/tag_map.cpp
src/tile_data.cpp
src/tilemaker.cpp
src/tile_worker.cpp
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ tilemaker: \
src/sharded_way_store.o \
src/shared_data.o \
src/shp_mem_tiles.o \
src/significant_tags.o \
src/sorted_node_store.o \
src/sorted_way_store.o \
src/tag_map.o \
src/tile_data.o \
src/tilemaker.o \
src/tile_worker.o \
Expand All @@ -132,6 +134,7 @@ test: \
test_deque_map \
test_pbf_reader \
test_pooled_string \
test_significant_tags \
test_sorted_node_store \
test_sorted_way_store

Expand Down Expand Up @@ -162,6 +165,12 @@ test_pooled_string: \
test/pooled_string.test.o
$(CXX) $(CXXFLAGS) -o test.pooled_string $^ $(INC) $(LIB) $(LDFLAGS) && ./test.pooled_string

test_significant_tags: \
src/significant_tags.o \
src/tag_map.o \
test/significant_tags.test.o
$(CXX) $(CXXFLAGS) -o test.significant_tags $^ $(INC) $(LIB) $(LDFLAGS) && ./test.significant_tags

test_sorted_node_store: \
src/external/streamvbyte_decode.o \
src/external/streamvbyte_encode.o \
Expand Down
58 changes: 33 additions & 25 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,21 @@ For example:

### Lua processing

Your Lua file needs to supply 5 things:
Your Lua file needs to supply a few things:

1. `node_keys`, a list of those OSM keys which indicate that a node should be processed
2. `init_function(name)` (optional), a function to initialize Lua logic
2. `node_function(node)`, a function to process an OSM node and add it to layers
3. `way_function(way)`, a function to process an OSM way and add it to layers
3. `exit_function` (optional), a function to finalize Lua logic (useful to show statistics)
1. (optional) `node_keys`, a list of those OSM tags which indicate that a node should be processed
2. (optional) `way_keys`, a list of those OSM tags which indicate that a way should be processed
3. `node_function()`, a function to process an OSM node and add it to layers
4. `way_function()`, a function to process an OSM way and add it to layers
5. (optional) `init_function(name)`, a function to initialize Lua logic
6. (optional) `exit_function`, a function to finalize Lua logic (useful to show statistics)
7. (optional) `relation_scan_function`, a function to determine whether your Lua file wishes to process the given relation
8. (optional) `relation_function`, a function to process an OSM relation and add it to layers
9. (optional) `attribute_function`, a function to remap attributes from shapefiles

`node_keys` is a simple list (or in Lua parlance, a 'table') of OSM tag keys. If a node has one of those keys, it will be processed by `node_function`; if not, it'll be skipped. For example, if you wanted to show highway crossings and railway stations, it should be `{ "highway", "railway" }`. (This avoids the need to process the vast majority of nodes which contain no important tags at all.)
`node_keys` is a simple list (or in Lua parlance, a 'table') of OSM tags. If a node has one of those keys, it will be processed by `node_function`; if not, it'll be skipped. For example, if you wanted to show highway crossings and railway stations, it should be `{ "highway", "railway" }`. (This avoids the need to process the vast majority of nodes which contain no important tags at all.)

`way_keys` is similar to `node_keys`, but for ways. For ways, you may also wish to express the filter in terms of the tag value, or as an inversion. For example, to exclude buildings: `way_keys = {"~building"}`. To build a map only of major roads: `way_keys = {"highway=motorway", "highway=trunk", "highway=primary", "highway=secondary"}`

`node_function` and `way_function` work the same way. They are called with an OSM object; you then inspect the tags of that object, and put it in your vector tiles' layers based on those tags. In essence, the process is:

Expand All @@ -127,28 +133,30 @@ Note the order: you write to a layer first, then set attributes after.

To do that, you use these methods:

* `node:Find(key)` or `way:Find(key)`: get the value for a tag, or the empty string if not present. For example, `way:Find("railway")` might return "rail" for a railway, "siding" for a siding, or "" if it isn't a railway at all.
* `node:Holds(key)` or `way:Holds(key)`: returns true if that key exists, false otherwise.
* `node:Layer("layer_name", false)` or `way:Layer("layer_name", is_area)`: write this node/way to the named layer. This is how you put objects in your vector tile. is_area (true/false) specifies whether a way should be treated as an area, or just as a linestring.
* `way:LayerAsCentroid("layer_name")`: write a single centroid point for this way to the named layer (useful for labels and POIs).
* `node:Attribute(key,value,minzoom)` or `node:Attribute(key,value,minzoom)`: add an attribute to the most recently written layer. Argument `minzoom` is optional, use it if you do not want to write the attribute on lower zoom levels.
* `node:AttributeNumeric(key,value,minzoom)`, `node:AttributeBoolean(key,value,minzoom)` (and `way:`...): for numeric/boolean columns.
* `node:Id()` or `way:Id()`: get the OSM ID of the current object.
* `node:ZOrder(number)` or `way:ZOrder(number)`: Set a numeric value (default 0, 1-byte signed integer) used to sort features within a layer. Use this feature to ensure a proper rendering order if the rendering engine itself does not support sorting. Sorting is not supported across layers merged with `write_to`. Features with different z-order are not merged if `combine_below` or `combine_polygons_below` is used.
* `node:MinZoom(zoom)` or `way:MinZoom(zoom)`: set the minimum zoom level (0-15) at which this object will be written. Note that the JSON layer configuration minimum still applies (so `:MinZoom(5)` will have no effect if your layer only starts at z6).
* `way:Length()` and `way:Area()`: return the length (metres)/area (square metres) of the current object. Requires recent Boost.
* `way:Centroid()`: return the lat/lon of the centre of the current object as a two-element Lua table (element 1 is lat, 2 is lon).
* `Find(key)`: get the value for a tag, or the empty string if not present. For example, `Find("railway")` might return "rail" for a railway, "siding" for a siding, or "" if it isn't a railway at all.
* `Holds(key)`: returns true if that key exists, false otherwise.
* `Layer("layer_name", is_area)`: write this node/way to the named layer. This is how you put objects in your vector tile. is_area (true/false) specifies whether a way should be treated as an area, or just as a linestring.
* `LayerAsCentroid("layer_name")`: write a single centroid point for this way to the named layer (useful for labels and POIs).
* `Attribute(key,value,minzoom)`: add an attribute to the most recently written layer. Argument `minzoom` is optional, use it if you do not want to write the attribute on lower zoom levels.
* `AttributeNumeric(key,value,minzoom)`, `AttributeBoolean(key,value,minzoom)`: for numeric/boolean columns.
* `Id()`: get the OSM ID of the current object.
* `ZOrder(number)`: Set a numeric value (default 0, 1-byte signed integer) used to sort features within a layer. Use this feature to ensure a proper rendering order if the rendering engine itself does not support sorting. Sorting is not supported across layers merged with `write_to`. Features with different z-order are not merged if `combine_below` or `combine_polygons_below` is used.
* `MinZoom(zoom)`: set the minimum zoom level (0-15) at which this object will be written. Note that the JSON layer configuration minimum still applies (so `:MinZoom(5)` will have no effect if your layer only starts at z6).
* `Length()` and `Area()`: return the length (metres)/area (square metres) of the current object. Requires recent Boost.
* `Centroid()`: return the lat/lon of the centre of the current object as a two-element Lua table (element 1 is lat, 2 is lon).

The simplest possible function, to include roads/paths and nothing else, might look like this:

function way_function(way)
local highway = way:Find("highway")
```lua
function way_function()
local highway = Find("highway")
if highway~="" then
way:Layer("roads", false)
way:Attribute("name", way:Find("name"))
way:Attribute("type", highway)
Layer("roads", false)
Attribute("name", Find("name"))
Attribute("type", highway)
end
end
```

Take a look at the supplied process.lua for a simple example, or the more complex OpenMapTiles-compatible script in `resources/`. You can specify another filename with the `--process` option.

Expand Down Expand Up @@ -197,11 +205,11 @@ When processing OSM objects with your Lua script, you can perform simple spatial

You can then find out whether a node is within one of these polygons using the `Intersects` method:

if node:Intersects("countries") then print("Looks like it's on land"); end
if Intersects("countries") then print("Looks like it's on land"); end

Or you can find out what country(/ies) the node is within using `FindIntersecting`, which returns a table:

names = node:FindIntersecting("countries")
names = FindIntersecting("countries")
print(table.concat(name,","))

To enable these functions, set `index` to true in your shapefile layer definition. `index_column` is not needed for `Intersects` but required for `FindIntersecting`.
Expand Down
34 changes: 20 additions & 14 deletions docs/RELATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,30 @@ This is a two-stage process: first, when reading relations, indicate that these

To define which relations should be accepted, add a `relation_scan_function`:

function relation_scan_function(relation)
if relation:Find("type")=="route" and relation:Find("route")=="bicycle" then
local network = relation:Find("network")
if network=="ncn" then relation:Accept() end
```lua
function relation_scan_function()
if Find("type")=="route" and Find("route")=="bicycle" then
local network = Find("network")
if network=="ncn" then Accept() end
end
end
```

This function takes the relation as its sole argument. Examine the tags using `relation:Find(key)` as normal. (You can also use `relation:Holds(key)` and `relation:Id()`.) If you want to use this relation, call `relation:Accept()`.
Examine the tags using `Find(key)` as normal. (You can also use `Holds(key)` and `Id()`.) If you want to use this relation, call `Accept()`.

#### Stage 2: accessing relations from ways

Now that you've accepted the relations, they will be available from `way_function`. They are accessed using an iterator (`way:NextRelation()`) which reads each relation for that way in turn, returning nil when there are no more relations available. Once you have accessed a relation with the iterator, you can read its tags with `way:FindInRelation(key)`. For example:
Now that you've accepted the relations, they will be available from `way_function`. They are accessed using an iterator (`NextRelation()`) which reads each relation for that way in turn, returning nil when there are no more relations available. Once you have accessed a relation with the iterator, you can read its tags with `FindInRelation(key)`. For example:

```lua
while true do
local rel = way:NextRelation()
local rel = NextRelation()
if not rel then break end
print ("Part of route "..way:FindInRelation("ref"))
print ("Part of route "..FindInRelation("ref"))
end
```

(Should you need to re-read the relations, you can reset the iterator with `way:RestartRelations()`.)
(Should you need to re-read the relations, you can reset the iterator with `RestartRelations()`.)


### Writing relation geometries
Expand All @@ -52,13 +56,15 @@ First, make sure that you have accepted the relations using `relation_scan_funct

Then write a `relation_function`, which works in the same way as `way_function` would:

function relation_function(relation)
if relation:Find("type")=="route" and relation:Find("route")=="bicycle" then
relation:Layer("bike_routes", false)
relation:Attribute("class", relation:Find("network"))
relation:Attribute("ref", relation:Find("ref"))
```lua
function relation_function()
if Find("type")=="route" and Find("route")=="bicycle" then
Layer("bike_routes", false)
Attribute("class", Find("network"))
Attribute("ref", Find("ref"))
end
end
```


### Not supported
Expand Down
14 changes: 11 additions & 3 deletions include/attribute_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,14 @@ struct AttributePair {
#define SHARD_BITS 14
#define ATTRIBUTE_SHARDS (1 << SHARD_BITS)

class AttributeStore;
class AttributePairStore {
public:
AttributePairStore():
finalized(false),
pairsMutex(ATTRIBUTE_SHARDS)
pairsMutex(ATTRIBUTE_SHARDS),
lookups(0),
lookupsUncached(0)
{
// The "hot" shard has a capacity of 64K, the others are unbounded.
pairs.push_back(DequeMap<AttributePair>(1 << 16));
Expand All @@ -202,9 +205,10 @@ class AttributePairStore {
const AttributePair& getPairUnsafe(uint32_t i) const;
uint32_t addPair(AttributePair& pair, bool isHot);

std::vector<DequeMap<AttributePair>> pairs;

private:
friend class AttributeStore;
std::vector<DequeMap<AttributePair>> pairs;
bool finalized;
// We refer to all attribute pairs by index.
//
Expand All @@ -214,6 +218,8 @@ class AttributePairStore {
// we suspect will be popular. It only ever has 64KB items,
// so that we can reference it with a short.
mutable std::vector<std::mutex> pairsMutex;
std::atomic<uint64_t> lookupsUncached;
std::atomic<uint64_t> lookups;
};

// AttributeSet is a set of AttributePairs
Expand Down Expand Up @@ -406,7 +412,8 @@ struct AttributeStore {
finalized(false),
sets(ATTRIBUTE_SHARDS),
setsMutex(ATTRIBUTE_SHARDS),
lookups(0) {
lookups(0),
lookupsUncached(0) {
}

AttributeKeyStore keyStore;
Expand All @@ -418,6 +425,7 @@ struct AttributeStore {
mutable std::vector<std::mutex> setsMutex;

mutable std::mutex mutex;
std::atomic<uint64_t> lookupsUncached;
std::atomic<uint64_t> lookups;
};

Expand Down
6 changes: 5 additions & 1 deletion include/deque_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ class DequeMap {
return -1;
}

const T& at(uint32_t index) const {
inline const T& operator[](uint32_t index) const {
return objects[index];
}

inline const T& at(uint32_t index) const {
return objects.at(index);
}

Expand Down
39 changes: 27 additions & 12 deletions include/osm_lua_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

#include <boost/container/flat_map.hpp>

class TagMap;
class SignificantTags;

// Lua
extern "C" {
#include "lua.h"
Expand All @@ -32,6 +35,19 @@ extern bool verbose;
class AttributeStore;
class AttributeSet;

// A string, which might be in `currentTags` as a value. If Lua
// code refers to an absent value, it'll fallback to passing
// it as a std::string.
//
// The intent is that Attribute("name", Find("name")) is a common
// pattern, and we ought to avoid marshalling a string back and
// forth from C++ to Lua when possible.
struct PossiblyKnownTagValue {
bool found;
uint32_t index;
std::string fallback;
};

/**
\brief OsmLuaProcessing - converts OSM objects into OutputObjects.

Expand All @@ -57,6 +73,7 @@ class OsmLuaProcessing {
~OsmLuaProcessing();

// ---- Helpers provided for main routine
void handleUserSignal(int signum);

// Has this object been assigned to any layers?
bool empty();
Expand All @@ -75,31 +92,25 @@ class OsmLuaProcessing {
using tag_map_t = boost::container::flat_map<protozero::data_view, protozero::data_view, DataViewLessThan>;

// Scan non-MP relation
bool scanRelation(WayID id, const tag_map_t &tags);
bool scanRelation(WayID id, const TagMap& tags);

/// \brief We are now processing a significant node
void setNode(NodeID id, LatpLon node, const tag_map_t &tags);
bool setNode(NodeID id, LatpLon node, const TagMap& tags);

/// \brief We are now processing a way
bool setWay(WayID wayId, LatpLonVec const &llVec, const tag_map_t &tags);
bool setWay(WayID wayId, LatpLonVec const &llVec, const TagMap& tags);

/** \brief 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 setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const tag_map_t &tags, bool isNativeMP, bool isInnerOuter);
void setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const TagMap& tags, bool isNativeMP, bool isInnerOuter);

// ---- Metadata queries called from Lua

// Get the ID of the current object
std::string Id() const;

// Check if there's a value for a given key
bool Holds(const std::string& key) const;

// Get an OSM tag for a given key (or return empty string if none)
const std::string Find(const std::string& key) const;

// ---- Spatial queries called from Lua

// Find intersecting shapefile layer
Expand Down Expand Up @@ -185,7 +196,8 @@ class OsmLuaProcessing {

void setVectorLayerMetadata(const uint_least8_t layer, const std::string &key, const uint type);

std::vector<std::string> GetSignificantNodeKeys();
SignificantTags GetSignificantNodeKeys();
SignificantTags GetSignificantWayKeys();

// ---- Cached geometries creation

Expand All @@ -200,6 +212,7 @@ class OsmLuaProcessing {
inline AttributeStore &getAttributeStore() { return attributeStore; }

struct luaProcessingException :std::exception {};
const TagMap* currentTags;

private:
/// Internal: clear current cached state
Expand All @@ -217,6 +230,8 @@ class OsmLuaProcessing {
lastStoredGeometryId = 0;
}

void removeAttributeIfNeeded(const std::string& key);

const inline Point getPoint() {
return Point(lon/10000000.0,latp/10000000.0);
}
Expand Down Expand Up @@ -259,7 +274,7 @@ class OsmLuaProcessing {
class LayerDefinition &layers;

std::vector<std::pair<OutputObject, AttributeSet>> outputs; // All output objects that have been created
const boost::container::flat_map<protozero::data_view, protozero::data_view, DataViewLessThan>* currentTags;
std::vector<std::string> outputKeys;

std::vector<OutputObject> finalizeOutputs();

Expand Down
Loading
Loading