Skip to content

[FEATURE] Add relation_members geometry type for processing OSM relation members #1426

@zstadler

Description

@zstadler

Summary

Add a new geometry: relation_members option to YAML custom maps that allows processing individual members of OSM relations as separate features. Instead of emitting one feature per relation, this emits one feature per qualifying member, using each member's geometry and tags.

Key Features:

  • Select relations using existing include_when/exclude_when filters
  • Emit one feature per qualifying member (way/node)
  • Filter members by type, role, and tags
  • Add member-level attributes from member tags
  • Support inline script expressions for member filtering and attributes

Problem

Currently, when working with OSM relations in YAML custom maps, you can only emit a single feature for the entire relation. However, many use cases require processing individual members of a relation as separate features:

  • Route relations (type=route): Extract individual road/rail segments that make up a route
  • Boundary relations (type=boundary): Extract individual boundary segments
  • Public transport routes: Extract individual stops or route segments
  • Waterway relations: Extract individual river segments

For example, a type=route relation with route=railway might contain 50 way members representing different track segments. Currently, you'd get one feature for the relation, but you need 50 features - one for each segment - with each segment's own geometry and tags.

Proposed Solution

Add a new geometry: relation_members option that:

  1. Selects relations using the existing include_when/exclude_when filters (applied to relation tags)
  2. Emits one feature per qualifying member instead of one feature per relation
  3. Uses each member's geometry (way → line/polygon, node → point)
  4. Supports member-level filtering by type, role, and tags
  5. Supports member-level attributes from member tags

Use Cases

1. Railway Route Segments

Extract individual track segments from railway route relations:

layers:
  - id: railway_segments
    features:
      - source: osm
        geometry: relation_members
        include_when:
          type: route
          route: railway
        member_types: [way]
        member_include_when:
          railway: rail
        attributes:
          - key: route_name
            tag_value: name
        member_attributes:
          - key: segment_ref
            tag_value: ref

2. Administrative Boundary Segments

Extract individual boundary segments from administrative boundary relations:

layers:
  - id: boundary_segments
    features:
      - source: osm
        geometry: relation_members
        include_when:
          type: boundary
          admin_level: "6"
        member_types: [way]
        member_roles: ["outer"]
        member_attributes:
          - key: boundary_type
            tag_value: boundary

3. Bus Route Stops

Extract individual stops from public transport route relations:

layers:
  - id: bus_stops
    features:
      - source: osm
        geometry: relation_members
        include_when:
          type: route
          route: bus
        member_types: [node]
        member_roles: ["stop"]
        member_include_when:
          public_transport: stop_position
        attributes:
          - key: route_ref
            tag_value: ref
        member_attributes:
          - key: stop_name
            tag_value: name

Configuration

New Geometry Type

geometry: relation_members

When used, the feature selection (include_when/exclude_when) applies to OSM relations, but features are emitted for each qualifying member using the member's geometry.

New Configuration Fields

All fields are optional and only valid with geometry: relation_members:

member_types

Filter members by OSM element type.

  • Type: Array of strings
  • Values: "node", "way", "relation" (or subset)
  • Default: All types (if not specified)
member_types: [way]           # Only process way members
member_types: [node, way]     # Process nodes and ways

member_roles

Filter members by their role in the relation.

  • Type: Array of strings
  • Default: All roles (if not specified)
  • Note: Empty string "" matches members with no role
member_roles: ["outer", "inner"]  # Only process outer/inner members
member_roles: [""]                # Only process members with no role
member_roles: ["stop", "platform"] # Only process stop/platform members

member_include_when

Filter members by their tags. Supports both structured expressions and inline script expressions.

  • Type: Boolean expression (structured or inline script)
  • Default: Include all members (if not specified)

Structured example:

member_include_when:
  railway: rail
  highway: [primary, secondary]

Inline script example:

member_include_when: '${ member.tags.has("highway") && member.tags.highway == "primary" }'

member_exclude_when

Exclude members by their tags. Applied after member_include_when.

  • Type: Boolean expression (structured or inline script)
  • Default: No exclusions (if not specified)
member_exclude_when:
  service: __any__

member_attributes

Add attributes to each member feature from the member's tags.

  • Type: Array of attribute definitions
  • Supports: All attribute features including inline script expressions
member_attributes:
  - key: segment_ref
    tag_value: ref
  - key: segment_name
    tag_value: name
  - key: combined_name
    value: '${ member.tags.name + " (" + feature.tags.name + ")" }'

Member Context Variables

When using inline script expressions in member_include_when, member_exclude_when, or member_attributes, the following variables are available:

  • member.tags - Map of tags from the member element (way/node)
  • member.role - Role of the member in the relation (string, may be empty)
  • member.type - OSM element type: "node", "way", or "relation" (string)
  • member.ref - OSM ID of the member element (long)
  • member.id - Same as member.ref (long)
  • feature.tags - Map of tags from the parent relation (inherited from parent context)
  • All other variables from parent contexts (args, feature.id, etc.)

Complete Example

layers:
  - id: route_segments
    features:
      - source: osm
        geometry: relation_members
        include_when:
          type: route
          route: railway
        member_types: [way]
        member_roles: [""]  # empty role
        member_include_when:
          railway: rail
        member_exclude_when:
          service: __any__
        attributes:
          - key: route_name
            tag_value: name
          - key: route_ref
            tag_value: ref
        member_attributes:
          - key: segment_ref
            tag_value: ref
          - key: segment_name
            tag_value: name
          - key: combined_name
            value: '${ member.tags.name + " (" + feature.tags.name + ")" }'

Behavior Notes

  1. Feature Selection: include_when/exclude_when filters apply to the relation, not individual members
  2. Member Filtering Order: Type → Role → member_include_whenmember_exclude_when
  3. Feature IDs: Each member feature gets a unique ID generated from relation ID and member reference
  4. Duplicate Members: If a relation contains the same member multiple times, only the first occurrence is processed
  5. Missing Members: Missing ways/nodes (common in extracts) are skipped with a logged error
  6. Nested Relations: Nested relation members are currently skipped (not recursively processed)
  7. Empty Relations: If all members are filtered out, no features are emitted
  8. Geometry Types:
    • Nodes → point geometry
    • Ways → line or polygon geometry (determined by whether way is closed and area tag)
    • Relations → skipped (not supported)
  9. Attributes: Both relation-level attributes (attributes) and member-level attributes (member_attributes) are applied to each member feature
  10. Post-processing: Post-processing operations like merge_linestrings can be used with relation_members geometry

Validation

  • member_types, member_roles, member_include_when, member_exclude_when, and member_attributes can only be used with geometry: relation_members
  • member_types values must be "node", "way", or "relation"
  • Clear error messages if these fields are used with other geometry types

Related

This feature follows the same pattern as multipolygon processing, where relation members are processed in two phases:

  • Phase 1: Identify relations matching relation_members features
  • Phase 2: Store member geometries/tags, then process relations to emit member features

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions