Skip to main content Link Search Menu Expand Document (external link)

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 and Relation

  • The query type: The letter code used in GOQL queries, which also includes areas

  • The geometry type: The six JTS Geometry subtypes created by the toGeometry() 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:

WaywLineString or LinearRing
aPolygon (without holes)
RelationPolygon (with or without holes) or MultiPolygon

In this chapter, we’ll look at the characteristics and additional methods of the Node, Way and Relation subtypes.


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.


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.


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

    // 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 and backward), 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.