CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-osmapi

Python wrapper for the OSM API

Pending
Overview
Eval results
Files

relations.mddocs/

Relation Operations

Comprehensive relation management including creation, modification, deletion, and nested relation handling. Relations group other OSM elements (nodes, ways, and other relations) together to represent complex geographic features like multipolygons, routes, and administrative boundaries.

Capabilities

Relation Retrieval

Get individual relations by ID with optional version specification.

def RelationGet(RelationId, RelationVersion=-1):
    """
    Returns relation with RelationId as a dict.
    
    Parameters:
    - RelationId (int): Unique identifier of the relation
    - RelationVersion (int, optional): Specific version to retrieve (-1 for latest)
    
    Returns:
    dict: Relation data including id, member (list), tag, changeset, version,
          user, uid, timestamp, visible
    
    Raises:
    - ElementDeletedApiError: If relation has been deleted
    - ElementNotFoundApiError: If relation cannot be found
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Get latest version of relation
relation = api.RelationGet(789)
print(f"Relation {relation['id']}: {relation['tag'].get('name', 'Unnamed')}")
print(f"Type: {relation['tag'].get('type', 'unknown')}")
print(f"Members: {len(relation['member'])}")

# Show member details
for member in relation['member']:
    print(f"  {member['type']} {member['ref']} as '{member['role']}'")

# Get specific version
relation_v2 = api.RelationGet(789, RelationVersion=2)
print(f"Version 2 had {len(relation_v2['member'])} members")

Relation Creation

Create new relations by grouping existing elements with defined roles.

def RelationCreate(RelationData):
    """
    Creates a relation based on the supplied RelationData dict.
    
    Parameters:
    - RelationData (dict): Relation data with member list and optional tag
      Required: member (list[dict]) - list of member objects
      Optional: tag (dict)
    
    Returns:
    dict: Updated RelationData with assigned id, version, changeset,
          user, uid, visible
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - OsmTypeAlreadyExistsError: If relation data contains existing ID
    - ChangesetClosedApiError: If changeset is closed
    - PreconditionFailedApiError: If referenced elements don't exist
    """

Usage Example:

import osmapi

api = osmapi.OsmApi(username="your_username", password="your_password")

with api.Changeset({"comment": "Creating bus route relation"}) as changeset_id:
    # Create a bus route relation
    new_relation = api.RelationCreate({
        "member": [
            {"type": "node", "ref": 12345, "role": "stop"},
            {"type": "way", "ref": 67890, "role": ""},
            {"type": "way", "ref": 67891, "role": ""},
            {"type": "node", "ref": 12346, "role": "stop"},
            {"type": "way", "ref": 67892, "role": ""},
            {"type": "node", "ref": 12347, "role": "stop"}
        ],
        "tag": {
            "type": "route",
            "route": "bus",
            "name": "Bus Route 42",
            "ref": "42",
            "operator": "City Transit",
            "public_transport:version": "2"
        }
    })
    print(f"Created relation {new_relation['id']} with {len(new_relation['member'])} members")

Relation Updates

Modify existing relations by updating members, tags, or other attributes.

def RelationUpdate(RelationData):
    """
    Updates relation with the supplied RelationData dict.
    
    Parameters:
    - RelationData (dict): Relation data with id, version, and updated fields
      Required: id (int), member (list[dict]), version (int)
      Optional: tag (dict)
    
    Returns:
    dict: Updated RelationData with new version, changeset, user, uid, visible
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - VersionMismatchApiError: If version doesn't match current
    - ChangesetClosedApiError: If changeset is closed
    - PreconditionFailedApiError: If referenced elements don't exist
    """

Usage Example:

import osmapi

api = osmapi.OsmApi(username="your_username", password="your_password")

# Get current relation data
relation = api.RelationGet(12345)

with api.Changeset({"comment": "Adding stop to bus route"}) as changeset_id:
    # Add a new stop to the route
    new_stop = {"type": "node", "ref": 98765, "role": "stop"}
    relation["member"].append(new_stop)
    
    # Update route information
    relation["tag"]["note"] = "Route extended to new terminal"
    
    updated_relation = api.RelationUpdate(relation)
    print(f"Updated relation {updated_relation['id']} to version {updated_relation['version']}")
    print(f"Now has {len(updated_relation['member'])} members")

Relation Deletion

Delete relations from OpenStreetMap (marks as invisible).

def RelationDelete(RelationData):
    """
    Delete relation with RelationData.
    
    Parameters:
    - RelationData (dict): Relation data with id, version, and current attributes
      Required: id (int), member (list[dict]), version (int)
      Optional: tag (dict)
    
    Returns:
    dict: Updated RelationData with visible=False and new version
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - VersionMismatchApiError: If version doesn't match current
    - ChangesetClosedApiError: If changeset is closed
    - ElementDeletedApiError: If relation already deleted
    - ElementNotFoundApiError: If relation cannot be found
    - PreconditionFailedApiError: If relation is still used by other relations
    """

Usage Example:

import osmapi

api = osmapi.OsmApi(username="your_username", password="your_password")

# Get relation to delete
relation = api.RelationGet(12345)

# Check if relation is used in other relations first
parent_relations = api.RelationRelations(relation["id"])
if parent_relations:
    print(f"Warning: Relation {relation['id']} is used in {len(parent_relations)} other relations")

with api.Changeset({"comment": "Removing obsolete relation"}) as changeset_id:
    deleted_relation = api.RelationDelete(relation)
    print(f"Deleted relation {deleted_relation['id']}, now version {deleted_relation['version']}")
    print(f"Visible: {deleted_relation['visible']}")  # False

Relation History

Retrieve complete version history for a relation.

def RelationHistory(RelationId):
    """
    Returns dict with version as key containing all relation versions.
    
    Parameters:
    - RelationId (int): Unique identifier of the relation
    
    Returns:
    dict: Version history with version numbers as keys and
          RelationData dicts as values
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Get complete history
history = api.RelationHistory(789)

for version, relation_data in history.items():
    print(f"Version {version}: {relation_data['user']} at {relation_data['timestamp']}")
    print(f"  Members: {len(relation_data['member'])}")
    print(f"  Type: {relation_data['tag'].get('type', 'unknown')}")
    
    # Show member changes
    member_types = {}
    for member in relation_data['member']:
        member_types[member['type']] = member_types.get(member['type'], 0) + 1
    print(f"  Composition: {dict(member_types)}")

Relation Relationships

Find relations that reference a specific relation.

def RelationRelations(RelationId):
    """
    Returns a list of relations containing the specified relation.
    
    Parameters:
    - RelationId (int): Unique identifier of the relation
    
    Returns:
    list[dict]: List of RelationData dicts containing the relation
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

relation_id = 789

# Find parent relations
parent_relations = api.RelationRelations(relation_id)
print(f"Relation {relation_id} is used in {len(parent_relations)} other relations:")

for parent in parent_relations:
    print(f"  Relation {parent['id']}: {parent['tag'].get('name', 'Unnamed')}")
    print(f"    Type: {parent['tag'].get('type', 'unknown')}")
    
    # Find this relation's role in the parent
    for member in parent['member']:
        if member['type'] == 'relation' and member['ref'] == relation_id:
            print(f"    Role: {member['role']}")

Relation with Full Data

Retrieve relation with all referenced elements at two levels deep.

def RelationFull(RelationId):
    """
    Returns the full data (two levels) for relation RelationId as list of dicts.
    
    Parameters:
    - RelationId (int): Unique identifier of the relation
    
    Returns:
    list[dict]: List of elements with type and data keys, including
               the relation itself and all directly referenced elements
    
    Raises:
    - ElementDeletedApiError: If relation has been deleted
    - ElementNotFoundApiError: If relation cannot be found
    """

Recursive Relation Full Data

Retrieve relation with all referenced elements at all levels (recursive).

def RelationFullRecur(RelationId):
    """
    Returns the full data (all levels) for relation RelationId as list of dicts.
    
    Parameters:
    - RelationId (int): Unique identifier of the relation
    
    Returns:
    list[dict]: List of elements with type and data keys, including
               the relation itself and all recursively referenced elements
    
    Raises:
    - ElementDeletedApiError: If any relation has been deleted
    - ElementNotFoundApiError: If relation cannot be found
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Get relation with two levels of data
full_data = api.RelationFull(789)

relation_data = None
nodes_data = []
ways_data = []
child_relations_data = []

for element in full_data:
    if element['type'] == 'relation':
        if element['data']['id'] == 789:
            relation_data = element['data']
        else:
            child_relations_data.append(element['data'])
    elif element['type'] == 'way':
        ways_data.append(element['data'])
    elif element['type'] == 'node':
        nodes_data.append(element['data'])

print(f"Relation {relation_data['id']}: {relation_data['tag'].get('name', 'Unnamed')}")
print(f"Contains {len(nodes_data)} nodes, {len(ways_data)} ways, {len(child_relations_data)} relations")

# For complex nested relations, use recursive version
if child_relations_data:
    print("Getting recursive data for complex relation...")
    recursive_data = api.RelationFullRecur(789)
    print(f"Recursive data contains {len(recursive_data)} total elements")

Bulk Relation Operations

Retrieve multiple relations in a single API call for efficiency.

def RelationsGet(RelationIdList):
    """
    Returns dict with relation IDs as keys for multiple relations.
    
    Parameters:
    - RelationIdList (list[int]): List of relation IDs to retrieve
    
    Returns:
    dict: Relation IDs as keys with RelationData dicts as values
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Get multiple relations efficiently
relation_ids = [789, 101112, 131415, 161718]
relations = api.RelationsGet(relation_ids)

for relation_id, relation_data in relations.items():
    relation_type = relation_data['tag'].get('type', 'unknown')
    name = relation_data['tag'].get('name', 'Unnamed')
    print(f"Relation {relation_id}: {relation_type} - {name}")
    print(f"  Members: {len(relation_data['member'])}")

# Check for missing relations
found_ids = set(relations.keys())
missing_ids = set(relation_ids) - found_ids
if missing_ids:
    print(f"Missing relations: {missing_ids}")

Relation Data Structure

RelationData Dictionary

RelationData = {
    'id': int,           # Relation ID (assigned by OSM after creation)
    'member': list[dict], # List of member objects (required)
    'tag': dict,         # Key-value pairs for attributes
    'version': int,      # Version number (starts at 1)
    'changeset': int,    # ID of changeset that last modified this relation
    'user': str,         # Username of last editor
    'uid': int,          # User ID of last editor
    'timestamp': str,    # ISO timestamp of last modification
    'visible': bool      # True if visible, False if deleted
}

RelationMember = {
    'type': str,         # 'node', 'way', or 'relation'
    'ref': int,          # Referenced element ID
    'role': str          # Role description (can be empty string)
}

Common Relation Types

Multipolygon Relations

Complex areas with holes or multiple parts.

multipolygon_relation = {
    "member": [
        {"type": "way", "ref": outer_way_id, "role": "outer"},
        {"type": "way", "ref": inner_way_id, "role": "inner"}
    ],
    "tag": {
        "type": "multipolygon",
        "natural": "forest",
        "name": "Central Forest Reserve"
    }
}

Route Relations

Transportation or hiking routes.

bus_route_relation = {
    "member": [
        {"type": "node", "ref": stop1_id, "role": "stop"},
        {"type": "way", "ref": way1_id, "role": ""},
        {"type": "node", "ref": stop2_id, "role": "stop"},
        {"type": "way", "ref": way2_id, "role": ""}
    ],
    "tag": {
        "type": "route",
        "route": "bus",
        "name": "Bus Line 42",
        "ref": "42",
        "operator": "Metro Transit"
    }
}

Boundary Relations

Administrative or other boundaries.

boundary_relation = {
    "member": [
        {"type": "way", "ref": boundary_way1_id, "role": "outer"},
        {"type": "way", "ref": boundary_way2_id, "role": "outer"}
    ],
    "tag": {
        "type": "boundary",
        "boundary": "administrative",
        "admin_level": "6",
        "name": "Example County"
    }
}

Relation Best Practices

Member Ordering

  • Order members logically (e.g., stops along a route)
  • For multipolygons, list outer ways first, then inner ways
  • Maintain consistent member ordering across versions

Role Assignment

  • Use standard roles for relation types
  • Empty roles are valid and often used
  • Document custom roles clearly in relation tags

Nested Relations

  • Avoid deeply nested relation hierarchies
  • Use RelationFullRecur() for complex nested structures
  • Be careful with circular references

Error Handling

Relation operations can raise various exceptions:

import osmapi

api = osmapi.OsmApi(username="user", password="pass")

try:
    relation = api.RelationGet(999999)
except osmapi.ElementNotFoundApiError:
    print("Relation does not exist")
except osmapi.ElementDeletedApiError:
    print("Relation has been deleted")

try:
    with api.Changeset({"comment": "Test"}) as changeset_id:
        # This will fail if referenced elements don't exist
        api.RelationCreate({
            "member": [
                {"type": "node", "ref": 999999, "role": "stop"}  # Non-existent node
            ],
            "tag": {"type": "route", "route": "bus"}
        })
except osmapi.PreconditionFailedApiError:
    print("Referenced elements don't exist or are not visible")
except osmapi.VersionMismatchApiError:
    print("Version conflict - relation was modified by another user")

Install with Tessl CLI

npx tessl i tessl/pypi-osmapi

docs

authentication.md

changesets.md

errors.md

index.md

nodes.md

notes.md

relations.md

ways.md

tile.json