Feature Subtypes
When we talk about types of features, there are actually three distinct concepts:
The object type: The GeoDesk API type (derived from
Feature
) that corresponds to the three basic OSM datatypes —Node
,Way
andRelation
The query type: The letter code used in GOQL queries, which also includes areas
The geometry type: The six JTS
Geometry
subtypes created by thetoGeometry()
method
Note that neither OSM nor the GeoDesk API have an “area” type — an area is represented by a Way
(for simple polygons without holes) or a Relation
(which can define more complex polygonal geometries).
This chart illustrates the three type concepts:
Object | Query | Geometry |
---|---|---|
Node | n | Point |
Way | w | LineString or LinearRing |
a | Polygon (without holes) | |
Relation | Polygon (with or without holes) or MultiPolygon | |
r | GeometryCollection |
In this chapter, we’ll look at the characteristics and additional methods of the Node
, Way
and Relation
subtypes.
Node
A feature represented by a single coordinate pair. A Node
can be stand-alone, or form part of a Way
. If the latter, belongsToWay()
returns true
.
parentWays()
returns all ways to which this node belongs (or an empty collection for a stand-alone node). An optional query string can be passed:
node.parentWays("w[waterway=river,stream]") // only returns rivers and streams
Sometimes it is more convenient to inverse a query using with()
: 0.2
library.ways("w[waterway=river,stream]").with(node) // same as above
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
.
Way
A linear or simple polygonal geometry, represented by two or more nodes. Linear rings and polygons have a minimum of four nodes (their first and last node are the same).
nodes()
returns an ordered collection of a way’s nodes. An optional query string can be passed:
way.nodes("[traffic_calming]") // only speed bumps etc.
Sometimes it is more convenient to inverse a query using of()
: 0.2
library.nodes("[traffic_calming]").of(way) // same as above
A Way
is iterable:
for(Node node: way) ...
- Iteration only retrieves nodes that have tags or are part of a relation; to query all nodes, use
nodes()
.
toXY()
returns an int
array with the way’s coordinates (X coordinates are stored at even index positions, Y at odd), which is the most space-efficient representation of the Way’s geometry.
Relation
A Relation
is used to tie together multiple features to build a larger singular feature (such as a long river or a complex polygon) or to create a new conceptual feature, such as a bus route or turn restriction.
members()
returns an ordered collection of a relation’s members. There are several related methods that filter members based on their object type, also with an optional query argument:
rel.members() // all members
rel.memberNodes() // only nodes
rel.memberWays("[highway]") // only roads, paths, etc.
rel.memberRelations() // only members that themselves are relations
rel.members("a") // only areas
rel.members("a[leisure=park]") // only park areas
Note that there is no “memberAreas” method, since areas can be Way
or Relation
objects. If you want only polygonal ways or area relations, use memberWays("a")
/ memberRelations("a")
.
Member queries can also be phrased using of()
: 0.2
library.ways().of(rel) // ways that are members of the given relation
To restrict members to specific roles, use role
in the query as if it were a tag: 0.2
rel.memberWays("w[waterway=canal][role='*_stream']")
// canals whose role ends with "_stream" ("main_stream", "side_stream")
first()
is useful if you expect at most one member with a given role:
Node capital = rel.memberNodes("[role=admin_centre]").first();
A Relation
is iterable:
for(Feature member: rel) ...
// is equivalent to
for(Feature member: rel.members()) ...
- A feature may appear among a relation’s members more than once (for example, a bus route may pass a road segment multiple times). Usually, each occurrence will have a different role (e.g.
forward
andbackward
), but there is no rule requiring this.
toGeometry()
creates a Polygon
or MultiPolygon
for area relations, and a GeometryCollection
(comprised of the geometries of its members) for all others.
The bounding box of a Relation
is the union of the bounding boxes of its members.