CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-osmapi

Python wrapper for the OSM API

Pending
Overview
Eval results
Files

changesets.mddocs/

Changeset Management

Complete changeset lifecycle including creation, modification, closure, and discussion management. Changesets group related edits together and provide context for modifications to OpenStreetMap data through metadata, comments, and discussion threads.

Capabilities

Changeset Context Manager

Automatic changeset management using Python context managers for streamlined editing workflows.

@contextmanager
def Changeset(ChangesetTags={}):
    """
    Context manager for a Changeset that automatically handles opening and closing.
    
    Parameters:
    - ChangesetTags (dict, optional): Tags to apply to the changeset
    
    Returns:
    int: Changeset ID
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - ChangesetAlreadyOpenError: If changeset already open
    """

Usage Example:

import osmapi

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

# Automatic changeset management
with api.Changeset({"comment": "Adding new POIs", "source": "GPS survey"}) as changeset_id:
    print(f"Working in changeset {changeset_id}")
    
    # All operations within this block use the same changeset
    node1 = api.NodeCreate({"lat": 47.6, "lon": -122.3, "tag": {"amenity": "cafe"}})
    node2 = api.NodeCreate({"lat": 47.61, "lon": -122.31, "tag": {"amenity": "bank"}})
    
    # Changeset automatically closed when leaving context
print("Changeset has been closed")

Changeset Retrieval

Get changeset information with optional discussion data.

def ChangesetGet(ChangesetId, include_discussion=False):
    """
    Returns changeset with ChangesetId as a dict.
    
    Parameters:
    - ChangesetId (int): Unique identifier of the changeset
    - include_discussion (bool, optional): Include discussion thread
    
    Returns:
    dict: Changeset data including id, open status, tag, timestamps,
          bounds, user info, and optionally discussion
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Get basic changeset info
changeset = api.ChangesetGet(12345)
print(f"Changeset {changeset['id']}: {changeset['tag'].get('comment', 'No comment')}")
print(f"User: {changeset['user']} (ID: {changeset['uid']})")
print(f"Created: {changeset['created_at']}")
print(f"Open: {changeset['open']}")

if not changeset['open']:
    print(f"Closed: {changeset['closed_at']}")

# Get changeset with discussion
changeset_with_discussion = api.ChangesetGet(12345, include_discussion=True)
if changeset_with_discussion['discussion']:
    print(f"\nDiscussion ({len(changeset_with_discussion['discussion'])} comments):")
    for comment in changeset_with_discussion['discussion']:
        print(f"  {comment['user']}: {comment['text']}")

Manual Changeset Creation

Create and manage changesets manually for fine-grained control.

def ChangesetCreate(ChangesetTags={}):
    """
    Opens a changeset.
    
    Parameters:
    - ChangesetTags (dict, optional): Tags to apply to the changeset
    
    Returns:
    int: Changeset ID
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - ChangesetAlreadyOpenError: If changeset already open
    - OsmApiError: If trying to create test changeset on production
    """

Usage Example:

import osmapi

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

# Create changeset manually
changeset_id = api.ChangesetCreate({
    "comment": "Manual data cleanup",
    "source": "Local knowledge",
    "created_by": "MyEditingApp/1.0"
})

print(f"Created changeset {changeset_id}")

try:
    # Perform operations
    node = api.NodeCreate({"lat": 47.6, "lon": -122.3, "tag": {"amenity": "shop"}})
    print(f"Created node {node['id']}")
    
finally:
    # Always close changeset
    closed_id = api.ChangesetClose()
    print(f"Closed changeset {closed_id}")

Changeset Updates

Update changeset tags and metadata.

def ChangesetUpdate(ChangesetTags={}):
    """
    Updates current changeset with ChangesetTags.
    
    Parameters:
    - ChangesetTags (dict): Tags to update on the changeset
    
    Returns:
    int: Changeset ID
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - ChangesetClosedApiError: If changeset is closed
    """

Usage Example:

import osmapi

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

changeset_id = api.ChangesetCreate({"comment": "Work in progress"})

# Update changeset with more detailed information
api.ChangesetUpdate({
    "comment": "Updated building addresses from field survey",
    "source": "GPS survey + local knowledge",
    "survey:date": "2024-01-15"
})

# Continue with operations
node = api.NodeCreate({"lat": 47.6, "lon": -122.3, "tag": {"addr:housenumber": "123"}})

api.ChangesetClose()

Changeset Closure

Explicitly close an open changeset.

def ChangesetClose():
    """
    Closes current changeset.
    
    Returns:
    int: Changeset ID that was closed
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - ChangesetClosedApiError: If changeset already closed
    """

Batch Upload Operations

Upload multiple changes to a changeset in a single operation.

def ChangesetUpload(ChangesData):
    """
    Upload data with the ChangesData list of change operations.
    
    Parameters:
    - ChangesData (list[dict]): List of change operations with
      type, action, and data keys
    
    Returns:
    list[dict]: Updated ChangesData with assigned IDs and versions
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - ChangesetClosedApiError: If changeset is closed
    """

Usage Example:

import osmapi

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

changeset_id = api.ChangesetCreate({"comment": "Batch operations example"})

# Prepare batch changes
changes_data = [
    {
        "type": "node",
        "action": "create",
        "data": {"lat": 47.6, "lon": -122.3, "tag": {"amenity": "cafe"}}
    },
    {
        "type": "node", 
        "action": "create",
        "data": {"lat": 47.61, "lon": -122.31, "tag": {"amenity": "bank"}}
    }
]

# Upload all changes at once
result = api.ChangesetUpload(changes_data)

for change in result:
    if change["action"] == "create":
        print(f"Created {change['type']} {change['data']['id']}")

api.ChangesetClose()

Changeset Download

Download all changes made in a specific changeset.

def ChangesetDownload(ChangesetId):
    """
    Download data from changeset ChangesetId.
    
    Parameters:
    - ChangesetId (int): Unique identifier of the changeset
    
    Returns:
    list[dict]: List of change operations with type, action, and data keys
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Download changeset data
changes = api.ChangesetDownload(12345)

print(f"Changeset contains {len(changes)} changes:")

# Analyze changes by type and action
summary = {}
for change in changes:
    key = f"{change['action']} {change['type']}"
    summary[key] = summary.get(key, 0) + 1

for change_type, count in summary.items():
    print(f"  {count} {change_type} operations")

# Show details of first few changes
for i, change in enumerate(changes[:3]):
    element = change['data']
    print(f"\nChange {i+1}: {change['action']} {change['type']} {element['id']}")
    if 'tag' in element and element['tag']:
        print(f"  Tags: {element['tag']}")

Changeset Queries

Search for changesets based on various criteria.

def ChangesetsGet(
    min_lon=None,
    min_lat=None, 
    max_lon=None,
    max_lat=None,
    userid=None,
    username=None,
    closed_after=None,
    created_before=None,
    only_open=False,
    only_closed=False
):
    """
    Returns changesets matching criteria as a dict.
    
    Parameters:
    - min_lon, min_lat, max_lon, max_lat (float, optional): Bounding box
    - userid (int, optional): User ID filter
    - username (str, optional): Username filter
    - closed_after (str, optional): ISO timestamp filter
    - created_before (str, optional): ISO timestamp filter
    - only_open (bool, optional): Only open changesets
    - only_closed (bool, optional): Only closed changesets
    
    Returns:
    dict: Changeset IDs as keys with ChangesetData dicts as values
    """

Usage Example:

import osmapi

api = osmapi.OsmApi()

# Find changesets in a bounding box
changesets = api.ChangesetsGet(
    min_lon=-122.4,
    min_lat=47.5,
    max_lon=-122.2,
    max_lat=47.7
)

print(f"Found {len(changesets)} changesets in the area:")
for cs_id, cs_data in changesets.items():
    print(f"  {cs_id}: {cs_data['tag'].get('comment', 'No comment')}")
    print(f"    by {cs_data['user']} at {cs_data['created_at']}")

# Find recent changesets by a specific user
recent_changesets = api.ChangesetsGet(
    username="specific_user",
    closed_after="2024-01-01T00:00:00Z"
)

print(f"\nRecent changesets by user: {len(recent_changesets)}")

Changeset Discussion

Add comments and manage discussions on changesets.

def ChangesetComment(ChangesetId, comment):
    """
    Adds a comment to the changeset ChangesetId.
    
    Parameters:
    - ChangesetId (int): Changeset to comment on
    - comment (str): Comment text
    
    Returns:
    dict: Updated ChangesetData with new comment
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - ChangesetClosedApiError: If changeset is closed
    """

def ChangesetSubscribe(ChangesetId):
    """
    Subscribe to changeset discussion to receive notifications.
    
    Parameters:
    - ChangesetId (int): Changeset to subscribe to
    
    Returns:
    dict: Updated ChangesetData
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - AlreadySubscribedApiError: If already subscribed
    """

def ChangesetUnsubscribe(ChangesetId):
    """
    Unsubscribe from changeset discussion notifications.
    
    Parameters:
    - ChangesetId (int): Changeset to unsubscribe from
    
    Returns:
    dict: Updated ChangesetData
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NotSubscribedApiError: If not subscribed
    """

Usage Example:

import osmapi

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

changeset_id = 12345

# Subscribe to discussion
try:
    api.ChangesetSubscribe(changeset_id)
    print(f"Subscribed to changeset {changeset_id} discussion")
except osmapi.AlreadySubscribedApiError:
    print("Already subscribed to this changeset")

# Add a comment
updated_changeset = api.ChangesetComment(
    changeset_id, 
    "Thanks for the improvements to the road network!"
)

print(f"Added comment to changeset {changeset_id}")
print(f"Comments count: {updated_changeset['comments_count']}")

# Later, unsubscribe if needed
try:
    api.ChangesetUnsubscribe(changeset_id)
    print("Unsubscribed from changeset discussion")
except osmapi.NotSubscribedApiError:
    print("Was not subscribed to this changeset")

Automatic Changeset Management

Force flush of pending changes in automatic mode.

def flush():
    """
    Force the changes to be uploaded to OSM and the changeset to be closed.
    
    Raises:
    - UsernamePasswordMissingError: If no authentication provided
    - NoChangesetOpenError: If no changeset is open
    - ChangesetAlreadyOpenError: If changeset conflict occurs
    """

Usage Example:

import osmapi

# Configure automatic changeset management
api = osmapi.OsmApi(
    username="your_username",
    password="your_password",
    changesetauto=True,
    changesetautotags={"comment": "Automated cleanup"},
    changesetautosize=100
)

# Make several changes - handled automatically
api.NodeCreate({"lat": 47.6, "lon": -122.3, "tag": {"amenity": "cafe"}})
api.NodeCreate({"lat": 47.61, "lon": -122.31, "tag": {"amenity": "bank"}})

# Force immediate upload and closure
api.flush()
print("All pending changes uploaded and changeset closed")

Changeset Data Structure

ChangesetData Dictionary

ChangesetData = {
    'id': int,              # Changeset ID
    'open': bool,           # True if changeset is still open
    'tag': dict,            # Changeset tags (comment, source, etc.)
    'created_at': str,      # ISO timestamp of creation
    'closed_at': str,       # ISO timestamp of closure (if closed)
    'comments_count': int,  # Number of discussion comments
    'discussion': list,     # Discussion comments (if requested)
    'max_lon': float,       # Eastern boundary of changes
    'max_lat': float,       # Northern boundary of changes
    'min_lon': float,       # Western boundary of changes  
    'min_lat': float,       # Southern boundary of changes
    'user': str,            # Username of changeset creator
    'uid': int              # User ID of changeset creator
}

ChangeData = {
    'type': str,            # 'node', 'way', or 'relation'
    'action': str,          # 'create', 'modify', or 'delete'
    'data': dict            # Element data (NodeData, WayData, or RelationData)
}

Common Changeset Tags

Standard Tags

{
    "comment": "Updated building addresses from survey",
    "source": "GPS survey + local knowledge",
    "created_by": "MyApp/1.0",
    "locale": "en_US"
}

Import Tags

{
    "comment": "Import government building dataset", 
    "source": "City Planning Department Open Data",
    "import": "yes",
    "url": "https://data.city.gov/buildings"
}

Mechanical Edit Tags

{
    "comment": "Fix address format inconsistencies",
    "source": "existing OSM data",
    "mechanical": "yes",  
    "bot": "no"
}

Changeset Best Practices

Good Changeset Comments

  • Be specific about what was changed
  • Mention the source of information
  • Use consistent language and format
  • Keep comments concise but descriptive

Changeset Size Management

  • Keep changesets focused on related changes
  • Avoid mixing different types of edits
  • Use reasonable batch sizes (default 500 elements)
  • Consider geographic locality of changes

Error Handling

import osmapi

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

try:
    changeset_id = api.ChangesetCreate({"comment": "Test changeset"})
    # ... perform operations
    api.ChangesetClose()
    
except osmapi.ChangesetAlreadyOpenError:
    print("Already have an open changeset")
except osmapi.ChangesetClosedApiError:
    print("Changeset was closed by someone else or timeout")
except osmapi.UsernamePasswordMissingError:
    print("Authentication required for changeset operations")

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