0
# Relation Operations
1
2
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.
3
4
## Capabilities
5
6
### Relation Retrieval
7
8
Get individual relations by ID with optional version specification.
9
10
```python { .api }
11
def RelationGet(RelationId, RelationVersion=-1):
12
"""
13
Returns relation with RelationId as a dict.
14
15
Parameters:
16
- RelationId (int): Unique identifier of the relation
17
- RelationVersion (int, optional): Specific version to retrieve (-1 for latest)
18
19
Returns:
20
dict: Relation data including id, member (list), tag, changeset, version,
21
user, uid, timestamp, visible
22
23
Raises:
24
- ElementDeletedApiError: If relation has been deleted
25
- ElementNotFoundApiError: If relation cannot be found
26
"""
27
```
28
29
**Usage Example:**
30
31
```python
32
import osmapi
33
34
api = osmapi.OsmApi()
35
36
# Get latest version of relation
37
relation = api.RelationGet(789)
38
print(f"Relation {relation['id']}: {relation['tag'].get('name', 'Unnamed')}")
39
print(f"Type: {relation['tag'].get('type', 'unknown')}")
40
print(f"Members: {len(relation['member'])}")
41
42
# Show member details
43
for member in relation['member']:
44
print(f" {member['type']} {member['ref']} as '{member['role']}'")
45
46
# Get specific version
47
relation_v2 = api.RelationGet(789, RelationVersion=2)
48
print(f"Version 2 had {len(relation_v2['member'])} members")
49
```
50
51
### Relation Creation
52
53
Create new relations by grouping existing elements with defined roles.
54
55
```python { .api }
56
def RelationCreate(RelationData):
57
"""
58
Creates a relation based on the supplied RelationData dict.
59
60
Parameters:
61
- RelationData (dict): Relation data with member list and optional tag
62
Required: member (list[dict]) - list of member objects
63
Optional: tag (dict)
64
65
Returns:
66
dict: Updated RelationData with assigned id, version, changeset,
67
user, uid, visible
68
69
Raises:
70
- UsernamePasswordMissingError: If no authentication provided
71
- NoChangesetOpenError: If no changeset is open
72
- OsmTypeAlreadyExistsError: If relation data contains existing ID
73
- ChangesetClosedApiError: If changeset is closed
74
- PreconditionFailedApiError: If referenced elements don't exist
75
"""
76
```
77
78
**Usage Example:**
79
80
```python
81
import osmapi
82
83
api = osmapi.OsmApi(username="your_username", password="your_password")
84
85
with api.Changeset({"comment": "Creating bus route relation"}) as changeset_id:
86
# Create a bus route relation
87
new_relation = api.RelationCreate({
88
"member": [
89
{"type": "node", "ref": 12345, "role": "stop"},
90
{"type": "way", "ref": 67890, "role": ""},
91
{"type": "way", "ref": 67891, "role": ""},
92
{"type": "node", "ref": 12346, "role": "stop"},
93
{"type": "way", "ref": 67892, "role": ""},
94
{"type": "node", "ref": 12347, "role": "stop"}
95
],
96
"tag": {
97
"type": "route",
98
"route": "bus",
99
"name": "Bus Route 42",
100
"ref": "42",
101
"operator": "City Transit",
102
"public_transport:version": "2"
103
}
104
})
105
print(f"Created relation {new_relation['id']} with {len(new_relation['member'])} members")
106
```
107
108
### Relation Updates
109
110
Modify existing relations by updating members, tags, or other attributes.
111
112
```python { .api }
113
def RelationUpdate(RelationData):
114
"""
115
Updates relation with the supplied RelationData dict.
116
117
Parameters:
118
- RelationData (dict): Relation data with id, version, and updated fields
119
Required: id (int), member (list[dict]), version (int)
120
Optional: tag (dict)
121
122
Returns:
123
dict: Updated RelationData with new version, changeset, user, uid, visible
124
125
Raises:
126
- UsernamePasswordMissingError: If no authentication provided
127
- NoChangesetOpenError: If no changeset is open
128
- VersionMismatchApiError: If version doesn't match current
129
- ChangesetClosedApiError: If changeset is closed
130
- PreconditionFailedApiError: If referenced elements don't exist
131
"""
132
```
133
134
**Usage Example:**
135
136
```python
137
import osmapi
138
139
api = osmapi.OsmApi(username="your_username", password="your_password")
140
141
# Get current relation data
142
relation = api.RelationGet(12345)
143
144
with api.Changeset({"comment": "Adding stop to bus route"}) as changeset_id:
145
# Add a new stop to the route
146
new_stop = {"type": "node", "ref": 98765, "role": "stop"}
147
relation["member"].append(new_stop)
148
149
# Update route information
150
relation["tag"]["note"] = "Route extended to new terminal"
151
152
updated_relation = api.RelationUpdate(relation)
153
print(f"Updated relation {updated_relation['id']} to version {updated_relation['version']}")
154
print(f"Now has {len(updated_relation['member'])} members")
155
```
156
157
### Relation Deletion
158
159
Delete relations from OpenStreetMap (marks as invisible).
160
161
```python { .api }
162
def RelationDelete(RelationData):
163
"""
164
Delete relation with RelationData.
165
166
Parameters:
167
- RelationData (dict): Relation data with id, version, and current attributes
168
Required: id (int), member (list[dict]), version (int)
169
Optional: tag (dict)
170
171
Returns:
172
dict: Updated RelationData with visible=False and new version
173
174
Raises:
175
- UsernamePasswordMissingError: If no authentication provided
176
- NoChangesetOpenError: If no changeset is open
177
- VersionMismatchApiError: If version doesn't match current
178
- ChangesetClosedApiError: If changeset is closed
179
- ElementDeletedApiError: If relation already deleted
180
- ElementNotFoundApiError: If relation cannot be found
181
- PreconditionFailedApiError: If relation is still used by other relations
182
"""
183
```
184
185
**Usage Example:**
186
187
```python
188
import osmapi
189
190
api = osmapi.OsmApi(username="your_username", password="your_password")
191
192
# Get relation to delete
193
relation = api.RelationGet(12345)
194
195
# Check if relation is used in other relations first
196
parent_relations = api.RelationRelations(relation["id"])
197
if parent_relations:
198
print(f"Warning: Relation {relation['id']} is used in {len(parent_relations)} other relations")
199
200
with api.Changeset({"comment": "Removing obsolete relation"}) as changeset_id:
201
deleted_relation = api.RelationDelete(relation)
202
print(f"Deleted relation {deleted_relation['id']}, now version {deleted_relation['version']}")
203
print(f"Visible: {deleted_relation['visible']}") # False
204
```
205
206
### Relation History
207
208
Retrieve complete version history for a relation.
209
210
```python { .api }
211
def RelationHistory(RelationId):
212
"""
213
Returns dict with version as key containing all relation versions.
214
215
Parameters:
216
- RelationId (int): Unique identifier of the relation
217
218
Returns:
219
dict: Version history with version numbers as keys and
220
RelationData dicts as values
221
"""
222
```
223
224
**Usage Example:**
225
226
```python
227
import osmapi
228
229
api = osmapi.OsmApi()
230
231
# Get complete history
232
history = api.RelationHistory(789)
233
234
for version, relation_data in history.items():
235
print(f"Version {version}: {relation_data['user']} at {relation_data['timestamp']}")
236
print(f" Members: {len(relation_data['member'])}")
237
print(f" Type: {relation_data['tag'].get('type', 'unknown')}")
238
239
# Show member changes
240
member_types = {}
241
for member in relation_data['member']:
242
member_types[member['type']] = member_types.get(member['type'], 0) + 1
243
print(f" Composition: {dict(member_types)}")
244
```
245
246
### Relation Relationships
247
248
Find relations that reference a specific relation.
249
250
```python { .api }
251
def RelationRelations(RelationId):
252
"""
253
Returns a list of relations containing the specified relation.
254
255
Parameters:
256
- RelationId (int): Unique identifier of the relation
257
258
Returns:
259
list[dict]: List of RelationData dicts containing the relation
260
"""
261
```
262
263
**Usage Example:**
264
265
```python
266
import osmapi
267
268
api = osmapi.OsmApi()
269
270
relation_id = 789
271
272
# Find parent relations
273
parent_relations = api.RelationRelations(relation_id)
274
print(f"Relation {relation_id} is used in {len(parent_relations)} other relations:")
275
276
for parent in parent_relations:
277
print(f" Relation {parent['id']}: {parent['tag'].get('name', 'Unnamed')}")
278
print(f" Type: {parent['tag'].get('type', 'unknown')}")
279
280
# Find this relation's role in the parent
281
for member in parent['member']:
282
if member['type'] == 'relation' and member['ref'] == relation_id:
283
print(f" Role: {member['role']}")
284
```
285
286
### Relation with Full Data
287
288
Retrieve relation with all referenced elements at two levels deep.
289
290
```python { .api }
291
def RelationFull(RelationId):
292
"""
293
Returns the full data (two levels) for relation RelationId as list of dicts.
294
295
Parameters:
296
- RelationId (int): Unique identifier of the relation
297
298
Returns:
299
list[dict]: List of elements with type and data keys, including
300
the relation itself and all directly referenced elements
301
302
Raises:
303
- ElementDeletedApiError: If relation has been deleted
304
- ElementNotFoundApiError: If relation cannot be found
305
"""
306
```
307
308
### Recursive Relation Full Data
309
310
Retrieve relation with all referenced elements at all levels (recursive).
311
312
```python { .api }
313
def RelationFullRecur(RelationId):
314
"""
315
Returns the full data (all levels) for relation RelationId as list of dicts.
316
317
Parameters:
318
- RelationId (int): Unique identifier of the relation
319
320
Returns:
321
list[dict]: List of elements with type and data keys, including
322
the relation itself and all recursively referenced elements
323
324
Raises:
325
- ElementDeletedApiError: If any relation has been deleted
326
- ElementNotFoundApiError: If relation cannot be found
327
"""
328
```
329
330
**Usage Example:**
331
332
```python
333
import osmapi
334
335
api = osmapi.OsmApi()
336
337
# Get relation with two levels of data
338
full_data = api.RelationFull(789)
339
340
relation_data = None
341
nodes_data = []
342
ways_data = []
343
child_relations_data = []
344
345
for element in full_data:
346
if element['type'] == 'relation':
347
if element['data']['id'] == 789:
348
relation_data = element['data']
349
else:
350
child_relations_data.append(element['data'])
351
elif element['type'] == 'way':
352
ways_data.append(element['data'])
353
elif element['type'] == 'node':
354
nodes_data.append(element['data'])
355
356
print(f"Relation {relation_data['id']}: {relation_data['tag'].get('name', 'Unnamed')}")
357
print(f"Contains {len(nodes_data)} nodes, {len(ways_data)} ways, {len(child_relations_data)} relations")
358
359
# For complex nested relations, use recursive version
360
if child_relations_data:
361
print("Getting recursive data for complex relation...")
362
recursive_data = api.RelationFullRecur(789)
363
print(f"Recursive data contains {len(recursive_data)} total elements")
364
```
365
366
### Bulk Relation Operations
367
368
Retrieve multiple relations in a single API call for efficiency.
369
370
```python { .api }
371
def RelationsGet(RelationIdList):
372
"""
373
Returns dict with relation IDs as keys for multiple relations.
374
375
Parameters:
376
- RelationIdList (list[int]): List of relation IDs to retrieve
377
378
Returns:
379
dict: Relation IDs as keys with RelationData dicts as values
380
"""
381
```
382
383
**Usage Example:**
384
385
```python
386
import osmapi
387
388
api = osmapi.OsmApi()
389
390
# Get multiple relations efficiently
391
relation_ids = [789, 101112, 131415, 161718]
392
relations = api.RelationsGet(relation_ids)
393
394
for relation_id, relation_data in relations.items():
395
relation_type = relation_data['tag'].get('type', 'unknown')
396
name = relation_data['tag'].get('name', 'Unnamed')
397
print(f"Relation {relation_id}: {relation_type} - {name}")
398
print(f" Members: {len(relation_data['member'])}")
399
400
# Check for missing relations
401
found_ids = set(relations.keys())
402
missing_ids = set(relation_ids) - found_ids
403
if missing_ids:
404
print(f"Missing relations: {missing_ids}")
405
```
406
407
## Relation Data Structure
408
409
### RelationData Dictionary
410
411
```python { .api }
412
RelationData = {
413
'id': int, # Relation ID (assigned by OSM after creation)
414
'member': list[dict], # List of member objects (required)
415
'tag': dict, # Key-value pairs for attributes
416
'version': int, # Version number (starts at 1)
417
'changeset': int, # ID of changeset that last modified this relation
418
'user': str, # Username of last editor
419
'uid': int, # User ID of last editor
420
'timestamp': str, # ISO timestamp of last modification
421
'visible': bool # True if visible, False if deleted
422
}
423
424
RelationMember = {
425
'type': str, # 'node', 'way', or 'relation'
426
'ref': int, # Referenced element ID
427
'role': str # Role description (can be empty string)
428
}
429
```
430
431
## Common Relation Types
432
433
### Multipolygon Relations
434
Complex areas with holes or multiple parts.
435
436
```python
437
multipolygon_relation = {
438
"member": [
439
{"type": "way", "ref": outer_way_id, "role": "outer"},
440
{"type": "way", "ref": inner_way_id, "role": "inner"}
441
],
442
"tag": {
443
"type": "multipolygon",
444
"natural": "forest",
445
"name": "Central Forest Reserve"
446
}
447
}
448
```
449
450
### Route Relations
451
Transportation or hiking routes.
452
453
```python
454
bus_route_relation = {
455
"member": [
456
{"type": "node", "ref": stop1_id, "role": "stop"},
457
{"type": "way", "ref": way1_id, "role": ""},
458
{"type": "node", "ref": stop2_id, "role": "stop"},
459
{"type": "way", "ref": way2_id, "role": ""}
460
],
461
"tag": {
462
"type": "route",
463
"route": "bus",
464
"name": "Bus Line 42",
465
"ref": "42",
466
"operator": "Metro Transit"
467
}
468
}
469
```
470
471
### Boundary Relations
472
Administrative or other boundaries.
473
474
```python
475
boundary_relation = {
476
"member": [
477
{"type": "way", "ref": boundary_way1_id, "role": "outer"},
478
{"type": "way", "ref": boundary_way2_id, "role": "outer"}
479
],
480
"tag": {
481
"type": "boundary",
482
"boundary": "administrative",
483
"admin_level": "6",
484
"name": "Example County"
485
}
486
}
487
```
488
489
## Relation Best Practices
490
491
### Member Ordering
492
- Order members logically (e.g., stops along a route)
493
- For multipolygons, list outer ways first, then inner ways
494
- Maintain consistent member ordering across versions
495
496
### Role Assignment
497
- Use standard roles for relation types
498
- Empty roles are valid and often used
499
- Document custom roles clearly in relation tags
500
501
### Nested Relations
502
- Avoid deeply nested relation hierarchies
503
- Use RelationFullRecur() for complex nested structures
504
- Be careful with circular references
505
506
## Error Handling
507
508
Relation operations can raise various exceptions:
509
510
```python
511
import osmapi
512
513
api = osmapi.OsmApi(username="user", password="pass")
514
515
try:
516
relation = api.RelationGet(999999)
517
except osmapi.ElementNotFoundApiError:
518
print("Relation does not exist")
519
except osmapi.ElementDeletedApiError:
520
print("Relation has been deleted")
521
522
try:
523
with api.Changeset({"comment": "Test"}) as changeset_id:
524
# This will fail if referenced elements don't exist
525
api.RelationCreate({
526
"member": [
527
{"type": "node", "ref": 999999, "role": "stop"} # Non-existent node
528
],
529
"tag": {"type": "route", "route": "bus"}
530
})
531
except osmapi.PreconditionFailedApiError:
532
print("Referenced elements don't exist or are not visible")
533
except osmapi.VersionMismatchApiError:
534
print("Version conflict - relation was modified by another user")
535
```