osmexp 0.2.0__tar.gz

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-0.2.0/LICENSE ADDED
@@ -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.
osmexp-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: osmexp
3
+ Version: 0.2.0
4
+ Summary: OpenStreetMap GeoJSON Exporter
5
+ Author-email: Ronaldo Ferreira <ronaldo@racum.com>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Source, https://github.com/racum/osmexp
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Natural Language :: English
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests
19
+ Requires-Dist: shapely
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-recording; extra == "dev"
23
+ Requires-Dist: vcrpy; extra == "dev"
24
+ Requires-Dist: ruff; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # osmexp - OpenStreetMap GeoJSON Exporter
28
+
29
+ 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.
30
+
31
+ ## Installation:
32
+
33
+ Make sure you have at least Python 3.11, and install it with:
34
+
35
+ ```
36
+ $ pip install osmexp
37
+ ```
38
+
39
+ The only requirements are `Requests` and `Shapely`.
40
+
41
+ ## Usage
42
+
43
+ For all exports, this is the format:
44
+
45
+ ```
46
+ $ osmexp [element_type] [element_id]
47
+ ```
48
+
49
+ 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).
50
+
51
+ The output is set to `STDOUT`, and it is recommended to be piped into a file or another command. On failure, an error message is sent to `STDERR` and the process exits with a non-zero status.
52
+
53
+ ### Exporting Nodes:
54
+
55
+ ```
56
+ $ osmexp node 9589344640 > outdoor_seating.geojson
57
+ ```
58
+
59
+ This returns a GeoJSON with a `Point` as a root type.
60
+
61
+ ### Exporting Ways:
62
+
63
+ ```
64
+ $ osmexp way 398987317 > canal_grande.geojson
65
+ ```
66
+
67
+ This returns a GeoJSON with a `LineString` as a root type if the line is “open” or `Polygon` if the line is “closed”.
68
+
69
+ ### Exporting Relations:
70
+
71
+ ```
72
+ $ osmexp rel 4817103 > venice.geojson
73
+ ```
74
+
75
+ 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).
76
+
77
+ ## Development
78
+
79
+ Clone the repository and install in editable mode with the `dev` extras (tests, linter):
80
+
81
+ ```
82
+ $ pip install -e '.[dev]'
83
+ ```
84
+
85
+ All dependencies are declared in `pyproject.toml`; there are no separate requirements files.
86
+
87
+ Run the test suite (it replays recorded HTTP responses, so no network is needed):
88
+
89
+ ```
90
+ $ pytest
91
+ ```
92
+
93
+ Lint and format with [Ruff](https://docs.astral.sh/ruff/):
94
+
95
+ ```
96
+ $ ruff check .
97
+ $ ruff format .
98
+ ```
99
+
100
+ ## License
101
+
102
+ This library is released under the **3-Clause BSD License**.
103
+
104
+ **tl;dr**: *"free to use as long as you credit me"*.
osmexp-0.2.0/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # osmexp - OpenStreetMap GeoJSON Exporter
2
+
3
+ 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.
4
+
5
+ ## Installation:
6
+
7
+ Make sure you have at least Python 3.11, and install it with:
8
+
9
+ ```
10
+ $ pip install osmexp
11
+ ```
12
+
13
+ The only requirements are `Requests` and `Shapely`.
14
+
15
+ ## Usage
16
+
17
+ For all exports, this is the format:
18
+
19
+ ```
20
+ $ osmexp [element_type] [element_id]
21
+ ```
22
+
23
+ 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).
24
+
25
+ The output is set to `STDOUT`, and it is recommended to be piped into a file or another command. On failure, an error message is sent to `STDERR` and the process exits with a non-zero status.
26
+
27
+ ### Exporting Nodes:
28
+
29
+ ```
30
+ $ osmexp node 9589344640 > outdoor_seating.geojson
31
+ ```
32
+
33
+ This returns a GeoJSON with a `Point` as a root type.
34
+
35
+ ### Exporting Ways:
36
+
37
+ ```
38
+ $ osmexp way 398987317 > canal_grande.geojson
39
+ ```
40
+
41
+ This returns a GeoJSON with a `LineString` as a root type if the line is “open” or `Polygon` if the line is “closed”.
42
+
43
+ ### Exporting Relations:
44
+
45
+ ```
46
+ $ osmexp rel 4817103 > venice.geojson
47
+ ```
48
+
49
+ 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).
50
+
51
+ ## Development
52
+
53
+ Clone the repository and install in editable mode with the `dev` extras (tests, linter):
54
+
55
+ ```
56
+ $ pip install -e '.[dev]'
57
+ ```
58
+
59
+ All dependencies are declared in `pyproject.toml`; there are no separate requirements files.
60
+
61
+ Run the test suite (it replays recorded HTTP responses, so no network is needed):
62
+
63
+ ```
64
+ $ pytest
65
+ ```
66
+
67
+ Lint and format with [Ruff](https://docs.astral.sh/ruff/):
68
+
69
+ ```
70
+ $ ruff check .
71
+ $ ruff format .
72
+ ```
73
+
74
+ ## License
75
+
76
+ This library is released under the **3-Clause BSD License**.
77
+
78
+ **tl;dr**: *"free to use as long as you credit me"*.
File without changes
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from .geojson import ExportError, export_node, export_relation, export_way
7
+
8
+ EXPORTERS = {
9
+ 'node': export_node,
10
+ 'way': export_way,
11
+ 'rel': export_relation,
12
+ }
13
+
14
+
15
+ def main() -> None:
16
+ parser = argparse.ArgumentParser(
17
+ prog='osmexp',
18
+ description='Export an OpenStreetMap element as GeoJSON to STDOUT.',
19
+ )
20
+ parser.add_argument('element', choices=EXPORTERS, help='The OSM element type.')
21
+ parser.add_argument('id', type=int, help='The OSM element ID (an integer).')
22
+ args = parser.parse_args()
23
+
24
+ try:
25
+ print(EXPORTERS[args.element](args.id))
26
+ except ExportError as error:
27
+ print(error, file=sys.stderr)
28
+ sys.exit(1)
29
+
30
+
31
+ if __name__ == '__main__':
32
+ main()
@@ -0,0 +1,87 @@
1
+ import itertools
2
+
3
+ import requests
4
+ from shapely import get_parts, polygonize_full
5
+ from shapely.geometry import GeometryCollection, LineString, Point, Polygon
6
+
7
+ API_URL = 'https://api.openstreetmap.org/api/0.6'
8
+ USER_AGENT = 'osmexp/0.2.0 (+https://github.com/racum/osmexp)'
9
+ TIMEOUT = 30
10
+ MIN_LINESTRING_NODES = 2
11
+
12
+
13
+ def fetch_elements(path: str) -> list[dict] | None:
14
+ "Fetches an OSM API endpoint and returns its `elements`, or `None` on any failure."
15
+ try:
16
+ response = requests.get(
17
+ f'{API_URL}/{path}',
18
+ headers={'User-Agent': USER_AGENT},
19
+ timeout=TIMEOUT,
20
+ )
21
+ response.raise_for_status()
22
+ elements = response.json().get('elements')
23
+ except (requests.RequestException, ValueError):
24
+ return None
25
+ return elements or None
26
+
27
+
28
+ def parse_geometries(elements: list[dict]) -> tuple[dict[int, Point], dict[int, LineString]]:
29
+ "Builds Shapely geometries keyed by OSM id: nodes as `Point`, ways as `LineString`."
30
+ nodes = {e['id']: Point(e['lon'], e['lat']) for e in elements if e['type'] == 'node'}
31
+ ways = {
32
+ e['id']: LineString([nodes[n] for n in e['nodes']])
33
+ for e in elements
34
+ if e['type'] == 'way' and len(e['nodes']) >= MIN_LINESTRING_NODES
35
+ }
36
+ return nodes, ways
37
+
38
+
39
+ def fetch_node(node_id: int) -> Point | None:
40
+ elements = fetch_elements(f'node/{node_id}.json')
41
+ if elements is None:
42
+ return None
43
+ nodes, _ = parse_geometries(elements)
44
+ return next(iter(nodes.values()), None)
45
+
46
+
47
+ def fetch_way(way_id: int) -> LineString | Polygon | None:
48
+ elements = fetch_elements(f'way/{way_id}/full.json')
49
+ if elements is None:
50
+ return None
51
+ _, ways = parse_geometries(elements)
52
+ way = next(iter(ways.values()), None)
53
+ if way is None:
54
+ return None
55
+ return Polygon(way) if way.is_ring else way
56
+
57
+
58
+ def fetch_relation(relation_id: int) -> GeometryCollection | None:
59
+ elements = fetch_elements(f'relation/{relation_id}/full.json')
60
+ if elements is None:
61
+ return None
62
+ _, ways = parse_geometries(elements)
63
+ relation = next((e for e in elements if e['type'] == 'relation'), None)
64
+ if relation is None:
65
+ return None
66
+
67
+ outer, inner = [], []
68
+ for member in relation['members']:
69
+ if member['type'] != 'way':
70
+ continue
71
+ line = ways.get(member['ref'])
72
+ if line is None:
73
+ continue
74
+ (inner if member['role'] == 'inner' else outer).append(line)
75
+
76
+ outer_polygons, *outer_leftovers = polygonize_full(outer)
77
+ inner_rings = [p.exterior for p in get_parts(polygonize_full(inner)[0])]
78
+
79
+ parts = []
80
+ for polygon in get_parts(outer_polygons):
81
+ holes = [ring for ring in inner_rings if polygon.contains(ring)]
82
+ parts.append(Polygon(polygon.exterior, holes) if holes else polygon)
83
+
84
+ # Ways that could not be assembled into polygons (e.g. self-crossing) stay as lines.
85
+ parts.extend(itertools.chain.from_iterable(get_parts(leftover) for leftover in outer_leftovers))
86
+
87
+ return GeometryCollection(parts)
@@ -0,0 +1,55 @@
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
+ class ExportError(Exception):
12
+ "Raised when an OSM element cannot be fetched or exported."
13
+
14
+
15
+ def right_hand_rule(geometry: BaseGeometry) -> BaseGeometry:
16
+ 'Enforces the "right hand rule" as defined by the GeoJSON spec (RFC-7946, Section 3.1.6).'
17
+ if geometry.geom_type == 'Polygon':
18
+ return orient(geometry, sign=1)
19
+ if geometry.geom_type == 'MultiPolygon':
20
+ return MultiPolygon([right_hand_rule(p) for p in geometry.geoms])
21
+ return geometry
22
+
23
+
24
+ def export_node(node_id: int) -> str:
25
+ node = fetch_node(node_id)
26
+ if node is None:
27
+ raise ExportError(f'Could not export node {node_id}.')
28
+ return to_geojson(node)
29
+
30
+
31
+ def export_way(way_id: int) -> str:
32
+ way = fetch_way(way_id)
33
+ if way is None:
34
+ raise ExportError(f'Could not export way {way_id}.')
35
+ return to_geojson(right_hand_rule(way))
36
+
37
+
38
+ def wrap_feature(part: BaseGeometry) -> dict:
39
+ return {
40
+ 'type': 'Feature',
41
+ 'properties': {},
42
+ 'geometry': json.loads(to_geojson(right_hand_rule(part))),
43
+ }
44
+
45
+
46
+ def export_relation(relation_id: int) -> str:
47
+ relation = fetch_relation(relation_id)
48
+ if relation is None:
49
+ raise ExportError(f'Could not export relation {relation_id}.')
50
+ return json.dumps(
51
+ {
52
+ 'type': 'FeatureCollection',
53
+ 'features': [wrap_feature(p) for p in get_parts(relation)],
54
+ }
55
+ )
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: osmexp
3
+ Version: 0.2.0
4
+ Summary: OpenStreetMap GeoJSON Exporter
5
+ Author-email: Ronaldo Ferreira <ronaldo@racum.com>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Source, https://github.com/racum/osmexp
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Natural Language :: English
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests
19
+ Requires-Dist: shapely
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-recording; extra == "dev"
23
+ Requires-Dist: vcrpy; extra == "dev"
24
+ Requires-Dist: ruff; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # osmexp - OpenStreetMap GeoJSON Exporter
28
+
29
+ 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.
30
+
31
+ ## Installation:
32
+
33
+ Make sure you have at least Python 3.11, and install it with:
34
+
35
+ ```
36
+ $ pip install osmexp
37
+ ```
38
+
39
+ The only requirements are `Requests` and `Shapely`.
40
+
41
+ ## Usage
42
+
43
+ For all exports, this is the format:
44
+
45
+ ```
46
+ $ osmexp [element_type] [element_id]
47
+ ```
48
+
49
+ 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).
50
+
51
+ The output is set to `STDOUT`, and it is recommended to be piped into a file or another command. On failure, an error message is sent to `STDERR` and the process exits with a non-zero status.
52
+
53
+ ### Exporting Nodes:
54
+
55
+ ```
56
+ $ osmexp node 9589344640 > outdoor_seating.geojson
57
+ ```
58
+
59
+ This returns a GeoJSON with a `Point` as a root type.
60
+
61
+ ### Exporting Ways:
62
+
63
+ ```
64
+ $ osmexp way 398987317 > canal_grande.geojson
65
+ ```
66
+
67
+ This returns a GeoJSON with a `LineString` as a root type if the line is “open” or `Polygon` if the line is “closed”.
68
+
69
+ ### Exporting Relations:
70
+
71
+ ```
72
+ $ osmexp rel 4817103 > venice.geojson
73
+ ```
74
+
75
+ 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).
76
+
77
+ ## Development
78
+
79
+ Clone the repository and install in editable mode with the `dev` extras (tests, linter):
80
+
81
+ ```
82
+ $ pip install -e '.[dev]'
83
+ ```
84
+
85
+ All dependencies are declared in `pyproject.toml`; there are no separate requirements files.
86
+
87
+ Run the test suite (it replays recorded HTTP responses, so no network is needed):
88
+
89
+ ```
90
+ $ pytest
91
+ ```
92
+
93
+ Lint and format with [Ruff](https://docs.astral.sh/ruff/):
94
+
95
+ ```
96
+ $ ruff check .
97
+ $ ruff format .
98
+ ```
99
+
100
+ ## License
101
+
102
+ This library is released under the **3-Clause BSD License**.
103
+
104
+ **tl;dr**: *"free to use as long as you credit me"*.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ osmexp/__init__.py
5
+ osmexp/__main__.py
6
+ osmexp/fetcher.py
7
+ osmexp/geojson.py
8
+ osmexp.egg-info/PKG-INFO
9
+ osmexp.egg-info/SOURCES.txt
10
+ osmexp.egg-info/dependency_links.txt
11
+ osmexp.egg-info/entry_points.txt
12
+ osmexp.egg-info/requires.txt
13
+ osmexp.egg-info/top_level.txt
14
+ tests/test_fetcher.py
15
+ tests/test_geojson.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ osmexp = osmexp.__main__:main
@@ -0,0 +1,8 @@
1
+ requests
2
+ shapely
3
+
4
+ [dev]
5
+ pytest
6
+ pytest-recording
7
+ vcrpy
8
+ ruff
@@ -0,0 +1 @@
1
+ osmexp
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "osmexp"
7
+ version = "0.2.0"
8
+ description = "OpenStreetMap GeoJSON Exporter"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Ronaldo Ferreira", email = "ronaldo@racum.com" }]
13
+ dependencies = ["requests", "shapely"]
14
+ classifiers = [
15
+ "Development Status :: 2 - Pre-Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Natural Language :: English",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ ]
23
+
24
+ [project.urls]
25
+ Source = "https://github.com/racum/osmexp"
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest", "pytest-recording", "vcrpy", "ruff"]
29
+
30
+ [project.scripts]
31
+ osmexp = "osmexp.__main__:main"
32
+
33
+ [tool.setuptools]
34
+ packages = ["osmexp"]
35
+
36
+ [tool.black]
37
+ line-length = 120
38
+ target-version = ['py311']
39
+ include = '\.pyi?$'
40
+ skip-string-normalization = true
41
+
42
+ [tool.ruff]
43
+ target-version = "py311"
44
+ line-length = 120
45
+ lint.select = ["E", "F", "TID", "I", "B", "PGH", "UP", "A", "C4", "PIE", "T20", "RSE", "RET", "SIM", "ARG", "PTH", "ERA", "PL", "RUF"]
46
+
47
+ [tool.ruff.format]
48
+ quote-style = "single"
49
+
50
+ [tool.ruff.lint.per-file-ignores]
51
+ "tests/*" = ["PLR2004"]
52
+ "osmexp/__main__.py" = ["PLR2004", "T201"]
53
+
54
+ [[tool.mypy.overrides]]
55
+ module = "shapely.*"
56
+ ignore_missing_imports = true
57
+
58
+ [tool.pytest.ini_options]
59
+ addopts = "--record-mode=none"
osmexp-0.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,52 @@
1
+ import pytest
2
+
3
+ from osmexp.fetcher import fetch_node, fetch_relation, fetch_way
4
+
5
+
6
+ @pytest.mark.vcr()
7
+ def test_fetch_node_as_point():
8
+ node = fetch_node(9589344640) # Outdoor seating.
9
+ assert node.geom_type == 'Point'
10
+ assert (node.x, node.y) == (12.3394685, 45.4331611)
11
+
12
+
13
+ @pytest.mark.vcr()
14
+ def test_fetch_open_way_as_linestring():
15
+ way = fetch_way(398987317) # Canal Grande.
16
+ assert way.geom_type == 'LineString'
17
+ assert len(way.coords) == 125
18
+ assert way.length == pytest.approx(0.049109547915808335) # Point units, not meters.
19
+
20
+
21
+ @pytest.mark.vcr()
22
+ def test_fetch_closed_way_as_polygon():
23
+ way = fetch_way(430963095) # Column of Saint Mark.
24
+ assert way.geom_type == 'Polygon'
25
+ assert way.area == pytest.approx(3.089739999806592e-09) # Point units, not meters.
26
+
27
+
28
+ @pytest.mark.vcr()
29
+ def test_fetch_single_relation_as_polygon():
30
+ rel = fetch_relation(4817103) # Venice.
31
+ assert rel.geom_type == 'GeometryCollection'
32
+ assert rel.geoms[0].geom_type == 'Polygon'
33
+ assert len(rel.geoms[0].exterior.coords) == 849
34
+ assert rel.geoms[0].area == pytest.approx(0.0006893646889149643) # Point units, not meters.
35
+
36
+
37
+ @pytest.mark.vcr()
38
+ def test_fetch_split_relation_as_many_polygons():
39
+ rel = fetch_relation(1850539) # Parque Estadual Paulo César Vinha
40
+ assert rel.geom_type == 'GeometryCollection'
41
+ assert len(rel.geoms) == 9
42
+ assert {g.geom_type for g in rel.geoms} == {'Polygon'}
43
+ assert sum(g.area for g in rel.geoms) == pytest.approx(0.0013636596847249436)
44
+
45
+
46
+ @pytest.mark.vcr()
47
+ def test_fetch_self_crossing_relation_as_many_linestrings():
48
+ rel = fetch_relation(284570) # Suzuka
49
+ assert rel.geom_type == 'GeometryCollection'
50
+ assert len(rel.geoms) == 2
51
+ assert {g.geom_type for g in rel.geoms} == {'LineString'}
52
+ assert sum(g.area for g in rel.geoms) == 0
@@ -0,0 +1,48 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from osmexp.geojson import export_node, export_relation, export_way
6
+
7
+
8
+ @pytest.mark.vcr()
9
+ def test_export_node():
10
+ geojson = json.loads(export_node(9589344640))
11
+ assert geojson['type'] == 'Point'
12
+ assert geojson['coordinates'] == [12.3394685, 45.4331611]
13
+
14
+
15
+ @pytest.mark.vcr()
16
+ def test_export_open_way():
17
+ geojson = json.loads(export_way(398987317))
18
+ assert geojson['type'] == 'LineString'
19
+
20
+
21
+ @pytest.mark.vcr()
22
+ def test_export_closed_way():
23
+ geojson = json.loads(export_way(430963095))
24
+ assert geojson['type'] == 'Polygon'
25
+
26
+
27
+ @pytest.mark.vcr()
28
+ def test_export_single_relation():
29
+ geojson = json.loads(export_relation(4817103))
30
+ assert geojson['type'] == 'FeatureCollection'
31
+ assert len(geojson['features']) == 1
32
+ assert {f['geometry']['type'] for f in geojson['features']} == {'Polygon'}
33
+
34
+
35
+ @pytest.mark.vcr()
36
+ def test_export_split_relation():
37
+ geojson = json.loads(export_relation(1850539))
38
+ assert geojson['type'] == 'FeatureCollection'
39
+ assert len(geojson['features']) == 9
40
+ assert {f['geometry']['type'] for f in geojson['features']} == {'Polygon'}
41
+
42
+
43
+ @pytest.mark.vcr()
44
+ def test_export_self_crossing_relation():
45
+ geojson = json.loads(export_relation(284570))
46
+ assert geojson['type'] == 'FeatureCollection'
47
+ assert len(geojson['features']) == 2
48
+ assert {f['geometry']['type'] for f in geojson['features']} == {'LineString'}