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


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.

OpenStreetMap uses three types of features:

  • Node – a simple point feature

  • Way – an ordered sequence of nodes, used to represent line strings and simple polygons

  • Relation – 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.

Type, identity and equality

type() returns a FeatureType enum (NODE, WAY or RELATION). To explicitly check if a Feature has a certain type, use isNode(), isWay() or isRelation().

id() returns the OSM identifier (a long). IDs are unique only within the feature type (which means a node and a way may have the same ID).

  • You can obtain a unique identifier that incorporates the type by using the FeatureId utility class.

  • id() may return 0 for anonymous nodes.

role() returns the role of the feature within a relation, if it was returned by a member query. This method returns null for features obtained via any other query (an empty string means the feature is a relation member, but has no assigned role in that particular relation).

equals(): 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.

  • Never rely on == for equality. Queries may return identical objects for the same feature, but are by no means guaranteed to do so (even for the same node in a closed way).


Tags are key-value properties of a feature. Tags are stored as strings, but there are convenience methods to turn strings into numeric values.

Get a tag value by key:


Check for presence of a tag:

if(feature.hasTag("highway")) ...
if(feature.hasTag("shop", "bakery")) ...

Get all tags:

Tags tags = feature.tags();

Tags is a Consumable, a special kind of iterator that works like an SQL ResultSet:

    String key = tags.key();
    String value = tags.stringValue();
    int intValue = tags.intValue();

Tags can be turned into other data structures:

Map<String,Object> tagMap = tags.toMap();

Location and geometry

bounds() returns a feature’s bounding box. This is the smallest axis-aligned rectangle that encloses the feature’s geometry.

lon() and lat() return the longitude and latitude of a Feature; x() and y() return its Mercator-projected coordinates.

  • For ways and relations, this is the center point of the feature’s bounding box (not the feature’s centroid).

toGeometry() creates a JTS Geometry for this feature:

toXY() returns the coordinates of a way as a compact array of Mercator-projected coordinates. X-values are stored at even indexes, Y-values at odd.

Use isArea() to check if the feature represents an area (always false for Node).

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
Features crosswalks ="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:

    // only members that are primary roads 

Member queries can be inverted, as well. The following is equivalent to the above example:

Features primaryRoads ="w[highway=primary]");
return primaryRoads.membersOf(route);


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:

    // Bus routes which traverse this street 

This query can also be inverted using Features.parentsOf():

world.relations("r[route=bus]").parentsOf(street)  // same as above  

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.

Placeholder features

A placeholder feature is a feature that is not present in a dataset, but is referenced from other features in the same dataset. This commonly happens with regional abstracts: A dataset that covers only Germany may include a relation for a train route to Paris that goes through Cologne; however, the dataset most likely won’t include the train stops in Belgium and France (which are members of that relation). In order to maintain referential integrity, gol build creates placeholders for these missing features.

  • isPlaceholder() returns true.

  • type() and id() are valid.

  • isArea() always returns false, even if the feature might actually be an area.

  • A placeholder has none of the feature’s actual tags, but it may contain synthetic tags such as geodesk:error=missing that describe the problem further.

  • A placeholder Way has no nodes; a placeholder Relation has no members.

  • parentRelations() returns only relations that are contained in the library.

  • toGeometry() creates an empty Geometry.

  • All other geometry-related methods return an X-coordinates of Integer.MIN_VALUE to indicate that the feature’s location is invalid/unknown.

  • Its length and area are 0.