Features
A Feature
represents a geographic element. This can be a point of interest like a mailbox or a restaurant, a park, a lake, a segment of a road, or a more abstract concept like a bus route.
Feature
has three subtypes that match the types used by OpenStreetMap:
Node
– a simple point featureWay
– an ordered sequence of nodes, used to represent line strings and simple polygonsRelation
– an object composed of multiple features, such as a polygon with holes, a route or a river system. A relation may contain nodes, ways or other relations as members.
Each feature has a geometry (Point
, LineString
, Polygon
or a collection type), as well as one or more tags (key-value pairs) that describe its details.
Feature
objects are obtained via queries. They are lightweight and immutable, suitable for passing by value.
void checkMuseums(Feature city)
{
for (Feature museum : museums.within(city))
{
FeatureType type = museum.type();
int64_t id = museum.id();
std::string name = museum["name"];
...
Type, identity and equality
type • typeName
type()
returns a FeatureType
enum (NODE
, WAY
or RELATION
).
typeName()
returns a const char*
("node"
, "way"
or "relation"
).
isNode • isWay • isRelation
To check if a Feature
has a certain type, use isNode()
, isWay()
or isRelation()
.
You can implicitly cast
Feature
to its sub-type; however, astd::runtime_error
will be thrown in case of a type mismatch. An attempt to assign aNode
,Way
orRelation
to the wrong type will result in a compile-time error.if(feature.isNode()) { Node node = feature; // <-- This is fine Way noWay = node; // <-- This line won't compile } else { // feature is not a Node Node willFail = feature; // <-- throws a runtime_error }
id
id()
returns the numeric OSM identifier. IDs are unique only within the feature type (which means a node and a way may have the same ID).
id()
may return0
for anonymous nodes.
role
role()
returns the role of the feature within a relation, if it was retrieved via a member query. This method returns an empty StringValue
for features obtained via any other query.
Equality (==
)
Two features are equal if they have the same type and ID.
If two
Feature
objects are returned from different member queries, with different roles, they are considered equal as long as the above holds true.Anonymous nodes are equal if they have the same location.
Anonymous nodes
An anonymous node has no tags and does not belong to any relations — it merely serves to define the geometry of a Way
. By default, feature libraries omit the IDs of such nodes to save space, in which case id()
returns 0
.
Tags
Tags are key-value properties of a feature. Tags are have a TagValue
, which can be implicitly turned into a std::string
, std::string_view
, double
, int
or bool
.
Get a tag value by key:
TagValue value = feature["opening_hours"];
std::string strValue = value;
int maxSpeed = feature["maxspeed"];
If a tag is not present, the resulting
TagValue
is an empty string.TODO: conversions
Check for presence of a tag:
if(feature.hasTag("highway")) ...
if(feature.hasTag("shop", "bakery")) ...
Get all tags:
Tags tags = feature.tags();
for(Tag tag: tags)
{
std::cout << tag.key() << " = " << tag.value();
}
Tags
can be turned into other data structures:
// A map of key strings (sorted alphabetically) to value strings
std::map<std::string,std::string> keyValueMap = feature.tags();
// A vector of Tag objects (in storage order)
std::vector<Tag> tagVector = feature.tags();
Location and geometry
bounds
bounds()
returns a feature’s bounding box (a Box
). This is the smallest axis-aligned rectangle that encloses the feature’s geometry.
lon • lat
lon()
and lat()
return the longitude and latitude of this feature (WGS-84 degrees)
x • y • xy
x()
and y()
return its Mercator-projected coordinates (as int32_t
). For ways and relations, this is the center point of the feature’s bounding box (not the feature’s centroid).
xy()
returns both as a Coordinate
.
centroid
centroid()
calculates the feature’s centroid (a Coordinate
).
toGeometry
toGeometry(GEOSContextHandle_t)
creates a GEOSGeometry
for this feature:
Point
for a NodeLineString
orLinearRing
for a non-area WayPolygon
for a Way that represents an areaPolygon
orMultiPolygon
for an areaRelation
GeometryCollection
for any otherRelation
In order to use this method, you will need to enable option
GEODESK_WITH_GEOS
in your CMake project:set(GEODESK_WITH_GEOS ON) set(GEOS_INCLUDE_PATHS "${geos_SOURCE_DIR}/include" "${geos_BINARY_DIR}/capi")
Example:
GEOSContextHandle_t geosContext = initGEOS_r(nullptr, nullptr);
GEOSGeometry* geom = feature.toGeometry(geosContext);
std::cout << "GEOS geometry created successfully." << std::endl;
GEOSGeom_destroy_r(geosContext, geom);
finishGEOS_r(geosContext);
isArea
isArea()
returns true
if the feature represents an area (always false
for Node
).
area • length
area()
measures the area of the feature (square meters as double
).
length()
measures the length of the feature (meters as double
).
Related features
Use nodes()
, members()
and parents()
to retrieve related features.
Nodes of a way
nodes()
returns an ordered collection of a way’s nodes. An optional query string can be passed:
return way.nodes("[traffic_calming]") // only speed bumps etc.
You can also invert the query by calling nodesOf()
on a set of features. This is especially useful if the filter condition is complex, as it makes your code easier to read, and may improve performance in tight loops.
// Obtain a set of all crosswalks
Nodes crosswalks = world("n[highway=crossing]");
// Return the crosswalks of the given street
return crosswalks.nodesOf(street);
Members of a relation
members()
returns an ordered collection of a relation’s members. An optional query string can be passed:
route.members("w[highway=primary]")
// only members that are primary roads
Member queries can be inverted, as well. The following is equivalent to the above example:
Features primaryRoads = world("w[highway=primary]");
return primaryRoads.membersOf(route);
Parents
parents()
returns the relations to which this feature belongs (or an empty collection if it is not part of any relation), as well as the ways (if any) to which a node belongs. An optional query string can be passed:
Relations busRoutes = street.parents("r[route=bus]");
// Bus routes which traverse this street
This query can also be inverted using Features.parentsOf()
:
world("r[route=bus]").parentsOf(street) // same as above
belongsTo • belongsToRelation
belongsTo(Feature parent)
checks whether this Feature
is part of a specific relation, or whether a Node
belongs to a given Way
. If parent
is a Node
, the result is always false
.
belongsToRelation()
checks whether a Feature
is a member of any Relation
.