Python wrapper for the OSM API
—
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.
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")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']}")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}")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()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
"""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()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']}")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)}")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")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")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)
}{
"comment": "Updated building addresses from survey",
"source": "GPS survey + local knowledge",
"created_by": "MyApp/1.0",
"locale": "en_US"
}{
"comment": "Import government building dataset",
"source": "City Planning Department Open Data",
"import": "yes",
"url": "https://data.city.gov/buildings"
}{
"comment": "Fix address format inconsistencies",
"source": "existing OSM data",
"mechanical": "yes",
"bot": "no"
}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