osmexp 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osmexp/__init__.py +0 -0
- osmexp/__main__.py +44 -0
- osmexp/fetcher.py +40 -0
- osmexp/geojson.py +45 -0
- osmexp-0.1.0.dist-info/LICENSE +26 -0
- osmexp-0.1.0.dist-info/METADATA +76 -0
- osmexp-0.1.0.dist-info/RECORD +10 -0
- osmexp-0.1.0.dist-info/WHEEL +5 -0
- osmexp-0.1.0.dist-info/entry_points.txt +2 -0
- osmexp-0.1.0.dist-info/top_level.txt +1 -0
osmexp/__init__.py
ADDED
|
File without changes
|
osmexp/__main__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from .geojson import export_node, export_relation, export_way
|
|
6
|
+
|
|
7
|
+
HELP = """
|
|
8
|
+
Usage: osmexp [element] [id]
|
|
9
|
+
|
|
10
|
+
Arguments:
|
|
11
|
+
element: The OSM element type, options: "rel", "way" or "node".
|
|
12
|
+
id: The OSM element ID: an integer number.
|
|
13
|
+
|
|
14
|
+
Output: A GeoJSON representation of the element.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
|
|
19
|
+
if len(sys.argv) != 3 or (len(sys.argv) == 2 and sys.argv[1] in ['-h', '--help']):
|
|
20
|
+
print(HELP)
|
|
21
|
+
sys.exit()
|
|
22
|
+
|
|
23
|
+
if sys.argv[1].lower() not in ['rel', 'way', 'node']:
|
|
24
|
+
print('Unknown element type, please use "rel", "way" or "node".')
|
|
25
|
+
sys.exit()
|
|
26
|
+
|
|
27
|
+
if not sys.argv[2].isdigit():
|
|
28
|
+
print('Element ID should be an integer.')
|
|
29
|
+
sys.exit()
|
|
30
|
+
|
|
31
|
+
element_type = sys.argv[1].lower()
|
|
32
|
+
element_id = sys.argv[2]
|
|
33
|
+
|
|
34
|
+
match element_type:
|
|
35
|
+
case 'node':
|
|
36
|
+
print(export_node(element_id))
|
|
37
|
+
case 'way':
|
|
38
|
+
print(export_way(element_id))
|
|
39
|
+
case 'rel':
|
|
40
|
+
print(export_relation(element_id))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
main()
|
osmexp/fetcher.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from shapely import get_parts, polygonize_full
|
|
6
|
+
from shapely.geometry import GeometryCollection, LineString, Point, Polygon
|
|
7
|
+
|
|
8
|
+
API_URL = 'https://www.openstreetmap.org/api/0.6'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_nodes_ways(elements: list[dict]) -> tuple[list[dict], list[dict]]:
|
|
12
|
+
nodes = {e['id']: Point(e['lon'], e['lat']) for e in elements if e['type'] == 'node'}
|
|
13
|
+
ways = [LineString([nodes[n] for n in e['nodes']]) for e in elements if e['type'] == 'way']
|
|
14
|
+
return list(nodes.values()), ways
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fetch_node(node_id: int) -> Point | None:
|
|
18
|
+
response = requests.get(f'{API_URL}/node/{node_id}.json')
|
|
19
|
+
if response.status_code != HTTPStatus.OK:
|
|
20
|
+
return None
|
|
21
|
+
return parse_nodes_ways(response.json()['elements'])[0][0]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def fetch_way(way_id: int) -> LineString | Polygon | None:
|
|
25
|
+
response = requests.get(f'{API_URL}/way/{way_id}/full.json')
|
|
26
|
+
if response.status_code != HTTPStatus.OK:
|
|
27
|
+
return None
|
|
28
|
+
_, ways = parse_nodes_ways(response.json()['elements'])
|
|
29
|
+
way = ways[0]
|
|
30
|
+
return Polygon(way) if way.is_closed and way.is_ring else way
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fetch_relation(relation_id: int) -> GeometryCollection | None:
|
|
34
|
+
response = requests.get(f'{API_URL}/relation/{relation_id}/full.json')
|
|
35
|
+
if response.status_code != HTTPStatus.OK:
|
|
36
|
+
return None
|
|
37
|
+
_, ways = parse_nodes_ways(response.json()['elements'])
|
|
38
|
+
cases = [get_parts(p) for p in polygonize_full(ways) if p]
|
|
39
|
+
parts = list(itertools.chain(*cases))
|
|
40
|
+
return GeometryCollection(parts)
|
osmexp/geojson.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from shapely import get_parts, to_geojson
|
|
4
|
+
from shapely.geometry import MultiPolygon
|
|
5
|
+
from shapely.geometry.base import BaseGeometry
|
|
6
|
+
from shapely.geometry.polygon import orient
|
|
7
|
+
|
|
8
|
+
from .fetcher import fetch_node, fetch_relation, fetch_way
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def right_hand_rule(geometry: BaseGeometry) -> BaseGeometry:
|
|
12
|
+
'Enforces the "right hand rule" as defined by the GeoJSON spec (RFC-7946, Section 3.1.6).'
|
|
13
|
+
if geometry.geom_type == 'Polygon':
|
|
14
|
+
if not geometry.exterior.is_ccw:
|
|
15
|
+
return orient(geometry, sign=1)
|
|
16
|
+
return geometry
|
|
17
|
+
if geometry.geom_type == 'MultiPolygon':
|
|
18
|
+
return MultiPolygon([right_hand_rule(p) for p in geometry.geoms])
|
|
19
|
+
return geometry
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def export_node(node_id) -> str:
|
|
23
|
+
if node := fetch_node(node_id):
|
|
24
|
+
return to_geojson(node)
|
|
25
|
+
return f'Error exporting node {node_id}.'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def export_way(way_id) -> str:
|
|
29
|
+
if way := fetch_way(way_id):
|
|
30
|
+
return to_geojson(right_hand_rule(way))
|
|
31
|
+
return f'Error exporting way {way_id}.'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def wrap_feature(part):
|
|
35
|
+
return {
|
|
36
|
+
'type': 'Feature',
|
|
37
|
+
'properties': {},
|
|
38
|
+
'geometry': json.loads(to_geojson(right_hand_rule(part))),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def export_relation(relation_id) -> str:
|
|
43
|
+
if rel := fetch_relation(relation_id):
|
|
44
|
+
return json.dumps({'type': 'FeatureCollection', 'features': [wrap_feature(p) for p in get_parts(rel)]})
|
|
45
|
+
return f'Error exporting relation {relation_id}.'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
3-Clause BSD License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015, Ronaldo "Racum" Ferreira
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
* Redistributions of source code must retain the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
* Neither the name of Ronaldo "Racum" Ferreira nor the
|
|
14
|
+
names of its contributors may be used to endorse or promote products
|
|
15
|
+
derived from this software without specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
18
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
19
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL RONALDO "RACUM" FERREIRA BE LIABLE FOR ANY
|
|
21
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
22
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
23
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
24
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
25
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: osmexp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenStreetMap GeoJSON Exporter
|
|
5
|
+
Home-page: https://github.com/racum/osmexp
|
|
6
|
+
Author: Ronaldo Ferreira
|
|
7
|
+
Author-email: ronaldo@racum.com
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
Project-URL: Source, https://github.com/racum/osmexp
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown; charset=UTF-8
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: shapely
|
|
21
|
+
|
|
22
|
+
# osmexp - OpenStreetMap GeoJSON Exporter
|
|
23
|
+
|
|
24
|
+
The [OSM API](https://wiki.openstreetmap.org/wiki/API_v0.6) uses a specific format for their geometric primitive types based on nodes, ways and relations; and those unfortunately don’t translate 1:1 with [GeoJSON types](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1), thus a conversion is needed,
|
|
25
|
+
|
|
26
|
+
## Installation:
|
|
27
|
+
|
|
28
|
+
Make sure have at least Python 3.11, and install it with:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ pip install osmexp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The only requirements are `Requests` and `Shapely`.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
For all exports, this is the format:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
$ osmexp [element_type] [element_id]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Where `element_type` can be `node`, `way` or `rel` (for "relation"). And the ID is the numerical identifier used by OSM, if you don’t know how to get the OSM ID, [follow this article](https://racum.blog/articles/osm-to-geojson/#features-on-openstreetmap).
|
|
45
|
+
|
|
46
|
+
The output is set to `STDOUT`, and it is recommended to be piped into a file or another command.
|
|
47
|
+
|
|
48
|
+
### Exporting Nodes:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
$ osmexp node 9589344640 > outdoor_seating.geojson
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This returns a GeoJSON with a `Point` as a root type.
|
|
55
|
+
|
|
56
|
+
### Exporting Ways:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ osmexp way 398987317 > canal_grande.geojson
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This returns a GeoJSON with a `LineString` as a root type if the the line is “open” or `Polygon` if the line is “closed”.
|
|
63
|
+
|
|
64
|
+
### Exporting Relations:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
$ osmexp rel 4817103 > venice.geojson
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This returns a GeoJSON with a `FeatureCollection` as a root, and the internal features as `LineString` or `Polygon`, depending if they can self-close, or fail the polygon transformation by other means (self crossing, etc).
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
This library is released under the **3-Clause BSD License**.
|
|
75
|
+
|
|
76
|
+
**tl;dr**: *"free to use as long as you credit me"*.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
osmexp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
osmexp/__main__.py,sha256=RYSrs39Rr7fsQGIuGBuiRltw79Vy3F3E_qgZff1T9xE,1046
|
|
3
|
+
osmexp/fetcher.py,sha256=LJ5LLUm1DaHigJGwY9IsMvwaTi4_AoJE3beL0IBRS_8,1526
|
|
4
|
+
osmexp/geojson.py,sha256=MFpYErc5qchHH15YtnvT2wolmBs_LWLmy9wcyPqv3zQ,1418
|
|
5
|
+
osmexp-0.1.0.dist-info/LICENSE,sha256=vY_J4si56jceKgBxnf5EENVfNVQ4vdXeb9h3BCR-3Is,1544
|
|
6
|
+
osmexp-0.1.0.dist-info/METADATA,sha256=u06mp2Fnn_2HlIWftxsCmrmNe0re1-RmS3gNtGsq3Zc,2433
|
|
7
|
+
osmexp-0.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
+
osmexp-0.1.0.dist-info/entry_points.txt,sha256=AGuz3edRWLmTiyA8RI1dw28Jt10QO57nguXtbRgp7Ns,48
|
|
9
|
+
osmexp-0.1.0.dist-info/top_level.txt,sha256=P2nAZvg0o-tTlV4-PuQ7nBP2PcEEk3DXRq9wSyn2rQM,7
|
|
10
|
+
osmexp-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
osmexp
|