├── .github └── workflows │ ├── pythonpackagetest.yml │ └── pythonpublish.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── MANIFEST.in ├── README.md ├── lint.sh ├── osm2geojson ├── __init__.py ├── __main__.py ├── areaKeys.json ├── helpers.py ├── main.py ├── parse_xml.py └── polygon-features.json ├── release.sh ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── data │ ├── barrier-wall.geojson │ ├── barrier-wall.osm │ ├── center-feature.geojson │ ├── center-feature.json │ ├── empty.geojson │ ├── empty.json │ ├── empty.osm │ ├── issue-16.geojson │ ├── issue-16.json │ ├── issue-35.geojson │ ├── issue-35.json │ ├── issue-4.geojson │ ├── issue-4.osm │ ├── issue-6.geojson │ ├── issue-6.json │ ├── issue-7.geojson │ ├── issue-7.json │ ├── issue-9-all.geojson │ ├── issue-9.geojson │ ├── issue-9.json │ ├── map.geojson │ ├── map.json │ ├── map.osm │ ├── meta.geojson │ ├── meta.json │ ├── node.geojson │ ├── node.json │ ├── node.osm │ ├── relation.geojson │ ├── relation.json │ ├── relation.osm │ ├── way.geojson │ ├── way.json │ └── way.osm ├── main.py └── parse_xml.py └── update-osm-poygon-features.sh /.github/workflows/pythonpackagetest.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Test package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | cache: 'pip' # Enables caching for dependencies 28 | 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install -r requirements.txt flake8 33 | 34 | - name: Lint with flake8 35 | run: | 36 | ./lint.sh 37 | 38 | - name: Run unittests 39 | run: | 40 | python -m unittest discover 41 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload package to PyPI 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v1 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | dist 3 | build 4 | *.egg-info 5 | .DS_Store 6 | *.pyc 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "osm-polygon-features"] 2 | path = osm-polygon-features 3 | url = https://github.com/tyrasd/osm-polygon-features 4 | [submodule "id-area-keys"] 5 | path = id-area-keys 6 | url = https://github.com/osmlab/id-area-keys 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.unittestArgs": ["discover"], 3 | "python.testing.pytestEnabled": false, 4 | "python.testing.nosetestsEnabled": false, 5 | "python.testing.unittestEnabled": true 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Parfeniuk Mykola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include osm2geojson/polygon-features.json 2 | include osm2geojson/areaKeys.json 3 | include requirements.txt 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osm2geojson 2 | 3 | ![Test package](https://github.com/aspectumapp/osm2geojson/workflows/Test%20package/badge.svg) 4 | 5 | Parse OSM and Overpass JSON with python. 6 | **This library is under development!** 7 | 8 | ### Usage 9 | 10 | Install this package with pip: 11 | 12 | ```sh 13 | $ pip install osm2geojson 14 | ``` 15 | 16 | If you want to convert OSM xml or Overpass json/xml to Geojson you can import this lib and use one of 4 methods: 17 | 18 | - `json2shapes(dict json_from_overpass)` - to convert Overpass json to \*Shape-objects 19 | - `xml2shapes(str xml_from_osm)` - to convert OSM xml or Overpass xml to \*Shape-objects 20 | - `json2geojson(dict json_from_overpass)` - to convert Overpass json to Geojson 21 | - `xml2geojson(str xml_from_osm)` - to convert OSM xml or Overpass xml to Geojson 22 | 23 | Additional parameters for all functions: 24 | 25 | - `filter_used_refs` - (default: `True`) defines geometry filtration strategy (will return all geometry if set as `False`) 26 | - `log_level` - (default: `'ERROR'`) controls logging level (will print all logs if set as `'INFO'`). More details [here](https://docs.python.org/3/library/logging.html#logging-levels) 27 | - `area_keys` - (default: `None`) defines which keys and values of an area should be saved from the list of OSM tags, see `areaKeys.json` for the defaults 28 | - `polygon_features` - (default: `None`) defines a whitelist/blacklist of features to be included in resulting polygons, see `polygon-features.json` for the defaults 29 | - `raise_on_failure` - (default: `False`) controls whether to throw an exception when geometry generation fails 30 | 31 | Other handy methods: 32 | 33 | - `overpass_call(str query)` - runs query to overpass-api.de server (retries 5 times in case of error). 34 | - `shape_to_feature(Shape shape, dict properties)` - Converts Shape-object to GeoJSON with passed properties. 35 | 36 | **\*Shape-object - for convenience created simple dict to save Shapely object (geometry) and OSM-properties. Structure of this object:** 37 | 38 | ```py 39 | shape_obj = { 40 | 'shape': Point | LineString | Polygon ..., 41 | 'properties': { 42 | 'type': 'relation' | 'node' ..., 43 | 'tags': { ... }, 44 | ... 45 | } 46 | } 47 | ``` 48 | 49 | After installing via `pip`, the module may also be used as a `argparse`-based command-line script. 50 | Either use the script name directly or call Python with the `-m` option and the package name: 51 | 52 | ```sh 53 | osm2geojson --help 54 | ``` 55 | 56 | ```sh 57 | python3 -m osm2geojson --help 58 | ``` 59 | 60 | ### Examples 61 | 62 | Convert OSM-xml to Geojson: 63 | 64 | ```py 65 | import codecs 66 | import osm2geojson 67 | 68 | with codecs.open('file.osm', 'r', encoding='utf-8') as data: 69 | xml = data.read() 70 | 71 | geojson = osm2geojson.xml2geojson(xml, filter_used_refs=False, log_level='INFO') 72 | # >> { "type": "FeatureCollection", "features": [ ... ] } 73 | ``` 74 | 75 | Convert OSM-json to Shape-objects: 76 | 77 | ```py 78 | import codecs 79 | import osm2geojson 80 | 81 | with codecs.open('file.json', 'r', encoding='utf-8') as data: 82 | json = data.read() 83 | 84 | shapes_with_props = osm2geojson.json2shapes(json) 85 | # >> [ { "shape": , "properties": {...} }, ... ] 86 | ``` 87 | 88 | ### Development 89 | 90 | Clone project with submodules 91 | 92 | ```sh 93 | $ git clone --recurse-submodules https://github.com/aspectumapp/osm2geojson.git 94 | ``` 95 | 96 | Setup package 97 | 98 | ```sh 99 | $ python setup.py develop 100 | ``` 101 | 102 | Run tests 103 | 104 | ```sh 105 | $ python -m unittest tests 106 | ``` 107 | 108 | Run single test 109 | 110 | ```sh 111 | $ python tests/main.py TestOsm2GeoJsonMethods.test_barrier_wall 112 | ``` 113 | 114 | Update osm-polygon-features to last version (if you want last version) 115 | 116 | ```sh 117 | $ ./update-osm-polygon-features.sh 118 | ``` 119 | 120 | ### ToDo 121 | 122 | * Rewrite _convert_shapes_to_multipolygon (and other multipolygon methods) to support complex situations with enclaves 123 | * Add tests and examples for cli tool 124 | * Add actions related to cli tool (more info https://github.com/aspectumapp/osm2geojson/pull/32#issuecomment-1073386381) 125 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | echo 'Main lint errors' 2 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 3 | 4 | echo 'All lint errors' 5 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 6 | -------------------------------------------------------------------------------- /osm2geojson/__init__.py: -------------------------------------------------------------------------------- 1 | from .parse_xml import parse as parse_xml 2 | from .helpers import read_data_file, overpass_call 3 | from .main import xml2geojson, json2geojson, xml2shapes, json2shapes, shape_to_feature 4 | -------------------------------------------------------------------------------- /osm2geojson/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import json 6 | import argparse 7 | 8 | from .main import json2geojson, xml2geojson 9 | 10 | 11 | def setup_parser() -> argparse.ArgumentParser: 12 | def file(v: str) -> str: 13 | if not os.path.exists(v): 14 | raise ValueError(v) 15 | return v 16 | 17 | parser = argparse.ArgumentParser(prog=__package__) 18 | parser.add_argument( 19 | "infile", 20 | type=file, 21 | help="OSM or Overpass JSON file to convert to GeoJSON" 22 | ) 23 | parser.add_argument( 24 | "outfile", 25 | help="write output of the processing to the specified file (uses stdout for '-')" 26 | ) 27 | parser.add_argument( 28 | "-f", 29 | "--force", 30 | action="store_true", 31 | help="allow overwriting of existing file" 32 | ) 33 | logging = parser.add_mutually_exclusive_group() 34 | logging.add_argument( 35 | "-q", 36 | "--quiet", 37 | action="store_true", 38 | help="suppress logging output" 39 | ) 40 | logging.add_argument( 41 | "-v", 42 | "--verbose", 43 | action="store_true", 44 | help="enable verbose logging output" 45 | ) 46 | parser.add_argument( 47 | "-i", 48 | "--indent", 49 | type=int, 50 | metavar="N", 51 | default=None, 52 | help="indentation using N spaces for the output file (defaults to none)" 53 | ) 54 | parser.add_argument( 55 | "--reader", 56 | choices=("json", "xml", "auto"), 57 | default="auto", 58 | help="specify the input file format (either OSM XML or Overpass JSON/XML), defaults to auto-detect" 59 | ) 60 | parser.add_argument( 61 | "--no-unused-filter", 62 | action="store_false", 63 | dest="filter_used_refs", 64 | help="don't filter unused references (only in shape JSON)" 65 | ) 66 | parser.add_argument( 67 | "--areas", 68 | type=file, 69 | default=None, 70 | metavar="file", 71 | help="JSON file defining the keys that should be included from areas (uses defaults if omitted)" 72 | ) 73 | parser.add_argument( 74 | "--polygons", 75 | type=file, 76 | default=None, 77 | metavar="file", 78 | help="JSON file defining the allowed/restricted polygon features (uses defaults if omitted)" 79 | ) 80 | return parser 81 | 82 | 83 | def main(args=None) -> int: 84 | args = args or sys.argv[1:] 85 | parser = setup_parser() 86 | args = parser.parse_args(args) 87 | 88 | if args.reader == "xml" or args.reader == "auto" and args.infile.endswith((".osm", ".xml")): 89 | parser_function = xml2geojson 90 | elif args.reader == "json" or args.reader == "auto" and args.infile.endswith(".json"): 91 | parser_function = json2geojson 92 | else: 93 | print("Auto-detecting input file format failed. Consider using --reader.", file=sys.stderr) 94 | return 1 95 | 96 | if args.outfile != "-" and os.path.exists(args.outfile) and not args.force: 97 | print( 98 | "Output file '{}' already exists. Consider using -f to force overwriting.".format(args.outfile), 99 | file=sys.stderr 100 | ) 101 | return 1 102 | 103 | with open(args.infile) as f: 104 | data = f.read() 105 | 106 | log_level = "WARNING" 107 | if args.quiet: 108 | log_level = "CRITICAL" 109 | elif args.verbose: 110 | log_level = "DEBUG" 111 | 112 | area_keys = None 113 | if args.areas: 114 | with open(args.areas) as f: 115 | area_keys = json.load(f) 116 | if "areaKeys" in area_keys and len(area_keys) == 1: 117 | area_keys = area_keys["areaKeys"] 118 | polygon_features = None 119 | if args.polygons: 120 | with open(args.polygons) as f: 121 | polygon_features = json.load(f) 122 | 123 | result = parser_function( 124 | data, 125 | filter_used_refs=args.filter_used_refs, 126 | log_level=log_level, 127 | area_keys=area_keys, 128 | polygon_features=polygon_features 129 | ) 130 | 131 | indent = args.indent 132 | if indent and indent < 0: 133 | indent = None 134 | if args.outfile == "-": 135 | target = sys.stdout 136 | else: 137 | target = open(args.outfile, "w") 138 | 139 | code = 0 140 | try: 141 | print(json.dumps(result, indent=indent), file=target) 142 | except (TypeError, ValueError) as exc: 143 | print(exc, file=sys.stderr) 144 | print("Falling back to raw dumping the object...", file=sys.stderr) 145 | print(result, file=target) 146 | code = 1 147 | 148 | if args.outfile != "-": 149 | target.flush() 150 | target.close() 151 | return code 152 | 153 | 154 | exit(main(sys.argv[1:])) 155 | -------------------------------------------------------------------------------- /osm2geojson/areaKeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "areaKeys": { 3 | "addr:*": { 4 | }, 5 | "advertising": { 6 | "billboard": true 7 | }, 8 | "aerialway": { 9 | "cable_car": true, 10 | "chair_lift": true, 11 | "drag_lift": true, 12 | "gondola": true, 13 | "goods": true, 14 | "j-bar": true, 15 | "magic_carpet": true, 16 | "mixed_lift": true, 17 | "platter": true, 18 | "rope_tow": true, 19 | "t-bar": true, 20 | "zip_line": true 21 | }, 22 | "aeroway": { 23 | "jet_bridge": true, 24 | "parking_position": true, 25 | "runway": true, 26 | "taxiway": true 27 | }, 28 | "allotments": { 29 | }, 30 | "amenity": { 31 | "bench": true 32 | }, 33 | "area:highway": { 34 | }, 35 | "attraction": { 36 | "dark_ride": true, 37 | "river_rafting": true, 38 | "summer_toboggan": true, 39 | "train": true, 40 | "water_slide": true 41 | }, 42 | "bridge:support": { 43 | }, 44 | "building": { 45 | }, 46 | "building:part": { 47 | }, 48 | "club": { 49 | }, 50 | "craft": { 51 | }, 52 | "emergency": { 53 | "designated": true, 54 | "destination": true, 55 | "no": true, 56 | "official": true, 57 | "private": true, 58 | "yes": true 59 | }, 60 | "golf": { 61 | "cartpath": true, 62 | "hole": true, 63 | "path": true 64 | }, 65 | "healthcare": { 66 | }, 67 | "historic": { 68 | }, 69 | "indoor": { 70 | "corridor": true, 71 | "wall": true 72 | }, 73 | "industrial": { 74 | }, 75 | "internet_access": { 76 | }, 77 | "junction": { 78 | "circular": true, 79 | "jughandle": true, 80 | "roundabout": true 81 | }, 82 | "landuse": { 83 | }, 84 | "leisure": { 85 | "slipway": true, 86 | "track": true 87 | }, 88 | "man_made": { 89 | "breakwater": true, 90 | "crane": true, 91 | "cutline": true, 92 | "dyke": true, 93 | "embankment": true, 94 | "groyne": true, 95 | "pier": true, 96 | "pipeline": true, 97 | "torii": true 98 | }, 99 | "military": { 100 | "trench": true 101 | }, 102 | "natural": { 103 | "bay": true, 104 | "cliff": true, 105 | "coastline": true, 106 | "ridge": true, 107 | "tree_row": true, 108 | "valley": true 109 | }, 110 | "office": { 111 | }, 112 | "piste:type": { 113 | "downhill": true, 114 | "hike": true, 115 | "ice_skate": true, 116 | "nordic": true, 117 | "skitour": true, 118 | "sled": true, 119 | "sleigh": true 120 | }, 121 | "place": { 122 | }, 123 | "playground": { 124 | "balancebeam": true, 125 | "slide": true, 126 | "zipwire": true 127 | }, 128 | "polling_station": { 129 | }, 130 | "power": { 131 | "cable": true, 132 | "line": true, 133 | "minor_line": true 134 | }, 135 | "public_transport": { 136 | "platform": true 137 | }, 138 | "residential": { 139 | }, 140 | "seamark:type": { 141 | }, 142 | "shop": { 143 | }, 144 | "telecom": { 145 | }, 146 | "tourism": { 147 | "artwork": true, 148 | "attraction": true 149 | }, 150 | "traffic_calming": { 151 | "bump": true, 152 | "chicane": true, 153 | "choker": true, 154 | "cushion": true, 155 | "dip": true, 156 | "hump": true, 157 | "island": true, 158 | "rumble_strip": true 159 | }, 160 | "waterway": { 161 | "canal": true, 162 | "dam": true, 163 | "ditch": true, 164 | "drain": true, 165 | "lock_gate": true, 166 | "river": true, 167 | "stream": true, 168 | "weir": true 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /osm2geojson/helpers.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import urllib 4 | from functools import wraps 5 | from time import sleep 6 | 7 | import requests 8 | 9 | OVERPASS = "https://overpass-api.de/api/interpreter/" 10 | dirname = os.path.dirname(os.path.dirname(__file__)) 11 | 12 | 13 | def read_data_file(name): 14 | path = os.path.join(dirname, 'tests/data', name) 15 | with codecs.open(path, 'r', encoding='utf-8') as data: 16 | return data.read() 17 | 18 | 19 | def retry_request_multi(max_retries): 20 | def retry(func): 21 | @wraps(func) 22 | def wrapper(*args, **kwargs): 23 | num_retries = 0 24 | while num_retries <= max_retries: 25 | try: 26 | ret = func(*args, **kwargs) 27 | break 28 | except requests.exceptions.HTTPError: 29 | if num_retries == max_retries: 30 | raise 31 | num_retries += 1 32 | sleep(5) 33 | return ret 34 | return wrapper 35 | return retry 36 | 37 | 38 | @retry_request_multi(5) 39 | def overpass_call(query): 40 | encoded = urllib.parse.quote(query.encode("utf-8"), safe='~()*!.\'') 41 | r = requests.post(OVERPASS, 42 | data="data={}".format(encoded), 43 | headers={"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"}) 44 | if not r.status_code == 200: 45 | raise requests.exceptions.HTTPError( 46 | 'Overpass server respond with status '+str(r.status_code)) 47 | return r.text 48 | -------------------------------------------------------------------------------- /osm2geojson/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import traceback 5 | import itertools 6 | from pprint import pformat 7 | from typing import Optional 8 | 9 | from shapely.geometry import (GeometryCollection, LineString, MultiLineString, 10 | MultiPolygon, Point, Polygon, mapping) 11 | from shapely.geometry.polygon import orient 12 | from shapely.ops import unary_union, linemerge 13 | 14 | from .parse_xml import parse as parse_xml 15 | 16 | 17 | logger = logging.getLogger(__name__) 18 | DEFAULT_POLYGON_FEATURES_FILE = os.path.join(os.path.dirname(__file__), 'polygon-features.json') 19 | DEFAULT_AREA_KEYS_FILE = os.path.join(os.path.dirname(__file__), 'areaKeys.json') 20 | 21 | if os.path.exists(DEFAULT_POLYGON_FEATURES_FILE): 22 | with open(DEFAULT_POLYGON_FEATURES_FILE) as f: 23 | _default_polygon_features = json.load(f) 24 | else: 25 | logger.warning("Default polygon features file not found, using empty filter") 26 | _default_polygon_features = [] 27 | 28 | if os.path.exists(DEFAULT_AREA_KEYS_FILE): 29 | with open(DEFAULT_AREA_KEYS_FILE) as f: 30 | _default_area_keys = json.load(f)['areaKeys'] 31 | else: 32 | logger.warning("Default area keys file not found, using empty filter") 33 | _default_area_keys = {} 34 | 35 | def get_message(*args): 36 | return ' '.join(args) 37 | 38 | def warning(*args): 39 | logger.warning(' '.join(args)) 40 | 41 | 42 | def error(*args): 43 | logger.error(' '.join(args)) 44 | 45 | 46 | def json2geojson( 47 | data, filter_used_refs=True, log_level='ERROR', 48 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 49 | raise_on_failure = False 50 | ): 51 | if isinstance(data, str): 52 | data = json.loads(data) 53 | return _json2geojson(data, filter_used_refs, log_level, area_keys, polygon_features, raise_on_failure) 54 | 55 | 56 | def xml2geojson( 57 | xml_str, filter_used_refs=True, log_level='ERROR', 58 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 59 | raise_on_failure = False 60 | ): 61 | data = parse_xml(xml_str) 62 | return _json2geojson(data, filter_used_refs, log_level, area_keys, polygon_features, raise_on_failure) 63 | 64 | 65 | def json2shapes( 66 | data, filter_used_refs=True, log_level='ERROR', 67 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 68 | raise_on_failure = False 69 | ): 70 | if isinstance(data, str): 71 | data = json.loads(data) 72 | return _json2shapes(data, filter_used_refs, log_level, area_keys, polygon_features, raise_on_failure) 73 | 74 | 75 | def xml2shapes( 76 | xml_str, filter_used_refs=True, log_level='ERROR', 77 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 78 | raise_on_failure = False 79 | ): 80 | data = parse_xml(xml_str) 81 | return _json2shapes(data, filter_used_refs, log_level, area_keys, polygon_features, raise_on_failure) 82 | 83 | 84 | def _json2geojson( 85 | data, filter_used_refs=True, log_level='ERROR', 86 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 87 | raise_on_failure = False 88 | ): 89 | features = [] 90 | for shape in _json2shapes(data, filter_used_refs, log_level, area_keys, polygon_features, raise_on_failure): 91 | feature = shape_to_feature(shape['shape'], shape['properties']) 92 | features.append(feature) 93 | 94 | return { 95 | 'type': 'FeatureCollection', 96 | 'features': features 97 | } 98 | 99 | 100 | def _json2shapes( 101 | data, filter_used_refs=True, log_level='ERROR', 102 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 103 | raise_on_failure = False 104 | ): 105 | logger.setLevel(log_level) 106 | shapes = [] 107 | 108 | refs = [ 109 | el 110 | for el in data['elements'] 111 | if el['type'] in ['node', 'way', 'relation'] 112 | ] 113 | 114 | refs_index = build_refs_index(refs) 115 | 116 | for el in data['elements']: 117 | shape = element_to_shape(el, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 118 | if shape is not None: 119 | shapes.append(shape) 120 | else: 121 | warning('Element not converted', pformat(el['id'])) 122 | 123 | if not filter_used_refs: 124 | return shapes 125 | 126 | used = { 127 | ref['id']: ref['used'] 128 | for ref in refs if 'used' in ref 129 | } 130 | filtered_shapes = [] 131 | for shape in shapes: 132 | if 'properties' not in shape: 133 | warning('Shape without props', pformat(shape)) 134 | if shape['properties']['id'] in used: 135 | continue 136 | filtered_shapes.append(shape) 137 | 138 | return filtered_shapes 139 | 140 | 141 | def element_to_shape(el, refs_index=None, area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, raise_on_failure = False): 142 | t = el['type'] 143 | if t == 'node': 144 | return node_to_shape(el) 145 | if t == 'way': 146 | return way_to_shape(el, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 147 | if t == 'relation': 148 | return relation_to_shape(el, refs_index, raise_on_failure=raise_on_failure) 149 | warning('Failed to convert element to shape') 150 | return None 151 | 152 | 153 | def _get_ref_name(el_type, id): 154 | return '%s/%s' % (el_type, id) 155 | 156 | 157 | def get_ref_name(el): 158 | return _get_ref_name(el['type'], el['id']) 159 | 160 | 161 | def _get_ref(el_type, id, refs_index): 162 | key = _get_ref_name(el_type, id) 163 | if key in refs_index: 164 | return refs_index[key] 165 | warning('Element not found in refs_index', pformat(el_type), pformat(id)) 166 | return None 167 | 168 | 169 | def get_ref(ref_el, refs_index): 170 | return _get_ref(ref_el['type'], ref_el['ref'], refs_index) 171 | 172 | 173 | def get_node_ref(id, refs_index): 174 | return _get_ref('node', id, refs_index) 175 | 176 | 177 | def build_refs_index(elements): 178 | return { 179 | get_ref_name(el): el 180 | for el in elements 181 | } 182 | 183 | 184 | def node_to_shape(node): 185 | return { 186 | 'shape': Point(node['lon'], node['lat']), 187 | 'properties': get_element_props(node) 188 | } 189 | 190 | 191 | def get_element_props(el, keys: list = None): 192 | keys = keys or [ 193 | 'type', 194 | 'id', 195 | 'tags', 196 | 'nodes', 197 | 'timestamp', 198 | 'user', 199 | 'uid', 200 | 'version' 201 | ] 202 | return { 203 | key: el[key] 204 | for key in keys 205 | if key in el 206 | } 207 | 208 | 209 | def convert_coords_to_lists(coords): 210 | if len(coords) < 1: 211 | return [] 212 | 213 | if isinstance(coords[0], float): 214 | return list(coords) 215 | 216 | return [convert_coords_to_lists(c) for c in coords] 217 | 218 | 219 | def shape_to_feature(g, props: dict = None): 220 | props = props or {} 221 | # shapely returns tuples (we need lists) 222 | g = mapping(g) 223 | g['coordinates'] = convert_coords_to_lists(g['coordinates']) 224 | return { 225 | "type": "Feature", 226 | 'properties': props, 227 | "geometry": g 228 | } 229 | 230 | 231 | def orient_multipolygon(p): 232 | p = [orient(geom) for geom in p.geoms] 233 | return MultiPolygon(p) 234 | 235 | 236 | def fix_invalid_polygon(p): 237 | if not p.is_valid: 238 | logger.info('Invalid geometry! Try to fix with 0 buffer') 239 | p = p.buffer(0) 240 | if p.is_valid: 241 | logger.info('Geometry fixed!') 242 | return p 243 | 244 | 245 | def way_to_shape( 246 | way, refs_index: dict = None, 247 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 248 | raise_on_failure = False 249 | ): 250 | refs_index = refs_index or {} 251 | if 'center' in way: 252 | center = way['center'] 253 | return { 254 | 'shape': Point(center['lon'], center['lat']), 255 | 'properties': get_element_props(way) 256 | } 257 | 258 | if 'geometry' in way and len(way['geometry']) > 0: 259 | coords = [ 260 | [nd['lon'], nd['lat']] 261 | for nd in way['geometry'] 262 | ] 263 | 264 | elif 'nodes' in way and len(way['nodes']) > 0: 265 | coords = [] 266 | for ref in way['nodes']: 267 | node = get_node_ref(ref, refs_index) 268 | if node: 269 | node['used'] = way['id'] 270 | coords.append([node['lon'], node['lat']]) 271 | else: 272 | message = get_message('Node not found in index', pformat(ref), 'for way', pformat(way)) 273 | warning(message) 274 | if raise_on_failure: 275 | raise Exception(message) 276 | return None 277 | 278 | elif 'ref' in way: 279 | ref = get_ref(way, refs_index) 280 | if not ref: 281 | message = get_message('Ref for way not found in index', pformat(way)) 282 | warning(message) 283 | if raise_on_failure: 284 | raise Exception(message) 285 | return None 286 | 287 | if 'id' in way: 288 | ref['used'] = way['id'] 289 | elif 'used' in way: 290 | ref['used'] = way['used'] 291 | else: 292 | # filter will not work for this situation 293 | warning('Failed to mark ref as used', pformat(ref), 'for way', pformat(way)) 294 | # do we need to raise expection here? I don't think so 295 | ref_way = way_to_shape(ref, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 296 | if ref_way is None: 297 | message = get_message('Way by ref not converted to shape', pformat(way)) 298 | warning(message) 299 | if raise_on_failure: 300 | raise Exception(message) 301 | return None 302 | coords = ( 303 | ref_way['shape'].exterior 304 | if isinstance(ref_way['shape'], Polygon) 305 | else ref_way['shape'] 306 | ).coords 307 | 308 | else: 309 | # throw exception 310 | message = get_message('Relation has way without nodes', pformat(way)) 311 | warning(message) 312 | if raise_on_failure: 313 | raise Exception(message) 314 | return None 315 | 316 | if len(coords) < 2: 317 | message = get_message('Not found coords for way', pformat(way)) 318 | warning(message) 319 | if raise_on_failure: 320 | raise Exception(message) 321 | return None 322 | 323 | props = get_element_props(way) 324 | if is_geometry_polygon(way, area_keys, polygon_features): 325 | try: 326 | poly = fix_invalid_polygon(Polygon(coords)) 327 | return { 328 | 'shape': poly, 329 | 'properties': props 330 | } 331 | except Exception: 332 | message = get_message('Failed to generate polygon from way', pformat(way)) 333 | warning(message) 334 | if raise_on_failure: 335 | raise Exception(message) 336 | return None 337 | else: 338 | return { 339 | 'shape': LineString(coords), 340 | 'properties': props 341 | } 342 | 343 | 344 | def is_exception(node, area_keys: Optional[dict] = None): 345 | area_keys = area_keys or _default_area_keys 346 | for tag in node['tags']: 347 | if tag in area_keys: 348 | value = node['tags'][tag] 349 | return value in area_keys[tag] and area_keys[tag][value] 350 | return False 351 | 352 | 353 | def is_same_coords(a, b): 354 | return a['lat'] == b['lat'] and a['lon'] == b['lon'] 355 | 356 | 357 | def is_geometry_polygon(node, area_keys: Optional[dict] = None, polygon_features: Optional[list] = None): 358 | if 'tags' not in node: 359 | return False 360 | tags = node['tags'] 361 | 362 | if 'area' in tags and tags['area'] == 'no': 363 | return False 364 | 365 | if 'area' in tags and tags['area'] == 'yes': 366 | return True 367 | 368 | if 'type' in tags and tags['type'] == 'multipolygon': 369 | return True 370 | 371 | # Fix for issue #7, but should be handled by id-area-keys or osm-polygon-features 372 | # For example https://github.com/tyrasd/osm-polygon-features/issues/5 373 | if 'geometry' in node and not is_same_coords(node['geometry'][0], node['geometry'][-1]): 374 | return False 375 | 376 | # For issue #7 and situation with barrier=wall 377 | if 'nodes' in node and node['nodes'][0] != node['nodes'][-1]: 378 | return False 379 | 380 | is_polygon = is_geometry_polygon_without_exceptions(node, polygon_features) 381 | if is_polygon: 382 | return not is_exception(node, area_keys) 383 | else: 384 | return False 385 | 386 | 387 | def is_geometry_polygon_without_exceptions(node, polygon_features: Optional[list] = None): 388 | polygon_features = polygon_features or _default_polygon_features 389 | tags = node['tags'] 390 | for rule in polygon_features: 391 | if rule['key'] in tags: 392 | if rule['polygon'] == 'all': 393 | return True 394 | if rule['polygon'] == 'whitelist' and tags[rule['key']] in rule['values']: 395 | return True 396 | if rule['polygon'] == 'blacklist': 397 | return tags[rule['key']] not in rule['values'] 398 | return False 399 | 400 | 401 | def relation_to_shape(rel, refs_index, area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, raise_on_failure = False): 402 | if 'center' in rel: 403 | center = rel['center'] 404 | return { 405 | 'shape': Point(center['lon'], center['lat']), 406 | 'properties': get_element_props(rel) 407 | } 408 | 409 | try: 410 | if is_geometry_polygon(rel, area_keys, polygon_features): 411 | return multipolygon_relation_to_shape(rel, refs_index, raise_on_failure=raise_on_failure) 412 | else: 413 | return multiline_realation_to_shape(rel, refs_index, raise_on_failure=raise_on_failure) 414 | except Exception as e: 415 | message = get_message('Failed to convert relation to shape: \n', pformat(e), pformat(rel)) 416 | error(message) 417 | if raise_on_failure: 418 | raise Exception(message) 419 | 420 | 421 | def multiline_realation_to_shape( 422 | rel, refs_index, 423 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 424 | raise_on_failure = False 425 | ): 426 | lines = [] 427 | 428 | if 'members' in rel: 429 | members = rel['members'] 430 | else: 431 | found_ref = get_ref(rel, refs_index) 432 | if not found_ref: 433 | message = get_message('Ref for multiline relation not found in index', pformat(rel)) 434 | error(message) 435 | if raise_on_failure: 436 | raise Exception(message) 437 | return None 438 | members = found_ref['members'] 439 | 440 | for member in members: 441 | if member['type'] == 'way': 442 | way_shape = way_to_shape(member, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 443 | elif member['type'] == 'relation': 444 | found_member = get_ref(member, refs_index) 445 | if found_member: 446 | found_member['used'] = rel['id'] 447 | way_shape = element_to_shape(member, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 448 | else: 449 | message = get_message('multiline member not handled', pformat(member)) 450 | warning(message) 451 | if raise_on_failure: 452 | raise Exception(message) 453 | continue 454 | 455 | if way_shape is None: 456 | # throw exception 457 | message = get_message('Failed to make way in relation', pformat(rel)) 458 | warning(message) 459 | if raise_on_failure: 460 | raise Exception(message) 461 | continue 462 | 463 | if isinstance(way_shape['shape'], Polygon): 464 | # this should not happen on real data 465 | way_shape['shape'] = LineString(way_shape['shape'].exterior.coords) 466 | lines.append(way_shape['shape']) 467 | 468 | if len(lines) < 1: 469 | message = get_message('No lines for multiline relation', pformat(rel)) 470 | warning(message) 471 | if raise_on_failure: 472 | raise Exception(message) 473 | return None 474 | 475 | multiline = MultiLineString(lines) 476 | multiline = linemerge(multiline) 477 | return { 478 | 'shape': multiline, 479 | 'properties': get_element_props(rel) 480 | } 481 | 482 | 483 | def multipolygon_relation_to_shape( 484 | rel, refs_index, 485 | area_keys: Optional[dict] = None, polygon_features: Optional[list] = None, 486 | raise_on_failure = False 487 | ): 488 | # List of Tuple (role, multipolygon) 489 | shapes = [] 490 | 491 | if 'members' in rel: 492 | members = rel['members'] 493 | else: 494 | found_ref = get_ref(rel, refs_index) 495 | if not found_ref: 496 | message = get_message('Ref for multipolygon relation not found in index', pformat(rel)) 497 | error(message) 498 | if raise_on_failure: 499 | raise Exception(message) 500 | return None 501 | members = found_ref['members'] 502 | 503 | for member in members: 504 | if member['type'] != 'way': 505 | message = get_message('Multipolygon member not handled', pformat(member)) 506 | warning(message) 507 | if raise_on_failure: 508 | raise Exception(message) 509 | continue 510 | 511 | member['used'] = rel['id'] 512 | 513 | way_shape = way_to_shape(member, refs_index, area_keys, polygon_features, raise_on_failure=raise_on_failure) 514 | if way_shape is None: 515 | # throw exception 516 | message = get_message('Failed to make way', pformat(member), 'in relation', pformat(rel)) 517 | warning(message) 518 | if raise_on_failure: 519 | raise Exception(message) 520 | continue 521 | 522 | if isinstance(way_shape['shape'], Polygon): 523 | way_shape['shape'] = LineString(way_shape['shape'].exterior.coords) 524 | 525 | shapes.append((member['role'], way_shape['shape'], member['ref'])) 526 | 527 | multipolygon = _convert_shapes_to_multipolygon(shapes, raise_on_failure=raise_on_failure) 528 | if multipolygon is None: 529 | message = get_message('Failed to convert computed shapes to multipolygon', pformat(rel['id'])) 530 | warning(message) 531 | if raise_on_failure: 532 | raise Exception(message) 533 | return None 534 | 535 | multipolygon = fix_invalid_polygon(multipolygon) 536 | multipolygon = to_multipolygon(multipolygon, raise_on_failure=raise_on_failure) 537 | multipolygon = orient_multipolygon(multipolygon) # do we need this? 538 | 539 | if multipolygon is None: 540 | message = get_message('Failed to fix multipolygon. Report this in github please!', pformat(rel)) 541 | warning(message) 542 | if raise_on_failure: 543 | raise Exception(message) 544 | return None 545 | 546 | return { 547 | 'shape': multipolygon, 548 | 'properties': get_element_props(rel) 549 | } 550 | 551 | 552 | def to_multipolygon(obj, raise_on_failure=False): 553 | if isinstance(obj, MultiPolygon): 554 | return obj 555 | 556 | if isinstance(obj, GeometryCollection): 557 | p = [ 558 | el for el in obj 559 | if isinstance(el, Polygon) 560 | ] 561 | return MultiPolygon(p) 562 | 563 | if isinstance(obj, Polygon): 564 | return MultiPolygon([obj]) 565 | 566 | # throw exception 567 | message = get_message('Failed to convert to multipolygon', type(obj)) 568 | warning(message) 569 | if raise_on_failure: 570 | raise Exception(message) 571 | return None 572 | 573 | 574 | def _convert_lines_to_multipolygon(lines, raise_on_failure=False): 575 | multi_line = MultiLineString(lines) 576 | merged_line = linemerge(multi_line) 577 | if isinstance(merged_line, MultiLineString): 578 | polygons = [] 579 | for line in merged_line.geoms: 580 | try: 581 | poly = Polygon(line) 582 | if poly.is_valid: 583 | polygons.append(poly) 584 | else: 585 | polygons.append(poly.buffer(0)) 586 | except Exception: 587 | # throw exception 588 | message = get_message('Failed to build polygon', pformat(line)) 589 | warning(message) 590 | if raise_on_failure: 591 | raise Exception(message) 592 | return to_multipolygon(unary_union(polygons), raise_on_failure=raise_on_failure) 593 | try: 594 | poly = Polygon(merged_line) 595 | except Exception as e: 596 | message = get_message('Failed to convert lines to polygon', pformat(e)) 597 | warning(message) 598 | if raise_on_failure: 599 | raise Exception(message) 600 | # traceback.print_exc() 601 | return None 602 | return to_multipolygon(poly, raise_on_failure=raise_on_failure) 603 | 604 | 605 | def _convert_shapes_to_multipolygon(shapes, raise_on_failure=False): 606 | if len(shapes) < 1: 607 | message = 'Failed to create multipolygon (Empty)' 608 | warning(message) 609 | if raise_on_failure: 610 | raise Exception(message) 611 | return None 612 | 613 | # Intermediate groups [(role, geom, ids)] 614 | groups = [] 615 | # New group each time it switches role 616 | for role, group in itertools.groupby(shapes, lambda s: s[0]): 617 | lines_and_ids = [(_[1], _[2]) for _ in group] 618 | groups.append((role, _convert_lines_to_multipolygon([_[0] for _ in lines_and_ids], raise_on_failure=raise_on_failure), [_[1] for _ in lines_and_ids])) 619 | 620 | multipolygon = None 621 | base_index = -1 622 | for i, (role, geom, ids) in enumerate(groups): 623 | if role == 'outer': 624 | multipolygon = geom 625 | base_index = i 626 | break 627 | 628 | if base_index < 0: 629 | message = 'Failed to create multipolygon. Shape with "outer" role not found' 630 | warning(message) 631 | if raise_on_failure: 632 | raise Exception(message) 633 | return None 634 | 635 | if not multipolygon.is_valid: 636 | group_ids = groups[base_index][2] 637 | message = get_message('Failed to create multipolygon. Base shape with role "outer" is invalid. Group ids:', pformat(group_ids)) 638 | warning(message) 639 | if raise_on_failure: 640 | raise Exception(message) 641 | return None 642 | 643 | # Itterate over the rest if there are any 644 | for i, (role, geom, ids) in enumerate(groups): 645 | if i == base_index: 646 | continue 647 | 648 | if role == "inner": 649 | multipolygon = multipolygon.difference(geom) 650 | else: 651 | multipolygon = multipolygon.union(geom) 652 | 653 | if multipolygon is None: 654 | message = get_message('Failed to compute multipolygon. Failing geometry:', role, geom) 655 | warning(message) 656 | if raise_on_failure: 657 | raise Exception(message) 658 | return None 659 | 660 | return multipolygon 661 | -------------------------------------------------------------------------------- /osm2geojson/parse_xml.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ElementTree 2 | 3 | default_types = ['node', 'way', 'relation', 'member', 'nd'] 4 | optional_meta_fields = ['timestamp', 'version:int', 'changeset:int', 'user', 'uid:int'] 5 | 6 | 7 | def parse_key(key): 8 | t = 'string' 9 | parts = key.split(':') 10 | if len(parts) > 1: 11 | t = parts[1] 12 | return parts[0], t 13 | 14 | 15 | def to_type(v, t): 16 | if t == 'string': 17 | return str(v) 18 | elif t == 'int': 19 | return int(v) 20 | elif t == 'float': 21 | return float(v) 22 | return v 23 | 24 | 25 | def with_meta_fields(fields: list = None): 26 | fields = fields or [] 27 | for field in optional_meta_fields: 28 | if field not in fields: 29 | fields.append(field) 30 | return fields 31 | 32 | 33 | def copy_fields(node, base, optional: list = None): 34 | optional = optional or [] 35 | obj = {} 36 | for key in base: 37 | key, t = parse_key(key) 38 | if key not in node.attrib: 39 | print(key, 'not found in', node.tag, node.attrib) 40 | obj[key] = to_type(node.attrib[key], t) 41 | for key in optional: 42 | key, t = parse_key(key) 43 | if key in node.attrib: 44 | obj[key] = to_type(node.attrib[key], t) 45 | return obj 46 | 47 | 48 | def filter_items_by_type(items, types): 49 | return [i for i in items if i['type'] in types] 50 | 51 | 52 | def tags_to_obj(tags): 53 | return {tag['k']: tag['v'] for tag in tags} 54 | 55 | 56 | def parse_bounds(node): 57 | return copy_fields(node, [ 58 | 'minlat:float', 59 | 'minlon:float', 60 | 'maxlat:float', 61 | 'maxlon:float' 62 | ]) 63 | 64 | 65 | def parse_count(node): 66 | bounds, tags, _empty, unhandled = parse_xml_node(node, []) 67 | item = copy_fields(node, ['id:int']) 68 | item['type'] = 'count' 69 | if len(tags) > 0: 70 | item['tags'] = tags_to_obj(tags) 71 | return item 72 | 73 | 74 | def parse_tag(node): 75 | return copy_fields(node, ['k', 'v']) 76 | 77 | 78 | def parse_nd(node): 79 | return copy_fields(node, [], ['ref:int', 'lat:float', 'lon:float']) 80 | 81 | 82 | def parse_node(node): 83 | bounds, tags, items, unhandled = parse_xml_node(node) 84 | item = copy_fields(node, [], with_meta_fields([ 85 | 'role', 86 | 'id:int', 87 | 'ref:int', 88 | 'lat:float', 89 | 'lon:float' 90 | ])) 91 | item['type'] = 'node' 92 | if len(tags) > 0: 93 | item['tags'] = tags_to_obj(tags) 94 | return item 95 | 96 | 97 | def parse_way(node): 98 | bounds, tags, nds, unhandled = parse_xml_node(node, ['nd']) 99 | geometry = [] 100 | nodes = [] 101 | for nd in nds: 102 | if 'ref' in nd and 'lat' not in nd and 'lon' not in nd: 103 | nodes.append(nd['ref']) 104 | else: 105 | geometry.append(nd) 106 | 107 | way = copy_fields(node, [], with_meta_fields(['ref:int', 'id:int', 'role'])) 108 | way['type'] = 'way' 109 | if len(tags) > 0: 110 | way['tags'] = tags_to_obj(tags) 111 | if geometry: 112 | way['geometry'] = geometry 113 | if nodes: 114 | way['nodes'] = nodes 115 | return way 116 | 117 | 118 | def parse_relation(node): 119 | bounds, tags, members, unhandled = parse_xml_node(node, ['member']) 120 | 121 | relation = copy_fields(node, [], with_meta_fields(['id:int', 'ref:int', 'role'])) 122 | relation['type'] = 'relation' 123 | if len(members) > 0: 124 | relation['members'] = members 125 | if bounds is not None: 126 | relation['bounds'] = bounds 127 | if len(tags) > 0: 128 | relation['tags'] = tags_to_obj(tags) 129 | return relation 130 | 131 | 132 | def format_ojson(elements, unhandled): 133 | version = 0.6 134 | generator = None 135 | timestamp_osm_base = None 136 | copyright = None 137 | 138 | for node in unhandled: 139 | if node.tag == 'meta' and 'osm_base' in node.attrib: 140 | timestamp_osm_base = node.attrib['osm_base'] 141 | elif node.tag == 'note': 142 | copyright = node.text 143 | elif node.tag == 'osm': 144 | if 'version' in node.attrib: 145 | version = float(node.attrib['version']) 146 | if 'generator' in node.attrib: 147 | generator = node.attrib['generator'] 148 | 149 | item = { 150 | 'version': version, 151 | 'elements': elements 152 | } 153 | 154 | if generator is not None: 155 | item['generator'] = generator 156 | if copyright is not None: 157 | item.setdefault('osm3s', {})['copyright'] = copyright 158 | if timestamp_osm_base is not None: 159 | item.setdefault('osm3s', {})['timestamp_osm_base'] = timestamp_osm_base 160 | 161 | return item 162 | 163 | 164 | def parse(xml_str): 165 | root = ElementTree.fromstring(xml_str) 166 | if root.tag != 'osm': 167 | print('OSM root node not found!') 168 | return None 169 | 170 | bounds, tags, elements, unhandled = parse_xml_node(root, ['node', 'way', 'relation', 'count']) 171 | unhandled.append(root) 172 | return format_ojson(elements, unhandled) 173 | 174 | 175 | def parse_node_type(node, node_type): 176 | if node_type == 'bounds': 177 | return parse_bounds(node) 178 | 179 | elif node_type == 'tag': 180 | return parse_tag(node) 181 | 182 | elif node_type == 'node': 183 | return parse_node(node) 184 | 185 | elif node_type == 'way': 186 | return parse_way(node) 187 | 188 | elif node_type == 'relation': 189 | return parse_relation(node) 190 | 191 | elif node_type == 'member': 192 | return parse_node_type(node, node.attrib['type']) 193 | 194 | elif node_type == 'nd': 195 | return parse_nd(node) 196 | 197 | else: 198 | print('Unhandled node type', node_type) 199 | return None 200 | 201 | 202 | def parse_xml_node(root, node_types: list = None): 203 | node_types = node_types or default_types 204 | bounds = None 205 | count = None 206 | tags = [] 207 | items = [] 208 | unhandled = [] 209 | 210 | for child in root: 211 | if child.tag == 'bounds': 212 | if bounds is not None: 213 | print('Node bounds should be unique') 214 | bounds = parse_bounds(child) 215 | elif child.tag == 'count': 216 | if count is not None: 217 | print('Node count should be unique') 218 | count = parse_count(child) 219 | else: 220 | if child.tag == 'tag': 221 | tags.append(parse_tag(child)) 222 | continue 223 | 224 | if child.tag not in default_types: 225 | unhandled.append(child) 226 | continue 227 | 228 | if child.tag in node_types: 229 | items.append(parse_node_type(child, child.tag)) 230 | 231 | if 'count' in node_types and count is not None: 232 | items.append(count) 233 | return bounds, tags, items, unhandled 234 | -------------------------------------------------------------------------------- /osm2geojson/polygon-features.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "building", 4 | "polygon": "all" 5 | }, 6 | { 7 | "key": "highway", 8 | "polygon": "whitelist", 9 | "values": [ 10 | "services", 11 | "rest_area", 12 | "escape", 13 | "elevator" 14 | ] 15 | }, 16 | { 17 | "key": "natural", 18 | "polygon": "blacklist", 19 | "values": [ 20 | "coastline", 21 | "cliff", 22 | "ridge", 23 | "arete", 24 | "tree_row" 25 | ] 26 | }, 27 | { 28 | "key": "landuse", 29 | "polygon": "all" 30 | }, 31 | { 32 | "key": "waterway", 33 | "polygon": "whitelist", 34 | "values": [ 35 | "riverbank", 36 | "dock", 37 | "boatyard", 38 | "dam" 39 | ] 40 | }, 41 | { 42 | "key": "amenity", 43 | "polygon": "all" 44 | }, 45 | { 46 | "key": "leisure", 47 | "polygon": "all" 48 | }, 49 | { 50 | "key": "barrier", 51 | "polygon": "whitelist", 52 | "values": [ 53 | "city_wall", 54 | "ditch", 55 | "hedge", 56 | "retaining_wall", 57 | "spikes" 58 | ] 59 | }, 60 | { 61 | "key": "railway", 62 | "polygon": "whitelist", 63 | "values": [ 64 | "station", 65 | "turntable", 66 | "roundhouse", 67 | "platform" 68 | ] 69 | }, 70 | { 71 | "key": "area", 72 | "polygon": "all" 73 | }, 74 | { 75 | "key": "boundary", 76 | "polygon": "all" 77 | }, 78 | { 79 | "key": "man_made", 80 | "polygon": "blacklist", 81 | "values": [ 82 | "cutline", 83 | "embankment", 84 | "pipeline" 85 | ] 86 | }, 87 | { 88 | "key": "power", 89 | "polygon": "whitelist", 90 | "values": [ 91 | "plant", 92 | "substation", 93 | "generator", 94 | "transformer" 95 | ] 96 | }, 97 | { 98 | "key": "place", 99 | "polygon": "all" 100 | }, 101 | { 102 | "key": "shop", 103 | "polygon": "all" 104 | }, 105 | { 106 | "key": "aeroway", 107 | "polygon": "blacklist", 108 | "values": [ 109 | "taxiway" 110 | ] 111 | }, 112 | { 113 | "key": "tourism", 114 | "polygon": "all" 115 | }, 116 | { 117 | "key": "historic", 118 | "polygon": "all" 119 | }, 120 | { 121 | "key": "public_transport", 122 | "polygon": "all" 123 | }, 124 | { 125 | "key": "office", 126 | "polygon": "all" 127 | }, 128 | { 129 | "key": "building:part", 130 | "polygon": "all" 131 | }, 132 | { 133 | "key": "military", 134 | "polygon": "all" 135 | }, 136 | { 137 | "key": "ruins", 138 | "polygon": "all" 139 | }, 140 | { 141 | "key": "area:highway", 142 | "polygon": "all" 143 | }, 144 | { 145 | "key": "craft", 146 | "polygon": "all" 147 | }, 148 | { 149 | "key": "golf", 150 | "polygon": "all" 151 | }, 152 | { 153 | "key": "indoor", 154 | "polygon": "all" 155 | }, 156 | { 157 | "key": "highway", 158 | "polygon": "blacklist", 159 | "values": [ 160 | "steps" 161 | ] 162 | } 163 | ] 164 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | ./lint.sh 3 | python -m unittest discover 4 | rm -rf dist 5 | python setup.py sdist 6 | twine upload dist/* 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | shapely 2 | requests 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.md 3 | license_files = LICENSE 4 | 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from setuptools import setup 3 | 4 | dirname = path.abspath(path.dirname(__file__)) 5 | with open(path.join(dirname, 'README.md'), encoding='utf-8') as f: 6 | long_description = f.read() 7 | 8 | 9 | def parse_requirements(filename): 10 | lines = (line.strip() for line in open(path.join(dirname, filename))) 11 | return [line for line in lines if line and not line.startswith("#")] 12 | 13 | 14 | setup( 15 | name='osm2geojson', 16 | version='0.2.6', 17 | license='MIT', 18 | description='Parse OSM and Overpass JSON', 19 | long_description=long_description, 20 | long_description_content_type='text/markdown', 21 | keywords='geometry gis osm parsing', 22 | author='Parfeniuk Mykola', 23 | author_email='mikola.parfenyuck@gmail.com', 24 | url='https://github.com/aspectumapp/osm2geojson', 25 | packages=['osm2geojson'], 26 | include_package_data=True, 27 | install_requires=parse_requirements("requirements.txt"), 28 | entry_points={ 29 | 'console_scripts': ['osm2geojson=osm2geojson.__main__.main:main'] 30 | } 31 | ) 32 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .parse_xml import TestParseXmlMethods 2 | from .main import TestOsm2GeoJsonMethods 3 | -------------------------------------------------------------------------------- /tests/data/barrier-wall.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 43934658, 9 | "tags": { 10 | "attribution": "Natural Resources Canada", 11 | "barrier": "wall", 12 | "canvec:CODE": "2240021", 13 | "canvec:UUID": "11CF457637C4E5F4E0409C8467120387", 14 | "source": "CanVec_Import_2009" 15 | }, 16 | "nodes": [558708287, 558708288, 558708289, 558708290], 17 | "timestamp": "2009-11-07T08:33:58Z", 18 | "user": "andrewpmk", 19 | "uid": 1679, 20 | "version": 1 21 | }, 22 | "geometry": { 23 | "type": "LineString", 24 | "coordinates": [ 25 | [ 26 | -79.3698231, 27 | 43.7749317 28 | ], 29 | [ 30 | -79.3694115, 31 | 43.7744942 32 | ], 33 | [ 34 | -79.3691393, 35 | 43.7738156 36 | ], 37 | [ 38 | -79.368756, 39 | 43.7717787 40 | ] 41 | ] 42 | } 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /tests/data/barrier-wall.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/data/center-feature.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type":"FeatureCollection", 3 | "features":[ 4 | { 5 | "type":"Feature", 6 | "properties":{ 7 | "type":"relation", 8 | "id":448930, 9 | "tags":{ 10 | "admin_level":"6", 11 | "boundary":"administrative", 12 | "flag":"http://upload.wikimedia.org/wikipedia/commons/b/bf/Flag_of_Rivne.svg", 13 | "int_name":"Rivne", 14 | "koatuu":"5610100000", 15 | "name":"\u0420\u0456\u0432\u043d\u0435", 16 | "name:be":"\u0420\u043e\u045e\u043d\u0430", 17 | "name:de":"Riwne", 18 | "name:en":"Rivne", 19 | "name:eo":"Rivno", 20 | "name:fr":"Rivne", 21 | "name:hu":"Rivne", 22 | "name:lt":"Rivn\u0117", 23 | "name:pl":"R\u00f3wne", 24 | "name:ru":"\u0420\u043e\u0432\u043d\u043e", 25 | "name:sr":"\u0420\u043e\u0432\u043d\u043e", 26 | "name:uk":"\u0420\u0456\u0432\u043d\u0435", 27 | "old_name":"\u0420\u043e\u0432\u043d\u043e", 28 | "place":"city", 29 | "population":"245323", 30 | "source":"landsat", 31 | "type":"boundary", 32 | "wikidata":"Q156739", 33 | "wikipedia":"uk:\u0420\u0456\u0432\u043d\u0435", 34 | "wikipedia:pl":"R\u00f3wne" 35 | } 36 | }, 37 | "geometry":{ 38 | "type":"Point", 39 | "coordinates":[ 40 | 26.2443283, 41 | 50.6108274 42 | ] 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /tests/data/center-feature.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":0.6, 3 | "generator":"Overpass API 0.7.56.1004 6cd3eaec", 4 | "osm3s":{ 5 | "timestamp_osm_base":"2020-08-09T22:22:02Z", 6 | "copyright":"The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements":[ 9 | { 10 | "type":"relation", 11 | "id":448930, 12 | "center":{ 13 | "lat":50.6108274, 14 | "lon":26.2443283 15 | }, 16 | "members":[ 17 | { 18 | "type":"way", 19 | "ref":330047439, 20 | "role":"outer" 21 | }, 22 | { 23 | "type":"way", 24 | "ref":285186746, 25 | "role":"outer" 26 | }, 27 | { 28 | "type":"way", 29 | "ref":336846282, 30 | "role":"outer" 31 | }, 32 | { 33 | "type":"way", 34 | "ref":125424949, 35 | "role":"outer" 36 | }, 37 | { 38 | "type":"way", 39 | "ref":40641289, 40 | "role":"outer" 41 | }, 42 | { 43 | "type":"way", 44 | "ref":124541972, 45 | "role":"outer" 46 | }, 47 | { 48 | "type":"way", 49 | "ref":125424966, 50 | "role":"outer" 51 | }, 52 | { 53 | "type":"way", 54 | "ref":125424961, 55 | "role":"outer" 56 | }, 57 | { 58 | "type":"way", 59 | "ref":327326112, 60 | "role":"outer" 61 | }, 62 | { 63 | "type":"way", 64 | "ref":327326113, 65 | "role":"outer" 66 | }, 67 | { 68 | "type":"way", 69 | "ref":332680384, 70 | "role":"outer" 71 | }, 72 | { 73 | "type":"way", 74 | "ref":332680378, 75 | "role":"outer" 76 | }, 77 | { 78 | "type":"way", 79 | "ref":332680383, 80 | "role":"outer" 81 | }, 82 | { 83 | "type":"way", 84 | "ref":332680382, 85 | "role":"outer" 86 | }, 87 | { 88 | "type":"way", 89 | "ref":330047442, 90 | "role":"outer" 91 | }, 92 | { 93 | "type":"way", 94 | "ref":332732930, 95 | "role":"outer" 96 | }, 97 | { 98 | "type":"way", 99 | "ref":332732925, 100 | "role":"outer" 101 | }, 102 | { 103 | "type":"node", 104 | "ref":146541158, 105 | "role":"admin_centre" 106 | } 107 | ], 108 | "tags":{ 109 | "admin_level":"6", 110 | "boundary":"administrative", 111 | "flag":"http://upload.wikimedia.org/wikipedia/commons/b/bf/Flag_of_Rivne.svg", 112 | "int_name":"Rivne", 113 | "koatuu":"5610100000", 114 | "name":"Рівне", 115 | "name:be":"Роўна", 116 | "name:de":"Riwne", 117 | "name:en":"Rivne", 118 | "name:eo":"Rivno", 119 | "name:fr":"Rivne", 120 | "name:hu":"Rivne", 121 | "name:lt":"Rivnė", 122 | "name:pl":"Równe", 123 | "name:ru":"Ровно", 124 | "name:sr":"Ровно", 125 | "name:uk":"Рівне", 126 | "old_name":"Ровно", 127 | "place":"city", 128 | "population":"245323", 129 | "source":"landsat", 130 | "type":"boundary", 131 | "wikidata":"Q156739", 132 | "wikipedia":"uk:Рівне", 133 | "wikipedia:pl":"Równe" 134 | } 135 | } 136 | ] 137 | } 138 | -------------------------------------------------------------------------------- /tests/data/empty.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [] 3 | } 4 | -------------------------------------------------------------------------------- /tests/data/empty.osm: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/data/issue-16.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 4402690, 9 | "nodes": [ 10 | 26763057, 11 | 26762979 12 | ] 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 13.33121, 19 | 52.50371 20 | ], 21 | [ 22 | 13.331418, 23 | 52.5037526 24 | ] 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tests/data/issue-16.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":0.6, 3 | "generator":"Overpass API 0.7.56.7 b85c4387", 4 | "osm3s":{ 5 | "timestamp_osm_base":"2021-02-08T19:17:02Z", 6 | "copyright":"The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements":[ 9 | { 10 | "type":"node", 11 | "id":26763057, 12 | "lat":52.5037100, 13 | "lon":13.3312100 14 | }, 15 | { 16 | "type":"node", 17 | "id":26762979, 18 | "lat":52.5037526, 19 | "lon":13.3314180 20 | }, 21 | { 22 | "type":"node", 23 | "id":293547445, 24 | "lat":52.4524372, 25 | "lon":13.2907973 26 | }, 27 | { 28 | "type":"node", 29 | "id":293543213, 30 | "lat":52.4523788, 31 | "lon":13.2906566 32 | }, 33 | { 34 | "type":"way", 35 | "id":26763057, 36 | "nodes":[ 37 | 293543213, 38 | 293547445, 39 | 293543213 40 | ] 41 | }, 42 | { 43 | "type":"way", 44 | "id":4402690, 45 | "nodes":[ 46 | 26763057, 47 | 26762979 48 | ] 49 | }, 50 | { 51 | "type":"relation", 52 | "id":6018664, 53 | "members":[ 54 | { 55 | "type":"way", 56 | "ref":26763057, 57 | "role":"inner" 58 | } 59 | ], 60 | "tags":{ 61 | "addr:city":"Berlin", 62 | "addr:country":"DE", 63 | "addr:housenumber":"45", 64 | "addr:postcode":"14195", 65 | "addr:street":"Habelschwerdter Allee", 66 | "addr:suburb":"Dahlem", 67 | "building":"yes", 68 | "contact:website":"http://www.fu-berlin.de/service/orientierung/rosi/index.html", 69 | "name":"Silberlaube", 70 | "operator":"Freie Universität Berlin", 71 | "source":"Geoportal Berlin / k5_2012_sw_sued.zip", 72 | "type":"multipolygon", 73 | "wheelchair":"yes" 74 | } 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /tests/data/issue-4.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "relation", 8 | "id": 7390177, 9 | "tags": { 10 | "name": "Troleibuzul 30", 11 | "name:en": "Trolleybus 30", 12 | "ref": "30", 13 | "route_master": "trolleybus", 14 | "type": "route_master" 15 | } 16 | }, 17 | "geometry": { 18 | "type": "LineString", 19 | "coordinates": [ 20 | [ 21 | 28.9355107, 22 | 46.935418 23 | ], 24 | [ 25 | 28.9361223, 26 | 46.9353713 27 | ], 28 | [ 29 | 28.9363025, 30 | 46.9353995 31 | ], 32 | [ 33 | 28.9363472, 34 | 46.9354105 35 | ], 36 | [ 37 | 28.9364463, 38 | 46.9354419 39 | ], 40 | [ 41 | 28.9365064, 42 | 46.9354661 43 | ], 44 | [ 45 | 28.9365702, 46 | 46.935512 47 | ], 48 | [ 49 | 28.9366113, 50 | 46.9355571 51 | ], 52 | [ 53 | 28.9366378, 54 | 46.9356638 55 | ], 56 | [ 57 | 28.9366632, 58 | 46.935735 59 | ], 60 | [ 61 | 28.936659, 62 | 46.9358069 63 | ], 64 | [ 65 | 28.9366543, 66 | 46.9358878 67 | ], 68 | [ 69 | 28.9366456, 70 | 46.9359377 71 | ], 72 | [ 73 | 28.9366346, 74 | 46.9359654 75 | ], 76 | [ 77 | 28.9365995, 78 | 46.9359968 79 | ], 80 | [ 81 | 28.9365323, 82 | 46.9360182 83 | ], 84 | [ 85 | 28.9364184, 86 | 46.9360372 87 | ], 88 | [ 89 | 28.9362376, 90 | 46.9360496 91 | ], 92 | [ 93 | 28.9344165, 94 | 46.9361902 95 | ], 96 | [ 97 | 28.9343567, 98 | 46.9361998 99 | ], 100 | [ 101 | 28.9342974, 102 | 46.9362143 103 | ], 104 | [ 105 | 28.9342409, 106 | 46.936234 107 | ], 108 | [ 109 | 28.9342075, 110 | 46.9362562 111 | ], 112 | [ 113 | 28.9341548, 114 | 46.9362981 115 | ], 116 | [ 117 | 28.9339175, 118 | 46.9369724 119 | ], 120 | [ 121 | 28.9337035, 122 | 46.9375774 123 | ], 124 | [ 125 | 28.9334859, 126 | 46.9382794 127 | ], 128 | [ 129 | 28.9332572, 130 | 46.9383675 131 | ], 132 | [ 133 | 28.9330687, 134 | 46.9384514 135 | ], 136 | [ 137 | 28.9325218, 138 | 46.9386838 139 | ], 140 | [ 141 | 28.9316066, 142 | 46.9391024 143 | ], 144 | [ 145 | 28.9307342, 146 | 46.9395419 147 | ], 148 | [ 149 | 28.9293994, 150 | 46.9402143 151 | ], 152 | [ 153 | 28.9286403, 154 | 46.9406767 155 | ], 156 | [ 157 | 28.9282945, 158 | 46.9408879 159 | ], 160 | [ 161 | 28.9272211, 162 | 46.9415436 163 | ], 164 | [ 165 | 28.9263712, 166 | 46.9420816 167 | ], 168 | [ 169 | 28.9249947, 170 | 46.942953 171 | ], 172 | [ 173 | 28.9239066, 174 | 46.9436323 175 | ], 176 | [ 177 | 28.9221254, 178 | 46.9447189 179 | ], 180 | [ 181 | 28.9191779, 182 | 46.9465554 183 | ], 184 | [ 185 | 28.9165173, 186 | 46.9482301 187 | ], 188 | [ 189 | 28.9150858, 190 | 46.949129 191 | ], 192 | [ 193 | 28.9144607, 194 | 46.9495229 195 | ], 196 | [ 197 | 28.9141062, 198 | 46.9497497 199 | ], 200 | [ 201 | 28.9138094, 202 | 46.9499396 203 | ], 204 | [ 205 | 28.9134805, 206 | 46.9501529 207 | ], 208 | [ 209 | 28.9134338, 210 | 46.9501823 211 | ], 212 | [ 213 | 28.9132383, 214 | 46.9503053 215 | ], 216 | [ 217 | 28.9070463, 218 | 46.9541789 219 | ], 220 | [ 221 | 28.9039577, 222 | 46.9561204 223 | ], 224 | [ 225 | 28.9008648, 226 | 46.9580637 227 | ], 228 | [ 229 | 28.892969, 230 | 46.9630145 231 | ], 232 | [ 233 | 28.8904746, 234 | 46.9645863 235 | ], 236 | [ 237 | 28.8900324, 238 | 46.9648624 239 | ], 240 | [ 241 | 28.887452, 242 | 46.9664734 243 | ], 244 | [ 245 | 28.8868528, 246 | 46.9668524 247 | ], 248 | [ 249 | 28.8856973, 250 | 46.9675723 251 | ], 252 | [ 253 | 28.8772129, 254 | 46.9728994 255 | ], 256 | [ 257 | 28.8771295, 258 | 46.9729738 259 | ], 260 | [ 261 | 28.8766207, 262 | 46.9732934 263 | ], 264 | [ 265 | 28.8756872, 266 | 46.9738815 267 | ], 268 | [ 269 | 28.8747815, 270 | 46.9744423 271 | ], 272 | [ 273 | 28.8743822, 274 | 46.9746977 275 | ], 276 | [ 277 | 28.8735324, 278 | 46.9752241 279 | ], 280 | [ 281 | 28.8725143, 282 | 46.9758684 283 | ], 284 | [ 285 | 28.8724268, 286 | 46.9759192 287 | ], 288 | [ 289 | 28.8715402, 290 | 46.9764936 291 | ], 292 | [ 293 | 28.8711987, 294 | 46.9768414 295 | ], 296 | [ 297 | 28.8710752, 298 | 46.9769186 299 | ], 300 | [ 301 | 28.8704503, 302 | 46.9773093 303 | ], 304 | [ 305 | 28.8700704, 306 | 46.9775594 307 | ], 308 | [ 309 | 28.869767, 310 | 46.977754 311 | ], 312 | [ 313 | 28.8696276, 314 | 46.9778374 315 | ], 316 | [ 317 | 28.8694987, 318 | 46.9779074 319 | ], 320 | [ 321 | 28.8690132, 322 | 46.9780494 323 | ], 324 | [ 325 | 28.8687307, 326 | 46.978222 327 | ], 328 | [ 329 | 28.8680483, 330 | 46.9786725 331 | ], 332 | [ 333 | 28.8674353, 334 | 46.979109 335 | ], 336 | [ 337 | 28.8672217, 338 | 46.9792537 339 | ], 340 | [ 341 | 28.8671667, 342 | 46.979373 343 | ], 344 | [ 345 | 28.8670724, 346 | 46.979479 347 | ], 348 | [ 349 | 28.8669115, 350 | 46.9796266 351 | ], 352 | [ 353 | 28.8667929, 354 | 46.97972 355 | ], 356 | [ 357 | 28.866694, 358 | 46.9797948 359 | ], 360 | [ 361 | 28.8654046, 362 | 46.9807196 363 | ], 364 | [ 365 | 28.8642261, 366 | 46.9815665 367 | ], 368 | [ 369 | 28.8634641, 370 | 46.9821112 371 | ], 372 | [ 373 | 28.8633276, 374 | 46.9822045 375 | ], 376 | [ 377 | 28.8631867, 378 | 46.9822826 379 | ], 380 | [ 381 | 28.863072, 382 | 46.9823381 383 | ], 384 | [ 385 | 28.8626905, 386 | 46.9825025 387 | ], 388 | [ 389 | 28.8625077, 390 | 46.9826367 391 | ], 392 | [ 393 | 28.8623197, 394 | 46.9827721 395 | ], 396 | [ 397 | 28.8620205, 398 | 46.9829817 399 | ], 400 | [ 401 | 28.8617725, 402 | 46.9831652 403 | ], 404 | [ 405 | 28.8614373, 406 | 46.9834008 407 | ], 408 | [ 409 | 28.8613148, 410 | 46.9834811 411 | ], 412 | [ 413 | 28.8609474, 414 | 46.9839022 415 | ], 416 | [ 417 | 28.8595782, 418 | 46.9848648 419 | ], 420 | [ 421 | 28.8588484, 422 | 46.9852536 423 | ], 424 | [ 425 | 28.8584273, 426 | 46.985558 427 | ], 428 | [ 429 | 28.8580935, 430 | 46.9857875 431 | ], 432 | [ 433 | 28.8579707, 434 | 46.9858794 435 | ], 436 | [ 437 | 28.857418, 438 | 46.9862484 439 | ], 440 | [ 441 | 28.8568831, 442 | 46.9866436 443 | ], 444 | [ 445 | 28.8567436, 446 | 46.9867426 447 | ], 448 | [ 449 | 28.8564815, 450 | 46.9870416 451 | ], 452 | [ 453 | 28.8563752, 454 | 46.9871681 455 | ], 456 | [ 457 | 28.8562855, 458 | 46.9872436 459 | ], 460 | [ 461 | 28.8527873, 462 | 46.9897177 463 | ], 464 | [ 465 | 28.8512145, 466 | 46.9908352 467 | ], 468 | [ 469 | 28.8510753, 470 | 46.9909431 471 | ], 472 | [ 473 | 28.8491185, 474 | 46.9923468 475 | ], 476 | [ 477 | 28.8489912, 478 | 46.9924381 479 | ], 480 | [ 481 | 28.8483564, 482 | 46.9928934 483 | ], 484 | [ 485 | 28.8480285, 486 | 46.9930639 487 | ], 488 | [ 489 | 28.8477028, 490 | 46.9931903 491 | ], 492 | [ 493 | 28.8476519, 494 | 46.9932253 495 | ], 496 | [ 497 | 28.8473073, 498 | 46.9934745 499 | ], 500 | [ 501 | 28.8467274, 502 | 46.9938929 503 | ], 504 | [ 505 | 28.8461526, 506 | 46.9943044 507 | ], 508 | [ 509 | 28.8460279, 510 | 46.994425 511 | ], 512 | [ 513 | 28.845898, 514 | 46.9945639 515 | ], 516 | [ 517 | 28.8458296, 518 | 46.9946423 519 | ], 520 | [ 521 | 28.8456061, 522 | 46.9949165 523 | ], 524 | [ 525 | 28.8453688, 526 | 46.9952555 527 | ], 528 | [ 529 | 28.8452473, 530 | 46.9954513 531 | ], 532 | [ 533 | 28.8449586, 534 | 46.9960817 535 | ], 536 | [ 537 | 28.8446441, 538 | 46.9968714 539 | ], 540 | [ 541 | 28.8441978, 542 | 46.9980188 543 | ], 544 | [ 545 | 28.8435869, 546 | 46.9995893 547 | ], 548 | [ 549 | 28.8433565, 550 | 47.0001984 551 | ], 552 | [ 553 | 28.8430737, 554 | 47.0009287 555 | ], 556 | [ 557 | 28.8428964, 558 | 47.0013854 559 | ], 560 | [ 561 | 28.8427495, 562 | 47.0017793 563 | ], 564 | [ 565 | 28.8423165, 566 | 47.0029252 567 | ], 568 | [ 569 | 28.8422868, 570 | 47.0030059 571 | ], 572 | [ 573 | 28.8422257, 574 | 47.0031717 575 | ], 576 | [ 577 | 28.8421906, 578 | 47.003262 579 | ], 580 | [ 581 | 28.8421227, 582 | 47.0034718 583 | ], 584 | [ 585 | 28.8420454, 586 | 47.0037486 587 | ], 588 | [ 589 | 28.8419976, 590 | 47.004028 591 | ], 592 | [ 593 | 28.8419773, 594 | 47.0042556 595 | ], 596 | [ 597 | 28.8419835, 598 | 47.0045296 599 | ], 600 | [ 601 | 28.8420274, 602 | 47.0050016 603 | ], 604 | [ 605 | 28.8420353, 606 | 47.0050868 607 | ], 608 | [ 609 | 28.8422122, 610 | 47.0060967 611 | ], 612 | [ 613 | 28.8422919, 614 | 47.0066218 615 | ], 616 | [ 617 | 28.8427361, 618 | 47.0091546 619 | ], 620 | [ 621 | 28.8427602, 622 | 47.0092882 623 | ], 624 | [ 625 | 28.8428069, 626 | 47.0095808 627 | ], 628 | [ 629 | 28.8428446, 630 | 47.0097297 631 | ], 632 | [ 633 | 28.8428799, 634 | 47.0098095 635 | ], 636 | [ 637 | 28.842964, 638 | 47.0100041 639 | ], 640 | [ 641 | 28.8431094, 642 | 47.0102246 643 | ], 644 | [ 645 | 28.8433242, 646 | 47.0104735 647 | ], 648 | [ 649 | 28.843503, 650 | 47.010646 651 | ], 652 | [ 653 | 28.8435928, 654 | 47.0107215 655 | ], 656 | [ 657 | 28.843882, 658 | 47.0109404 659 | ], 660 | [ 661 | 28.844133, 662 | 47.0111275 663 | ], 664 | [ 665 | 28.8451856, 666 | 47.0119243 667 | ], 668 | [ 669 | 28.8453994, 670 | 47.012117 671 | ], 672 | [ 673 | 28.8456879, 674 | 47.012407 675 | ], 676 | [ 677 | 28.8458533, 678 | 47.0125289 679 | ], 680 | [ 681 | 28.8472376, 682 | 47.0134836 683 | ], 684 | [ 685 | 28.8475639, 686 | 47.0137113 687 | ], 688 | [ 689 | 28.8476553, 690 | 47.0137719 691 | ], 692 | [ 693 | 28.8481258, 694 | 47.014092 695 | ], 696 | [ 697 | 28.8477625, 698 | 47.0143348 699 | ], 700 | [ 701 | 28.8475708, 702 | 47.0144665 703 | ], 704 | [ 705 | 28.8474986, 706 | 47.0145111 707 | ], 708 | [ 709 | 28.8468593, 710 | 47.014948 711 | ], 712 | [ 713 | 28.8455938, 714 | 47.0157937 715 | ], 716 | [ 717 | 28.8447798, 718 | 47.0163407 719 | ], 720 | [ 721 | 28.8443122, 722 | 47.0166545 723 | ], 724 | [ 725 | 28.8439972, 726 | 47.0168623 727 | ], 728 | [ 729 | 28.8436585, 730 | 47.0170796 731 | ], 732 | [ 733 | 28.8435216, 734 | 47.0171713 735 | ], 736 | [ 737 | 28.8434177, 738 | 47.0172409 739 | ], 740 | [ 741 | 28.8421288, 742 | 47.0181035 743 | ], 744 | [ 745 | 28.8414779, 746 | 47.0185392 747 | ], 748 | [ 749 | 28.8414395, 750 | 47.0185649 751 | ], 752 | [ 753 | 28.8400379, 754 | 47.0195093 755 | ], 756 | [ 757 | 28.8395993, 758 | 47.0198007 759 | ], 760 | [ 761 | 28.8394736, 762 | 47.0198851 763 | ], 764 | [ 765 | 28.8394, 766 | 47.0199342 767 | ], 768 | [ 769 | 28.8391095, 770 | 47.020128 771 | ], 772 | [ 773 | 28.8382013, 774 | 47.020734 775 | ], 776 | [ 777 | 28.8380604, 778 | 47.0208279 779 | ], 780 | [ 781 | 28.8379461, 782 | 47.0209048 783 | ], 784 | [ 785 | 28.836746, 786 | 47.021712 787 | ], 788 | [ 789 | 28.8366739, 790 | 47.0217605 791 | ], 792 | [ 793 | 28.8365655, 794 | 47.021831 795 | ], 796 | [ 797 | 28.8362299, 798 | 47.0220491 799 | ], 800 | [ 801 | 28.8358931, 802 | 47.0222773 803 | ], 804 | [ 805 | 28.8353721, 806 | 47.0226306 807 | ], 808 | [ 809 | 28.83527, 810 | 47.0226972 811 | ], 812 | [ 813 | 28.8351729, 814 | 47.0227604 815 | ], 816 | [ 817 | 28.8347058, 818 | 47.0230705 819 | ], 820 | [ 821 | 28.8341039, 822 | 47.02347 823 | ], 824 | [ 825 | 28.833846, 826 | 47.0236502 827 | ], 828 | [ 829 | 28.8337778, 830 | 47.023696 831 | ], 832 | [ 833 | 28.8337344, 834 | 47.0237251 835 | ], 836 | [ 837 | 28.8336126, 838 | 47.0238078 839 | ], 840 | [ 841 | 28.8314679, 842 | 47.0252482 843 | ], 844 | [ 845 | 28.831138, 846 | 47.0254599 847 | ], 848 | [ 849 | 28.8309905, 850 | 47.0255605 851 | ], 852 | [ 853 | 28.8305919, 854 | 47.0253253 855 | ], 856 | [ 857 | 28.8295579, 858 | 47.0246112 859 | ], 860 | [ 861 | 28.8293657, 862 | 47.0244785 863 | ], 864 | [ 865 | 28.8292506, 866 | 47.0243964 867 | ], 868 | [ 869 | 28.8281515, 870 | 47.0236177 871 | ], 872 | [ 873 | 28.8280608, 874 | 47.0235485 875 | ], 876 | [ 877 | 28.8281475, 878 | 47.0234913 879 | ], 880 | [ 881 | 28.8287484, 882 | 47.0231066 883 | ], 884 | [ 885 | 28.8292275, 886 | 47.0227986 887 | ], 888 | [ 889 | 28.8296094, 890 | 47.0225532 891 | ] 892 | ] 893 | } 894 | } 895 | ] 896 | } -------------------------------------------------------------------------------- /tests/data/issue-4.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /tests/data/issue-6.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 155575971, 9 | "tags": { 10 | "bench": "yes", 11 | "covered": "yes", 12 | "highway": "platform", 13 | "name": "Thalwil Zentrum", 14 | "public_transport": "platform", 15 | "shelter": "yes" 16 | }, 17 | "nodes": [ 18 | 1679690155, 19 | 1679690156 20 | ] 21 | }, 22 | "geometry": { 23 | "type": "LineString", 24 | "coordinates": [ 25 | [ 26 | 8.5642032, 27 | 47.2946705 28 | ], 29 | [ 30 | 8.5643781, 31 | 47.2948068 32 | ] 33 | ] 34 | } 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /tests/data/issue-6.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0.6, 3 | "generator": "Overpass API 0.7.56.1002 b121d216", 4 | "osm3s": { 5 | "timestamp_osm_base": "2020-04-03T06:39:02Z", 6 | "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements": [ 9 | { 10 | "type": "way", 11 | "id": 155575971, 12 | "bounds": { 13 | "minlat": 47.2946705, 14 | "minlon": 8.5642032, 15 | "maxlat": 47.2948068, 16 | "maxlon": 8.5643781 17 | }, 18 | "nodes": [ 19 | 1679690155, 20 | 1679690156 21 | ], 22 | "geometry": [ 23 | { 24 | "lat": 47.2946705, 25 | "lon": 8.5642032 26 | }, 27 | { 28 | "lat": 47.2948068, 29 | "lon": 8.5643781 30 | } 31 | ], 32 | "tags": { 33 | "bench": "yes", 34 | "covered": "yes", 35 | "highway": "platform", 36 | "name": "Thalwil Zentrum", 37 | "public_transport": "platform", 38 | "shelter": "yes" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /tests/data/issue-7.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type":"FeatureCollection", 3 | "features":[ 4 | { 5 | "type":"Feature", 6 | "properties":{ 7 | "type":"way", 8 | "id":387345429, 9 | "tags":{ 10 | "conveying":"forward", 11 | "foot":"yes", 12 | "highway":"steps", 13 | "incline":"up", 14 | "indoor":"yes", 15 | "level":"-1", 16 | "tunnel":"yes" 17 | }, 18 | "nodes": [3906444343, 3906444348] 19 | }, 20 | "geometry":{ 21 | "type":"LineString", 22 | "coordinates":[ 23 | [ 24 | 8.5960345, 25 | 47.3969601 26 | ], 27 | [ 28 | 8.5961111, 29 | 47.3971207 30 | ] 31 | ] 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tests/data/issue-7.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":0.6, 3 | "generator":"Overpass API 0.7.56.1002 b121d216", 4 | "osm3s":{ 5 | "timestamp_osm_base":"2020-04-03T06:39:02Z", 6 | "copyright":"The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements":[ 9 | { 10 | "type":"way", 11 | "id":387345429, 12 | "bounds":{ 13 | "minlat":47.3969601, 14 | "minlon":8.5960345, 15 | "maxlat":47.3971207, 16 | "maxlon":8.5961111 17 | }, 18 | "nodes":[ 19 | 3906444343, 20 | 3906444348 21 | ], 22 | "geometry":[ 23 | { 24 | "lat":47.3969601, 25 | "lon":8.5960345 26 | }, 27 | { 28 | "lat":47.3971207, 29 | "lon":8.5961111 30 | } 31 | ], 32 | "tags":{ 33 | "conveying":"forward", 34 | "foot":"yes", 35 | "highway":"steps", 36 | "incline":"up", 37 | "indoor":"yes", 38 | "level":"-1", 39 | "tunnel":"yes" 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tests/data/issue-9-all.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "node", 8 | "id": 98939159 9 | }, 10 | "geometry": { 11 | "type": "Point", 12 | "coordinates": [ 13 | 7.1823979, 14 | 50.7430368 15 | ] 16 | } 17 | }, 18 | { 19 | "type": "Feature", 20 | "properties": { 21 | "type": "node", 22 | "id": 603010137 23 | }, 24 | "geometry": { 25 | "type": "Point", 26 | "coordinates": [ 27 | 7.1818215, 28 | 50.7425134 29 | ] 30 | } 31 | }, 32 | { 33 | "type": "Feature", 34 | "properties": { 35 | "type": "node", 36 | "id": 6038685227, 37 | "tags": { 38 | "barrier": "gate" 39 | } 40 | }, 41 | "geometry": { 42 | "type": "Point", 43 | "coordinates": [ 44 | 7.1815196, 45 | 50.7425764 46 | ] 47 | } 48 | }, 49 | { 50 | "type": "Feature", 51 | "properties": { 52 | "type": "node", 53 | "id": 603010136 54 | }, 55 | "geometry": { 56 | "type": "Point", 57 | "coordinates": [ 58 | 7.1813173, 59 | 50.7426186 60 | ] 61 | } 62 | }, 63 | { 64 | "type": "Feature", 65 | "properties": { 66 | "type": "node", 67 | "id": 603010135 68 | }, 69 | "geometry": { 70 | "type": "Point", 71 | "coordinates": [ 72 | 7.1809006, 73 | 50.7427531 74 | ] 75 | } 76 | }, 77 | { 78 | "type": "Feature", 79 | "properties": { 80 | "type": "node", 81 | "id": 603010113 82 | }, 83 | "geometry": { 84 | "type": "Point", 85 | "coordinates": [ 86 | 7.1814061, 87 | 50.7435033 88 | ] 89 | } 90 | }, 91 | { 92 | "type": "Feature", 93 | "properties": { 94 | "type": "way", 95 | "id": 361110018, 96 | "tags": { 97 | "source": "yahoo satellite" 98 | }, 99 | "nodes": [ 100 | 98939159, 603010137, 6038685227, 603010136, 603010135, 603010113 101 | ] 102 | }, 103 | "geometry": { 104 | "type": "LineString", 105 | "coordinates": [ 106 | [ 107 | 7.1823979, 108 | 50.7430368 109 | ], 110 | [ 111 | 7.1818215, 112 | 50.7425134 113 | ], 114 | [ 115 | 7.1815196, 116 | 50.7425764 117 | ], 118 | [ 119 | 7.1813173, 120 | 50.7426186 121 | ], 122 | [ 123 | 7.1809006, 124 | 50.7427531 125 | ], 126 | [ 127 | 7.1814061, 128 | 50.7435033 129 | ] 130 | ] 131 | } 132 | } 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /tests/data/issue-9.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 361110018, 9 | "tags": { 10 | "source": "yahoo satellite" 11 | }, 12 | "nodes": [ 13 | 98939159, 603010137, 6038685227, 603010136, 603010135, 603010113 14 | ] 15 | }, 16 | "geometry": { 17 | "type": "LineString", 18 | "coordinates": [ 19 | [ 20 | 7.1823979, 21 | 50.7430368 22 | ], 23 | [ 24 | 7.1818215, 25 | 50.7425134 26 | ], 27 | [ 28 | 7.1815196, 29 | 50.7425764 30 | ], 31 | [ 32 | 7.1813173, 33 | 50.7426186 34 | ], 35 | [ 36 | 7.1809006, 37 | 50.7427531 38 | ], 39 | [ 40 | 7.1814061, 41 | 50.7435033 42 | ] 43 | ] 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /tests/data/issue-9.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":0.6, 3 | "generator":"Overpass API 0.7.56.1004 6cd3eaec", 4 | "osm3s":{ 5 | "timestamp_osm_base":"2020-05-11T11:11:02Z", 6 | "copyright":"The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements":[ 9 | { 10 | "type":"node", 11 | "id":98939159, 12 | "lat":50.7430368, 13 | "lon":7.1823979 14 | }, 15 | { 16 | "type":"node", 17 | "id":603010137, 18 | "lat":50.7425134, 19 | "lon":7.1818215 20 | }, 21 | { 22 | "type":"node", 23 | "id":6038685227, 24 | "lat":50.7425764, 25 | "lon":7.1815196, 26 | "tags":{ 27 | "barrier":"gate" 28 | } 29 | }, 30 | { 31 | "type":"node", 32 | "id":603010136, 33 | "lat":50.7426186, 34 | "lon":7.1813173 35 | }, 36 | { 37 | "type":"node", 38 | "id":603010135, 39 | "lat":50.7427531, 40 | "lon":7.1809006 41 | }, 42 | { 43 | "type":"node", 44 | "id":603010113, 45 | "lat":50.7435033, 46 | "lon":7.1814061 47 | }, 48 | { 49 | "type":"way", 50 | "id":361110018, 51 | "nodes":[ 52 | 98939159, 53 | 603010137, 54 | 6038685227, 55 | 603010136, 56 | 603010135, 57 | 603010113 58 | ], 59 | "tags":{ 60 | "source":"yahoo satellite" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /tests/data/map.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 110768775, 9 | "tags": { 10 | "addr:housenumber": "27", 11 | "addr:street": "\u0414\u0440\u0430\u0433\u043e\u043c\u0430\u043d\u043e\u0432\u0430 \u0432\u0443\u043b\u0438\u0446\u044f", 12 | "building": "yes", 13 | "building:levels": "3" 14 | }, 15 | "nodes": [ 16 | 1264073607, 17 | 1264073676, 18 | 1264073662, 19 | 1264073656, 20 | 1264073600, 21 | 1264073609, 22 | 1264073612, 23 | 1264073627, 24 | 1264073651, 25 | 1264073645, 26 | 1264073646, 27 | 1264073621, 28 | 1264073661, 29 | 1264073606, 30 | 1264073684, 31 | 1264073686, 32 | 1264073675, 33 | 1264073630, 34 | 1264073608, 35 | 1264073655, 36 | 1264073666, 37 | 1264073648, 38 | 1264073626, 39 | 1264073659, 40 | 1264073619, 41 | 1264073597, 42 | 1264073653, 43 | 1264073614, 44 | 1264073607 45 | ], 46 | "timestamp": "2017-07-14T06:14:28Z", 47 | "user": "igornnn", 48 | "uid": 6248770, 49 | "version": 3 50 | }, 51 | "geometry": { 52 | "type": "Polygon", 53 | "coordinates": [ 54 | [ 55 | [ 56 | 26.2430262, 57 | 50.6136309 58 | ], 59 | [ 60 | 26.2429494, 61 | 50.6137121 62 | ], 63 | [ 64 | 26.2425283, 65 | 50.6135518 66 | ], 67 | [ 68 | 26.2425321, 69 | 50.613518 70 | ], 71 | [ 72 | 26.2425167, 73 | 50.6134765 74 | ], 75 | [ 76 | 26.2425019, 77 | 50.613461 78 | ], 79 | [ 80 | 26.2427864, 81 | 50.6132548 82 | ], 83 | [ 84 | 26.2426142, 85 | 50.6131605 86 | ], 87 | [ 88 | 26.242321, 89 | 50.6133762 90 | ], 91 | [ 92 | 26.2422633, 93 | 50.613374 94 | ], 95 | [ 96 | 26.2421697, 97 | 50.6133884 98 | ], 99 | [ 100 | 26.2421013, 101 | 50.6134187 102 | ], 103 | [ 104 | 26.242059, 105 | 50.6134534 106 | ], 107 | [ 108 | 26.2420276, 109 | 50.6135073 110 | ], 111 | [ 112 | 26.24203, 113 | 50.6135655 114 | ], 115 | [ 116 | 26.2420606, 117 | 50.6136173 118 | ], 119 | [ 120 | 26.2421109, 121 | 50.6136547 122 | ], 123 | [ 124 | 26.2421925, 125 | 50.6136854 126 | ], 127 | [ 128 | 26.2422829, 129 | 50.6136951 130 | ], 131 | [ 132 | 26.2423496, 133 | 50.6136888 134 | ], 135 | [ 136 | 26.2423733, 137 | 50.6136825 138 | ], 139 | [ 140 | 26.2430427, 141 | 50.6139376 142 | ], 143 | [ 144 | 26.2431244, 145 | 50.6138513 146 | ], 147 | [ 148 | 26.2432296, 149 | 50.6138914 150 | ], 151 | [ 152 | 26.2433569, 153 | 50.613757 154 | ], 155 | [ 156 | 26.2432125, 157 | 50.613702 158 | ], 159 | [ 160 | 26.2434099, 161 | 50.6134936 162 | ], 163 | [ 164 | 26.2432236, 165 | 50.6134225 166 | ], 167 | [ 168 | 26.2430262, 169 | 50.6136309 170 | ] 171 | ] 172 | ] 173 | } 174 | }, 175 | { 176 | "type": "Feature", 177 | "properties": { 178 | "type": "way", 179 | "id": 110768780, 180 | "tags": { 181 | "building": "yes" 182 | }, 183 | "nodes": [ 184 | 1264073627, 185 | 1264073612, 186 | 1264073605, 187 | 1264073663, 188 | 1264073627 189 | ], 190 | "timestamp": "2011-04-28T19:30:31Z", 191 | "user": "serges", 192 | "uid": 80691, 193 | "version": 1 194 | }, 195 | "geometry": { 196 | "type": "Polygon", 197 | "coordinates": [ 198 | [ 199 | [ 200 | 26.2426142, 201 | 50.6131605 202 | ], 203 | [ 204 | 26.2427864, 205 | 50.6132548 206 | ], 207 | [ 208 | 26.2433485, 209 | 50.6128414 210 | ], 211 | [ 212 | 26.2431763, 213 | 50.6127471 214 | ], 215 | [ 216 | 26.2426142, 217 | 50.6131605 218 | ] 219 | ] 220 | ] 221 | } 222 | }, 223 | { 224 | "type": "Feature", 225 | "properties": { 226 | "type": "way", 227 | "id": 110768785, 228 | "tags": { 229 | "building": "yes" 230 | }, 231 | "nodes": [ 232 | 1264073636, 233 | 1264073628, 234 | 1264073678, 235 | 1264073673, 236 | 1264073636 237 | ], 238 | "timestamp": "2011-04-28T19:30:32Z", 239 | "user": "serges", 240 | "uid": 80691, 241 | "version": 1 242 | }, 243 | "geometry": { 244 | "type": "Polygon", 245 | "coordinates": [ 246 | [ 247 | [ 248 | 26.2434148, 249 | 50.613227 250 | ], 251 | [ 252 | 26.2436145, 253 | 50.6133074 254 | ], 255 | [ 256 | 26.2439073, 257 | 50.6130147 258 | ], 259 | [ 260 | 26.2437077, 261 | 50.6129343 262 | ], 263 | [ 264 | 26.2434148, 265 | 50.613227 266 | ] 267 | ] 268 | ] 269 | } 270 | }, 271 | { 272 | "type": "Feature", 273 | "properties": { 274 | "type": "way", 275 | "id": 111451113, 276 | "tags": { 277 | "amenity": "school", 278 | "name": "\u0428\u043a\u043e\u043b\u0430 \u21162", 279 | "official_name": "\u0420\u0456\u0432\u043d\u0435\u043d\u0441\u044c\u043a\u0438\u0439 \u041d\u0412\u041a \"\u0428\u043a\u043e\u043b\u0430-\u043b\u0456\u0446\u0435\u0439\" \u21162 \u0420\u041c\u0420" 280 | }, 281 | "nodes": [ 282 | 1264073677, 283 | 1269773694, 284 | 1261273933, 285 | 1155159949, 286 | 4883483007, 287 | 1244184441, 288 | 3311728275, 289 | 1269773697, 290 | 1264073640, 291 | 1264073681, 292 | 1264073667, 293 | 1264073664, 294 | 1264073668, 295 | 1264073677 296 | ], 297 | "timestamp": "2017-05-29T12:05:03Z", 298 | "user": "Sergioni145", 299 | "uid": 2436512, 300 | "version": 3 301 | }, 302 | "geometry": { 303 | "type": "Polygon", 304 | "coordinates": [ 305 | [ 306 | [ 307 | 26.2447798, 308 | 50.6144751 309 | ], 310 | [ 311 | 26.2445933, 312 | 50.6146854 313 | ], 314 | [ 315 | 26.2442781, 316 | 50.6145787 317 | ], 318 | [ 319 | 26.2431467, 320 | 50.6141959 321 | ], 322 | [ 323 | 26.2437751, 324 | 50.613523 325 | ], 326 | [ 327 | 26.243832, 328 | 50.6134621 329 | ], 330 | [ 331 | 26.2447415, 332 | 50.6138889 333 | ], 334 | [ 335 | 26.2452392, 336 | 50.6141224 337 | ], 338 | [ 339 | 26.2452162, 340 | 50.6141442 341 | ], 342 | [ 343 | 26.2451292, 344 | 50.6142349 345 | ], 346 | [ 347 | 26.2450426, 348 | 50.6142014 349 | ], 350 | [ 351 | 26.2448618, 352 | 50.6143894 353 | ], 354 | [ 355 | 26.2448339, 356 | 50.6144184 357 | ], 358 | [ 359 | 26.2447798, 360 | 50.6144751 361 | ] 362 | ] 363 | ] 364 | } 365 | }, 366 | { 367 | "type": "Feature", 368 | "properties": { 369 | "type": "way", 370 | "id": 202526713, 371 | "tags": { 372 | "highway": "unclassified" 373 | }, 374 | "nodes": [ 375 | 1244184441, 376 | 4883483007, 377 | 1155159949 378 | ], 379 | "timestamp": "2017-05-29T12:05:03Z", 380 | "user": "Sergioni145", 381 | "uid": 2436512, 382 | "version": 2 383 | }, 384 | "geometry": { 385 | "type": "LineString", 386 | "coordinates": [ 387 | [ 388 | 26.243832, 389 | 50.6134621 390 | ], 391 | [ 392 | 26.2437751, 393 | 50.613523 394 | ], 395 | [ 396 | 26.2431467, 397 | 50.6141959 398 | ] 399 | ] 400 | } 401 | }, 402 | { 403 | "type": "Feature", 404 | "properties": { 405 | "type": "way", 406 | "id": 496671186, 407 | "tags": { 408 | "building:levels": "4", 409 | "building:part": "yes" 410 | }, 411 | "nodes": [ 412 | 4883483005, 413 | 4883483006, 414 | 1264073666, 415 | 4883482994, 416 | 4883482995, 417 | 1264073630, 418 | 4883482996, 419 | 1264073686, 420 | 4883482997, 421 | 4883482998, 422 | 1264073606, 423 | 4883482999, 424 | 1264073621, 425 | 4883483000, 426 | 4883483001, 427 | 1264073645, 428 | 1264073651, 429 | 4883483002, 430 | 4883483003, 431 | 1264073609, 432 | 4883483004, 433 | 1264073662, 434 | 4883482993, 435 | 4883483005 436 | ], 437 | "timestamp": "2017-05-29T12:05:00Z", 438 | "user": "Sergioni145", 439 | "uid": 2436512, 440 | "version": 1 441 | }, 442 | "geometry": { 443 | "type": "Polygon", 444 | "coordinates": [ 445 | [ 446 | [ 447 | 26.242479, 448 | 50.6136307 449 | ], 450 | [ 451 | 26.2424316, 452 | 50.6136611 453 | ], 454 | [ 455 | 26.2423733, 456 | 50.6136825 457 | ], 458 | [ 459 | 26.242314, 460 | 50.6136929 461 | ], 462 | [ 463 | 26.2422525, 464 | 50.6136939 465 | ], 466 | [ 467 | 26.2421925, 468 | 50.6136854 469 | ], 470 | [ 471 | 26.2421173, 472 | 50.6136586 473 | ], 474 | [ 475 | 26.2420606, 476 | 50.6136173 477 | ], 478 | [ 479 | 26.2420359, 480 | 50.6135827 481 | ], 482 | [ 483 | 26.2420247, 484 | 50.6135453 485 | ], 486 | [ 487 | 26.2420276, 488 | 50.6135073 489 | ], 490 | [ 491 | 26.2420533, 492 | 50.6134593 493 | ], 494 | [ 495 | 26.2421013, 496 | 50.6134187 497 | ], 498 | [ 499 | 26.2421492, 500 | 50.6133957 501 | ], 502 | [ 503 | 26.2422042, 504 | 50.6133805 505 | ], 506 | [ 507 | 26.2422633, 508 | 50.613374 509 | ], 510 | [ 511 | 26.242321, 512 | 50.6133762 513 | ], 514 | [ 515 | 26.2423942, 516 | 50.613392 517 | ], 518 | [ 519 | 26.2424565, 520 | 50.6134212 521 | ], 522 | [ 523 | 26.2425019, 524 | 50.613461 525 | ], 526 | [ 527 | 26.2425256, 528 | 50.6135052 529 | ], 530 | [ 531 | 26.2425283, 532 | 50.6135518 533 | ], 534 | [ 535 | 26.242512, 536 | 50.6135933 537 | ], 538 | [ 539 | 26.242479, 540 | 50.6136307 541 | ] 542 | ] 543 | ] 544 | } 545 | }, 546 | { 547 | "type": "Feature", 548 | "properties": { 549 | "type": "way", 550 | "id": 496671187, 551 | "tags": { 552 | "highway": "service" 553 | }, 554 | "nodes": [ 555 | 4883483007, 556 | 4883483008 557 | ], 558 | "timestamp": "2017-05-29T12:05:00Z", 559 | "user": "Sergioni145", 560 | "uid": 2436512, 561 | "version": 1 562 | }, 563 | "geometry": { 564 | "type": "LineString", 565 | "coordinates": [ 566 | [ 567 | 26.2437751, 568 | 50.613523 569 | ], 570 | [ 571 | 26.2435109, 572 | 50.6134091 573 | ] 574 | ] 575 | } 576 | }, 577 | { 578 | "type": "Feature", 579 | "properties": { 580 | "type": "way", 581 | "id": 496671188, 582 | "tags": { 583 | "landuse": "grass" 584 | }, 585 | "nodes": [ 586 | 4883483009, 587 | 4883483010, 588 | 4883483011, 589 | 4883483012, 590 | 4883483009 591 | ], 592 | "timestamp": "2017-05-29T12:05:00Z", 593 | "user": "Sergioni145", 594 | "uid": 2436512, 595 | "version": 1 596 | }, 597 | "geometry": { 598 | "type": "Polygon", 599 | "coordinates": [ 600 | [ 601 | [ 602 | 26.2433173, 603 | 50.6136731 604 | ], 605 | [ 606 | 26.2435165, 607 | 50.6134522 608 | ], 609 | [ 610 | 26.2436622, 611 | 50.6135051 612 | ], 613 | [ 614 | 26.2434631, 615 | 50.613726 616 | ], 617 | [ 618 | 26.2433173, 619 | 50.6136731 620 | ] 621 | ] 622 | ] 623 | } 624 | }, 625 | { 626 | "type": "Feature", 627 | "properties": { 628 | "type": "way", 629 | "id": 496671189, 630 | "tags": { 631 | "landuse": "grass" 632 | }, 633 | "nodes": [ 634 | 4883483013, 635 | 4883483014, 636 | 4883483015, 637 | 4883483016, 638 | 4883483013 639 | ], 640 | "timestamp": "2017-05-29T12:05:00Z", 641 | "user": "Sergioni145", 642 | "uid": 2436512, 643 | "version": 1 644 | }, 645 | "geometry": { 646 | "type": "Polygon", 647 | "coordinates": [ 648 | [ 649 | [ 650 | 26.2436134, 651 | 50.6133774 652 | ], 653 | [ 654 | 26.2439633, 655 | 50.6130248 656 | ], 657 | [ 658 | 26.2441211, 659 | 50.6130879 660 | ], 661 | [ 662 | 26.2437712, 663 | 50.6134405 664 | ], 665 | [ 666 | 26.2436134, 667 | 50.6133774 668 | ] 669 | ] 670 | ] 671 | } 672 | }, 673 | { 674 | "type": "Feature", 675 | "properties": { 676 | "type": "relation", 677 | "id": 1562147, 678 | "tags": { 679 | "building": "yes", 680 | "type": "multipolygon" 681 | }, 682 | "timestamp": "2017-03-21T16:01:43Z", 683 | "user": "Aurimas Fi\u0161eras", 684 | "uid": 651869, 685 | "version": 2 686 | }, 687 | "geometry": { 688 | "type": "MultiPolygon", 689 | "coordinates": [ 690 | [ 691 | [ 692 | [ 693 | 26.244626, 694 | 50.6140348 695 | ], 696 | [ 697 | 26.2447092, 698 | 50.6139482 699 | ], 700 | [ 701 | 26.2452162, 702 | 50.6141442 703 | ], 704 | [ 705 | 26.2451292, 706 | 50.6142349 707 | ], 708 | [ 709 | 26.2450426, 710 | 50.6142014 711 | ], 712 | [ 713 | 26.2448618, 714 | 50.6143894 715 | ], 716 | [ 717 | 26.2448339, 718 | 50.6144184 719 | ], 720 | [ 721 | 26.2447798, 722 | 50.6144751 723 | ], 724 | [ 725 | 26.2435259, 726 | 50.6139904 727 | ], 728 | [ 729 | 26.2438381, 730 | 50.6136652 731 | ], 732 | [ 733 | 26.2440783, 734 | 50.613758 735 | ], 736 | [ 737 | 26.2440091, 738 | 50.6138301 739 | ], 740 | [ 741 | 26.243914, 742 | 50.6137934 743 | ], 744 | [ 745 | 26.2437785, 746 | 50.6139345 747 | ], 748 | [ 749 | 26.2441465, 750 | 50.6140768 751 | ], 752 | [ 753 | 26.2443056, 754 | 50.613911 755 | ], 756 | [ 757 | 26.2444499, 758 | 50.6139668 759 | ], 760 | [ 761 | 26.2444885, 762 | 50.6139266 763 | ], 764 | [ 765 | 26.2445601, 766 | 50.6139542 767 | ], 768 | [ 769 | 26.2445215, 770 | 50.6139944 771 | ], 772 | [ 773 | 26.244626, 774 | 50.6140348 775 | ] 776 | ] 777 | ] 778 | ] 779 | } 780 | }, 781 | { 782 | "type": "Feature", 783 | "properties": { 784 | "type": "relation", 785 | "id": 1802915, 786 | "tags": { 787 | "name": "\u0414\u0440\u0430\u0433\u043e\u043c\u0430\u043d\u043e\u0432\u0430 \u0432\u0443\u043b\u0438\u0446\u044f", 788 | "type": "associatedStreet" 789 | }, 790 | "timestamp": "2018-02-04T12:21:30Z", 791 | "user": "\u0421\u0435\u0440\u0433\u0456\u0439 \u0414\u0443\u0431\u0438\u043a", 792 | "uid": 423716, 793 | "version": 9 794 | }, 795 | "geometry": { 796 | "type": "LineString", 797 | "coordinates": [ 798 | [ 799 | 26.2430262, 800 | 50.6136309 801 | ], 802 | [ 803 | 26.2429494, 804 | 50.6137121 805 | ], 806 | [ 807 | 26.2425283, 808 | 50.6135518 809 | ], 810 | [ 811 | 26.2425321, 812 | 50.613518 813 | ], 814 | [ 815 | 26.2425167, 816 | 50.6134765 817 | ], 818 | [ 819 | 26.2425019, 820 | 50.613461 821 | ], 822 | [ 823 | 26.2427864, 824 | 50.6132548 825 | ], 826 | [ 827 | 26.2426142, 828 | 50.6131605 829 | ], 830 | [ 831 | 26.242321, 832 | 50.6133762 833 | ], 834 | [ 835 | 26.2422633, 836 | 50.613374 837 | ], 838 | [ 839 | 26.2421697, 840 | 50.6133884 841 | ], 842 | [ 843 | 26.2421013, 844 | 50.6134187 845 | ], 846 | [ 847 | 26.242059, 848 | 50.6134534 849 | ], 850 | [ 851 | 26.2420276, 852 | 50.6135073 853 | ], 854 | [ 855 | 26.24203, 856 | 50.6135655 857 | ], 858 | [ 859 | 26.2420606, 860 | 50.6136173 861 | ], 862 | [ 863 | 26.2421109, 864 | 50.6136547 865 | ], 866 | [ 867 | 26.2421925, 868 | 50.6136854 869 | ], 870 | [ 871 | 26.2422829, 872 | 50.6136951 873 | ], 874 | [ 875 | 26.2423496, 876 | 50.6136888 877 | ], 878 | [ 879 | 26.2423733, 880 | 50.6136825 881 | ], 882 | [ 883 | 26.2430427, 884 | 50.6139376 885 | ], 886 | [ 887 | 26.2431244, 888 | 50.6138513 889 | ], 890 | [ 891 | 26.2432296, 892 | 50.6138914 893 | ], 894 | [ 895 | 26.2433569, 896 | 50.613757 897 | ], 898 | [ 899 | 26.2432125, 900 | 50.613702 901 | ], 902 | [ 903 | 26.2434099, 904 | 50.6134936 905 | ], 906 | [ 907 | 26.2432236, 908 | 50.6134225 909 | ], 910 | [ 911 | 26.2430262, 912 | 50.6136309 913 | ] 914 | ] 915 | } 916 | } 917 | ] 918 | } 919 | -------------------------------------------------------------------------------- /tests/data/map.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0.6, 3 | "elements": [ 4 | { 5 | "id": 4883483009, 6 | "lat": 50.6136731, 7 | "lon": 26.2433173, 8 | "timestamp": "2017-05-29T12:04:57Z", 9 | "version": 1, 10 | "changeset": 49071406, 11 | "user": "Sergioni145", 12 | "uid": 2436512, 13 | "type": "node" 14 | }, 15 | { 16 | "id": 1264073640, 17 | "lat": 50.6141442, 18 | "lon": 26.2452162, 19 | "timestamp": "2011-04-28T19:30:27Z", 20 | "version": 1, 21 | "changeset": 7997837, 22 | "user": "serges", 23 | "uid": 80691, 24 | "type": "node" 25 | }, 26 | { 27 | "id": 1264073646, 28 | "lat": 50.6133884, 29 | "lon": 26.2421697, 30 | "timestamp": "2011-04-28T19:30:28Z", 31 | "version": 1, 32 | "changeset": 7997837, 33 | "user": "serges", 34 | "uid": 80691, 35 | "type": "node" 36 | }, 37 | { 38 | "id": 1264073668, 39 | "lat": 50.6144184, 40 | "lon": 26.2448339, 41 | "timestamp": "2011-04-28T19:30:29Z", 42 | "version": 1, 43 | "changeset": 7997837, 44 | "user": "serges", 45 | "uid": 80691, 46 | "type": "node" 47 | }, 48 | { 49 | "id": 1264073619, 50 | "lat": 50.613757, 51 | "lon": 26.2433569, 52 | "timestamp": "2011-04-28T19:30:26Z", 53 | "version": 1, 54 | "changeset": 7997837, 55 | "user": "serges", 56 | "uid": 80691, 57 | "type": "node" 58 | }, 59 | { 60 | "id": 4883483002, 61 | "lat": 50.613392, 62 | "lon": 26.2423942, 63 | "timestamp": "2017-05-29T12:04:57Z", 64 | "version": 1, 65 | "changeset": 49071406, 66 | "user": "Sergioni145", 67 | "uid": 2436512, 68 | "type": "node" 69 | }, 70 | { 71 | "id": 1264073662, 72 | "lat": 50.6135518, 73 | "lon": 26.2425283, 74 | "timestamp": "2017-05-29T12:05:02Z", 75 | "version": 2, 76 | "changeset": 49071406, 77 | "user": "Sergioni145", 78 | "uid": 2436512, 79 | "type": "node" 80 | }, 81 | { 82 | "id": 1264073659, 83 | "lat": 50.6138914, 84 | "lon": 26.2432296, 85 | "timestamp": "2011-04-28T19:30:28Z", 86 | "version": 1, 87 | "changeset": 7997837, 88 | "user": "serges", 89 | "uid": 80691, 90 | "type": "node" 91 | }, 92 | { 93 | "id": 4883483001, 94 | "lat": 50.6133805, 95 | "lon": 26.2422042, 96 | "timestamp": "2017-05-29T12:04:57Z", 97 | "version": 1, 98 | "changeset": 49071406, 99 | "user": "Sergioni145", 100 | "uid": 2436512, 101 | "type": "node" 102 | }, 103 | { 104 | "id": 1264073680, 105 | "lat": 50.6138301, 106 | "lon": 26.2440091, 107 | "timestamp": "2011-04-28T19:30:29Z", 108 | "version": 1, 109 | "changeset": 7997837, 110 | "user": "serges", 111 | "uid": 80691, 112 | "type": "node" 113 | }, 114 | { 115 | "id": 1264073684, 116 | "lat": 50.6135655, 117 | "lon": 26.24203, 118 | "timestamp": "2011-04-28T19:30:30Z", 119 | "version": 1, 120 | "changeset": 7997837, 121 | "user": "serges", 122 | "uid": 80691, 123 | "type": "node" 124 | }, 125 | { 126 | "id": 1264073596, 127 | "lat": 50.6139345, 128 | "lon": 26.2437785, 129 | "timestamp": "2011-04-28T19:30:25Z", 130 | "version": 1, 131 | "changeset": 7997837, 132 | "user": "serges", 133 | "uid": 80691, 134 | "type": "node" 135 | }, 136 | { 137 | "id": 4883483015, 138 | "lat": 50.6130879, 139 | "lon": 26.2441211, 140 | "timestamp": "2017-05-29T12:04:57Z", 141 | "version": 1, 142 | "changeset": 49071406, 143 | "user": "Sergioni145", 144 | "uid": 2436512, 145 | "type": "node" 146 | }, 147 | { 148 | "id": 1264073664, 149 | "lat": 50.6143894, 150 | "lon": 26.2448618, 151 | "timestamp": "2011-04-28T19:30:28Z", 152 | "version": 1, 153 | "changeset": 7997837, 154 | "user": "serges", 155 | "uid": 80691, 156 | "type": "node" 157 | }, 158 | { 159 | "id": 1264073645, 160 | "lat": 50.613374, 161 | "lon": 26.2422633, 162 | "timestamp": "2017-05-29T12:05:02Z", 163 | "version": 2, 164 | "changeset": 49071406, 165 | "user": "Sergioni145", 166 | "uid": 2436512, 167 | "type": "node" 168 | }, 169 | { 170 | "id": 1264073609, 171 | "lat": 50.613461, 172 | "lon": 26.2425019, 173 | "timestamp": "2017-05-29T12:05:02Z", 174 | "version": 2, 175 | "changeset": 49071406, 176 | "user": "Sergioni145", 177 | "uid": 2436512, 178 | "type": "node" 179 | }, 180 | { 181 | "id": 4883483014, 182 | "lat": 50.6130248, 183 | "lon": 26.2439633, 184 | "timestamp": "2017-05-29T12:04:57Z", 185 | "version": 1, 186 | "changeset": 49071406, 187 | "user": "Sergioni145", 188 | "uid": 2436512, 189 | "type": "node" 190 | }, 191 | { 192 | "id": 1264073648, 193 | "lat": 50.6139376, 194 | "lon": 26.2430427, 195 | "timestamp": "2011-04-28T19:30:28Z", 196 | "version": 1, 197 | "changeset": 7997837, 198 | "user": "serges", 199 | "uid": 80691, 200 | "type": "node" 201 | }, 202 | { 203 | "id": 1264073686, 204 | "lat": 50.6136173, 205 | "lon": 26.2420606, 206 | "timestamp": "2017-05-29T12:05:02Z", 207 | "version": 2, 208 | "changeset": 49071406, 209 | "user": "Sergioni145", 210 | "uid": 2436512, 211 | "type": "node" 212 | }, 213 | { 214 | "id": 4883483012, 215 | "lat": 50.613726, 216 | "lon": 26.2434631, 217 | "timestamp": "2017-05-29T12:04:57Z", 218 | "version": 1, 219 | "changeset": 49071406, 220 | "user": "Sergioni145", 221 | "uid": 2436512, 222 | "type": "node" 223 | }, 224 | { 225 | "id": 1264073643, 226 | "lat": 50.6139266, 227 | "lon": 26.2444885, 228 | "timestamp": "2011-04-28T19:30:27Z", 229 | "version": 1, 230 | "changeset": 7997837, 231 | "user": "serges", 232 | "uid": 80691, 233 | "type": "node" 234 | }, 235 | { 236 | "id": 1264073599, 237 | "lat": 50.6139482, 238 | "lon": 26.2447092, 239 | "timestamp": "2011-04-28T19:30:25Z", 240 | "version": 1, 241 | "changeset": 7997837, 242 | "user": "serges", 243 | "uid": 80691, 244 | "type": "node" 245 | }, 246 | { 247 | "id": 4883482996, 248 | "lat": 50.6136586, 249 | "lon": 26.2421173, 250 | "timestamp": "2017-05-29T12:04:57Z", 251 | "version": 1, 252 | "changeset": 49071406, 253 | "user": "Sergioni145", 254 | "uid": 2436512, 255 | "type": "node" 256 | }, 257 | { 258 | "id": 1264073626, 259 | "lat": 50.6138513, 260 | "lon": 26.2431244, 261 | "timestamp": "2011-04-28T19:30:26Z", 262 | "version": 1, 263 | "changeset": 7997837, 264 | "user": "serges", 265 | "uid": 80691, 266 | "type": "node" 267 | }, 268 | { 269 | "id": 1264073600, 270 | "lat": 50.6134765, 271 | "lon": 26.2425167, 272 | "timestamp": "2011-04-28T19:30:25Z", 273 | "version": 1, 274 | "changeset": 7997837, 275 | "user": "serges", 276 | "uid": 80691, 277 | "type": "node" 278 | }, 279 | { 280 | "id": 1264073605, 281 | "lat": 50.6128414, 282 | "lon": 26.2433485, 283 | "timestamp": "2011-04-28T19:30:25Z", 284 | "version": 1, 285 | "changeset": 7997837, 286 | "user": "serges", 287 | "uid": 80691, 288 | "type": "node" 289 | }, 290 | { 291 | "id": 1264073624, 292 | "lat": 50.6140768, 293 | "lon": 26.2441465, 294 | "timestamp": "2011-04-28T19:30:26Z", 295 | "version": 1, 296 | "changeset": 7997837, 297 | "user": "serges", 298 | "uid": 80691, 299 | "type": "node" 300 | }, 301 | { 302 | "id": 1264073607, 303 | "lat": 50.6136309, 304 | "lon": 26.2430262, 305 | "timestamp": "2011-04-28T19:30:25Z", 306 | "version": 1, 307 | "changeset": 7997837, 308 | "user": "serges", 309 | "uid": 80691, 310 | "type": "node" 311 | }, 312 | { 313 | "id": 4883483000, 314 | "lat": 50.6133957, 315 | "lon": 26.2421492, 316 | "timestamp": "2017-05-29T12:04:57Z", 317 | "version": 1, 318 | "changeset": 49071406, 319 | "user": "Sergioni145", 320 | "uid": 2436512, 321 | "type": "node" 322 | }, 323 | { 324 | "id": 1264073630, 325 | "lat": 50.6136854, 326 | "lon": 26.2421925, 327 | "timestamp": "2017-05-29T12:05:02Z", 328 | "version": 2, 329 | "changeset": 49071406, 330 | "user": "Sergioni145", 331 | "uid": 2436512, 332 | "type": "node" 333 | }, 334 | { 335 | "id": 1264073681, 336 | "lat": 50.6142349, 337 | "lon": 26.2451292, 338 | "timestamp": "2011-04-28T19:30:29Z", 339 | "version": 1, 340 | "changeset": 7997837, 341 | "user": "serges", 342 | "uid": 80691, 343 | "type": "node" 344 | }, 345 | { 346 | "id": 4883482993, 347 | "lat": 50.6135933, 348 | "lon": 26.242512, 349 | "timestamp": "2017-05-29T12:04:57Z", 350 | "version": 1, 351 | "changeset": 49071406, 352 | "user": "Sergioni145", 353 | "uid": 2436512, 354 | "type": "node" 355 | }, 356 | { 357 | "id": 1264073670, 358 | "lat": 50.613911, 359 | "lon": 26.2443056, 360 | "timestamp": "2011-04-28T19:30:29Z", 361 | "version": 1, 362 | "changeset": 7997837, 363 | "user": "serges", 364 | "uid": 80691, 365 | "type": "node" 366 | }, 367 | { 368 | "id": 1264073651, 369 | "lat": 50.6133762, 370 | "lon": 26.242321, 371 | "timestamp": "2017-05-29T12:05:02Z", 372 | "version": 2, 373 | "changeset": 49071406, 374 | "user": "Sergioni145", 375 | "uid": 2436512, 376 | "type": "node" 377 | }, 378 | { 379 | "id": 4883483011, 380 | "lat": 50.6135051, 381 | "lon": 26.2436622, 382 | "timestamp": "2017-05-29T12:04:57Z", 383 | "version": 1, 384 | "changeset": 49071406, 385 | "user": "Sergioni145", 386 | "uid": 2436512, 387 | "type": "node" 388 | }, 389 | { 390 | "id": 4883482994, 391 | "lat": 50.6136929, 392 | "lon": 26.242314, 393 | "timestamp": "2017-05-29T12:04:57Z", 394 | "version": 1, 395 | "changeset": 49071406, 396 | "user": "Sergioni145", 397 | "uid": 2436512, 398 | "type": "node" 399 | }, 400 | { 401 | "id": 1264073597, 402 | "lat": 50.613702, 403 | "lon": 26.2432125, 404 | "timestamp": "2011-04-28T19:30:25Z", 405 | "version": 1, 406 | "changeset": 7997837, 407 | "user": "serges", 408 | "uid": 80691, 409 | "type": "node" 410 | }, 411 | { 412 | "id": 1264073611, 413 | "lat": 50.6136652, 414 | "lon": 26.2438381, 415 | "timestamp": "2011-04-28T19:30:26Z", 416 | "version": 1, 417 | "changeset": 7997837, 418 | "user": "serges", 419 | "uid": 80691, 420 | "type": "node" 421 | }, 422 | { 423 | "id": 1264073606, 424 | "lat": 50.6135073, 425 | "lon": 26.2420276, 426 | "timestamp": "2017-05-29T12:05:02Z", 427 | "version": 2, 428 | "changeset": 49071406, 429 | "user": "Sergioni145", 430 | "uid": 2436512, 431 | "type": "node" 432 | }, 433 | { 434 | "id": 4883483007, 435 | "lat": 50.613523, 436 | "lon": 26.2437751, 437 | "timestamp": "2017-05-29T12:04:57Z", 438 | "version": 1, 439 | "changeset": 49071406, 440 | "user": "Sergioni145", 441 | "uid": 2436512, 442 | "type": "node" 443 | }, 444 | { 445 | "id": 1264073627, 446 | "lat": 50.6131605, 447 | "lon": 26.2426142, 448 | "timestamp": "2011-04-28T19:30:26Z", 449 | "version": 1, 450 | "changeset": 7997837, 451 | "user": "serges", 452 | "uid": 80691, 453 | "type": "node" 454 | }, 455 | { 456 | "id": 1264073636, 457 | "lat": 50.613227, 458 | "lon": 26.2434148, 459 | "timestamp": "2011-04-28T19:30:27Z", 460 | "version": 1, 461 | "changeset": 7997837, 462 | "user": "serges", 463 | "uid": 80691, 464 | "type": "node" 465 | }, 466 | { 467 | "id": 1264073673, 468 | "lat": 50.6129343, 469 | "lon": 26.2437077, 470 | "timestamp": "2011-04-28T19:30:29Z", 471 | "version": 1, 472 | "changeset": 7997837, 473 | "user": "serges", 474 | "uid": 80691, 475 | "type": "node" 476 | }, 477 | { 478 | "id": 1264073655, 479 | "lat": 50.6136888, 480 | "lon": 26.2423496, 481 | "timestamp": "2011-04-28T19:30:28Z", 482 | "version": 1, 483 | "changeset": 7997837, 484 | "user": "serges", 485 | "uid": 80691, 486 | "type": "node" 487 | }, 488 | { 489 | "id": 4883482997, 490 | "lat": 50.6135827, 491 | "lon": 26.2420359, 492 | "timestamp": "2017-05-29T12:04:57Z", 493 | "version": 1, 494 | "changeset": 49071406, 495 | "user": "Sergioni145", 496 | "uid": 2436512, 497 | "type": "node" 498 | }, 499 | { 500 | "id": 1264073675, 501 | "lat": 50.6136547, 502 | "lon": 26.2421109, 503 | "timestamp": "2011-04-28T19:30:29Z", 504 | "version": 1, 505 | "changeset": 7997837, 506 | "user": "serges", 507 | "uid": 80691, 508 | "type": "node" 509 | }, 510 | { 511 | "id": 1264073661, 512 | "lat": 50.6134534, 513 | "lon": 26.242059, 514 | "timestamp": "2011-04-28T19:30:28Z", 515 | "version": 1, 516 | "changeset": 7997837, 517 | "user": "serges", 518 | "uid": 80691, 519 | "type": "node" 520 | }, 521 | { 522 | "id": 1264073656, 523 | "lat": 50.613518, 524 | "lon": 26.2425321, 525 | "timestamp": "2011-04-28T19:30:28Z", 526 | "version": 1, 527 | "changeset": 7997837, 528 | "user": "serges", 529 | "uid": 80691, 530 | "type": "node" 531 | }, 532 | { 533 | "id": 1264073612, 534 | "lat": 50.6132548, 535 | "lon": 26.2427864, 536 | "timestamp": "2011-04-28T19:30:26Z", 537 | "version": 1, 538 | "changeset": 7997837, 539 | "user": "serges", 540 | "uid": 80691, 541 | "type": "node" 542 | }, 543 | { 544 | "id": 1264073608, 545 | "lat": 50.6136951, 546 | "lon": 26.2422829, 547 | "timestamp": "2011-04-28T19:30:25Z", 548 | "version": 1, 549 | "changeset": 7997837, 550 | "user": "serges", 551 | "uid": 80691, 552 | "type": "node" 553 | }, 554 | { 555 | "id": 1264073663, 556 | "lat": 50.6127471, 557 | "lon": 26.2431763, 558 | "timestamp": "2011-04-28T19:30:28Z", 559 | "version": 1, 560 | "changeset": 7997837, 561 | "user": "serges", 562 | "uid": 80691, 563 | "type": "node" 564 | }, 565 | { 566 | "id": 4883483016, 567 | "lat": 50.6134405, 568 | "lon": 26.2437712, 569 | "timestamp": "2017-05-29T12:04:57Z", 570 | "version": 1, 571 | "changeset": 49071406, 572 | "user": "Sergioni145", 573 | "uid": 2436512, 574 | "type": "node" 575 | }, 576 | { 577 | "id": 1264073667, 578 | "lat": 50.6142014, 579 | "lon": 26.2450426, 580 | "timestamp": "2011-04-28T19:30:28Z", 581 | "version": 1, 582 | "changeset": 7997837, 583 | "user": "serges", 584 | "uid": 80691, 585 | "type": "node" 586 | }, 587 | { 588 | "id": 1264073628, 589 | "lat": 50.6133074, 590 | "lon": 26.2436145, 591 | "timestamp": "2011-04-28T19:30:26Z", 592 | "version": 1, 593 | "changeset": 7997837, 594 | "user": "serges", 595 | "uid": 80691, 596 | "type": "node" 597 | }, 598 | { 599 | "id": 1264073666, 600 | "lat": 50.6136825, 601 | "lon": 26.2423733, 602 | "timestamp": "2017-05-29T12:05:02Z", 603 | "version": 2, 604 | "changeset": 49071406, 605 | "user": "Sergioni145", 606 | "uid": 2436512, 607 | "type": "node" 608 | }, 609 | { 610 | "id": 4883482998, 611 | "lat": 50.6135453, 612 | "lon": 26.2420247, 613 | "timestamp": "2017-05-29T12:04:57Z", 614 | "version": 1, 615 | "changeset": 49071406, 616 | "user": "Sergioni145", 617 | "uid": 2436512, 618 | "type": "node" 619 | }, 620 | { 621 | "id": 1269773694, 622 | "lat": 50.6146854, 623 | "lon": 26.2445933, 624 | "timestamp": "2011-05-01T21:01:53Z", 625 | "version": 1, 626 | "changeset": 8026116, 627 | "user": "serges", 628 | "uid": 80691, 629 | "type": "node" 630 | }, 631 | { 632 | "id": 1264073614, 633 | "lat": 50.6134225, 634 | "lon": 26.2432236, 635 | "timestamp": "2011-04-28T19:30:26Z", 636 | "version": 1, 637 | "changeset": 7997837, 638 | "user": "serges", 639 | "uid": 80691, 640 | "type": "node" 641 | }, 642 | { 643 | "id": 1264073677, 644 | "lat": 50.6144751, 645 | "lon": 26.2447798, 646 | "timestamp": "2011-04-28T19:30:29Z", 647 | "version": 1, 648 | "changeset": 7997837, 649 | "user": "serges", 650 | "uid": 80691, 651 | "type": "node" 652 | }, 653 | { 654 | "id": 1264073625, 655 | "lat": 50.6139904, 656 | "lon": 26.2435259, 657 | "timestamp": "2011-04-28T19:30:26Z", 658 | "version": 1, 659 | "changeset": 7997837, 660 | "user": "serges", 661 | "uid": 80691, 662 | "type": "node" 663 | }, 664 | { 665 | "id": 1264073678, 666 | "lat": 50.6130147, 667 | "lon": 26.2439073, 668 | "timestamp": "2011-04-28T19:30:29Z", 669 | "version": 1, 670 | "changeset": 7997837, 671 | "user": "serges", 672 | "uid": 80691, 673 | "type": "node" 674 | }, 675 | { 676 | "id": 4883483006, 677 | "lat": 50.6136611, 678 | "lon": 26.2424316, 679 | "timestamp": "2017-05-29T12:04:57Z", 680 | "version": 1, 681 | "changeset": 49071406, 682 | "user": "Sergioni145", 683 | "uid": 2436512, 684 | "type": "node" 685 | }, 686 | { 687 | "id": 3311728275, 688 | "lat": 50.6138889, 689 | "lon": 26.2447415, 690 | "timestamp": "2015-01-26T09:48:18Z", 691 | "version": 1, 692 | "changeset": 28414321, 693 | "user": "TheSteelRat", 694 | "uid": 614722, 695 | "type": "node", 696 | "tags": { 697 | "barrier": "block", 698 | "bicycle": "yes", 699 | "foot": "yes", 700 | "horse": "yes", 701 | "motor_vehicle": "no", 702 | "note": "Low wall of bricks in the middle of the lane specifically for blocking passage", 703 | "source": "survey 25 January 2015" 704 | } 705 | }, 706 | { 707 | "id": 4883483004, 708 | "lat": 50.6135052, 709 | "lon": 26.2425256, 710 | "timestamp": "2017-05-29T12:04:57Z", 711 | "version": 1, 712 | "changeset": 49071406, 713 | "user": "Sergioni145", 714 | "uid": 2436512, 715 | "type": "node" 716 | }, 717 | { 718 | "id": 1155159949, 719 | "lat": 50.6141959, 720 | "lon": 26.2431467, 721 | "timestamp": "2011-04-26T20:06:35Z", 722 | "version": 3, 723 | "changeset": 7979131, 724 | "user": "serges", 725 | "uid": 80691, 726 | "type": "node" 727 | }, 728 | { 729 | "id": 1261273933, 730 | "lat": 50.6145787, 731 | "lon": 26.2442781, 732 | "timestamp": "2011-04-26T20:06:28Z", 733 | "version": 1, 734 | "changeset": 7979131, 735 | "user": "serges", 736 | "uid": 80691, 737 | "type": "node" 738 | }, 739 | { 740 | "id": 4883483005, 741 | "lat": 50.6136307, 742 | "lon": 26.242479, 743 | "timestamp": "2017-05-29T12:04:57Z", 744 | "version": 1, 745 | "changeset": 49071406, 746 | "user": "Sergioni145", 747 | "uid": 2436512, 748 | "type": "node" 749 | }, 750 | { 751 | "id": 1264073621, 752 | "lat": 50.6134187, 753 | "lon": 26.2421013, 754 | "timestamp": "2017-05-29T12:05:02Z", 755 | "version": 2, 756 | "changeset": 49071406, 757 | "user": "Sergioni145", 758 | "uid": 2436512, 759 | "type": "node" 760 | }, 761 | { 762 | "id": 1264073672, 763 | "lat": 50.6139668, 764 | "lon": 26.2444499, 765 | "timestamp": "2011-04-28T19:30:29Z", 766 | "version": 1, 767 | "changeset": 7997837, 768 | "user": "serges", 769 | "uid": 80691, 770 | "type": "node" 771 | }, 772 | { 773 | "id": 1269773697, 774 | "lat": 50.6141224, 775 | "lon": 26.2452392, 776 | "timestamp": "2011-05-01T21:01:53Z", 777 | "version": 1, 778 | "changeset": 8026116, 779 | "user": "serges", 780 | "uid": 80691, 781 | "type": "node" 782 | }, 783 | { 784 | "id": 4883482995, 785 | "lat": 50.6136939, 786 | "lon": 26.2422525, 787 | "timestamp": "2017-05-29T12:04:57Z", 788 | "version": 1, 789 | "changeset": 49071406, 790 | "user": "Sergioni145", 791 | "uid": 2436512, 792 | "type": "node" 793 | }, 794 | { 795 | "id": 1264073647, 796 | "lat": 50.6139542, 797 | "lon": 26.2445601, 798 | "timestamp": "2011-04-28T19:30:28Z", 799 | "version": 1, 800 | "changeset": 7997837, 801 | "user": "serges", 802 | "uid": 80691, 803 | "type": "node" 804 | }, 805 | { 806 | "id": 1264073653, 807 | "lat": 50.6134936, 808 | "lon": 26.2434099, 809 | "timestamp": "2011-04-28T19:30:28Z", 810 | "version": 1, 811 | "changeset": 7997837, 812 | "user": "serges", 813 | "uid": 80691, 814 | "type": "node" 815 | }, 816 | { 817 | "id": 4883483008, 818 | "lat": 50.6134091, 819 | "lon": 26.2435109, 820 | "timestamp": "2017-05-29T12:04:57Z", 821 | "version": 1, 822 | "changeset": 49071406, 823 | "user": "Sergioni145", 824 | "uid": 2436512, 825 | "type": "node" 826 | }, 827 | { 828 | "id": 1244184441, 829 | "lat": 50.6134621, 830 | "lon": 26.243832, 831 | "timestamp": "2011-04-14T09:23:47Z", 832 | "version": 1, 833 | "changeset": 7858958, 834 | "user": "serges", 835 | "uid": 80691, 836 | "type": "node" 837 | }, 838 | { 839 | "id": 1264073676, 840 | "lat": 50.6137121, 841 | "lon": 26.2429494, 842 | "timestamp": "2011-04-28T19:30:29Z", 843 | "version": 1, 844 | "changeset": 7997837, 845 | "user": "serges", 846 | "uid": 80691, 847 | "type": "node" 848 | }, 849 | { 850 | "id": 1264073687, 851 | "lat": 50.6140348, 852 | "lon": 26.244626, 853 | "timestamp": "2011-04-28T19:30:30Z", 854 | "version": 1, 855 | "changeset": 7997837, 856 | "user": "serges", 857 | "uid": 80691, 858 | "type": "node" 859 | }, 860 | { 861 | "id": 1264073631, 862 | "lat": 50.613758, 863 | "lon": 26.2440783, 864 | "timestamp": "2011-04-28T19:30:27Z", 865 | "version": 1, 866 | "changeset": 7997837, 867 | "user": "serges", 868 | "uid": 80691, 869 | "type": "node" 870 | }, 871 | { 872 | "id": 4883483013, 873 | "lat": 50.6133774, 874 | "lon": 26.2436134, 875 | "timestamp": "2017-05-29T12:04:57Z", 876 | "version": 1, 877 | "changeset": 49071406, 878 | "user": "Sergioni145", 879 | "uid": 2436512, 880 | "type": "node" 881 | }, 882 | { 883 | "id": 4883482999, 884 | "lat": 50.6134593, 885 | "lon": 26.2420533, 886 | "timestamp": "2017-05-29T12:04:57Z", 887 | "version": 1, 888 | "changeset": 49071406, 889 | "user": "Sergioni145", 890 | "uid": 2436512, 891 | "type": "node" 892 | }, 893 | { 894 | "id": 1264073638, 895 | "lat": 50.6137934, 896 | "lon": 26.243914, 897 | "timestamp": "2011-04-28T19:30:27Z", 898 | "version": 1, 899 | "changeset": 7997837, 900 | "user": "serges", 901 | "uid": 80691, 902 | "type": "node" 903 | }, 904 | { 905 | "id": 4883483010, 906 | "lat": 50.6134522, 907 | "lon": 26.2435165, 908 | "timestamp": "2017-05-29T12:04:57Z", 909 | "version": 1, 910 | "changeset": 49071406, 911 | "user": "Sergioni145", 912 | "uid": 2436512, 913 | "type": "node" 914 | }, 915 | { 916 | "id": 1264073634, 917 | "lat": 50.6139944, 918 | "lon": 26.2445215, 919 | "timestamp": "2011-04-28T19:30:27Z", 920 | "version": 1, 921 | "changeset": 7997837, 922 | "user": "serges", 923 | "uid": 80691, 924 | "type": "node" 925 | }, 926 | { 927 | "id": 4883483003, 928 | "lat": 50.6134212, 929 | "lon": 26.2424565, 930 | "timestamp": "2017-05-29T12:04:57Z", 931 | "version": 1, 932 | "changeset": 49071406, 933 | "user": "Sergioni145", 934 | "uid": 2436512, 935 | "type": "node" 936 | }, 937 | { 938 | "id": 110768775, 939 | "timestamp": "2017-07-14T06:14:28Z", 940 | "version": 3, 941 | "changeset": 50274315, 942 | "user": "igornnn", 943 | "uid": 6248770, 944 | "type": "way", 945 | "tags": { 946 | "addr:housenumber": "27", 947 | "addr:street": "\u0414\u0440\u0430\u0433\u043e\u043c\u0430\u043d\u043e\u0432\u0430 \u0432\u0443\u043b\u0438\u0446\u044f", 948 | "building": "yes", 949 | "building:levels": "3" 950 | }, 951 | "nodes": [ 952 | 1264073607, 953 | 1264073676, 954 | 1264073662, 955 | 1264073656, 956 | 1264073600, 957 | 1264073609, 958 | 1264073612, 959 | 1264073627, 960 | 1264073651, 961 | 1264073645, 962 | 1264073646, 963 | 1264073621, 964 | 1264073661, 965 | 1264073606, 966 | 1264073684, 967 | 1264073686, 968 | 1264073675, 969 | 1264073630, 970 | 1264073608, 971 | 1264073655, 972 | 1264073666, 973 | 1264073648, 974 | 1264073626, 975 | 1264073659, 976 | 1264073619, 977 | 1264073597, 978 | 1264073653, 979 | 1264073614, 980 | 1264073607 981 | ] 982 | }, 983 | { 984 | "id": 110768780, 985 | "timestamp": "2011-04-28T19:30:31Z", 986 | "version": 1, 987 | "changeset": 7997837, 988 | "user": "serges", 989 | "uid": 80691, 990 | "type": "way", 991 | "tags": { 992 | "building": "yes" 993 | }, 994 | "nodes": [ 995 | 1264073627, 996 | 1264073612, 997 | 1264073605, 998 | 1264073663, 999 | 1264073627 1000 | ] 1001 | }, 1002 | { 1003 | "id": 110768781, 1004 | "timestamp": "2017-03-21T16:01:43Z", 1005 | "version": 3, 1006 | "changeset": 47044280, 1007 | "user": "Aurimas Fi\u0161eras", 1008 | "uid": 651869, 1009 | "type": "way", 1010 | "nodes": [ 1011 | 1264073687, 1012 | 1264073634, 1013 | 1264073647, 1014 | 1264073643, 1015 | 1264073672, 1016 | 1264073670, 1017 | 1264073624, 1018 | 1264073596, 1019 | 1264073638, 1020 | 1264073680, 1021 | 1264073631, 1022 | 1264073611, 1023 | 1264073625, 1024 | 1264073677, 1025 | 1264073668, 1026 | 1264073664, 1027 | 1264073667, 1028 | 1264073681, 1029 | 1264073640, 1030 | 1264073599, 1031 | 1264073687 1032 | ] 1033 | }, 1034 | { 1035 | "id": 110768785, 1036 | "timestamp": "2011-04-28T19:30:32Z", 1037 | "version": 1, 1038 | "changeset": 7997837, 1039 | "user": "serges", 1040 | "uid": 80691, 1041 | "type": "way", 1042 | "tags": { 1043 | "building": "yes" 1044 | }, 1045 | "nodes": [ 1046 | 1264073636, 1047 | 1264073628, 1048 | 1264073678, 1049 | 1264073673, 1050 | 1264073636 1051 | ] 1052 | }, 1053 | { 1054 | "id": 111451113, 1055 | "timestamp": "2017-05-29T12:05:03Z", 1056 | "version": 3, 1057 | "changeset": 49071406, 1058 | "user": "Sergioni145", 1059 | "uid": 2436512, 1060 | "type": "way", 1061 | "tags": { 1062 | "amenity": "school", 1063 | "name": "\u0428\u043a\u043e\u043b\u0430 \u21162", 1064 | "official_name": "\u0420\u0456\u0432\u043d\u0435\u043d\u0441\u044c\u043a\u0438\u0439 \u041d\u0412\u041a \"\u0428\u043a\u043e\u043b\u0430-\u043b\u0456\u0446\u0435\u0439\" \u21162 \u0420\u041c\u0420" 1065 | }, 1066 | "nodes": [ 1067 | 1264073677, 1068 | 1269773694, 1069 | 1261273933, 1070 | 1155159949, 1071 | 4883483007, 1072 | 1244184441, 1073 | 3311728275, 1074 | 1269773697, 1075 | 1264073640, 1076 | 1264073681, 1077 | 1264073667, 1078 | 1264073664, 1079 | 1264073668, 1080 | 1264073677 1081 | ] 1082 | }, 1083 | { 1084 | "id": 202526713, 1085 | "timestamp": "2017-05-29T12:05:03Z", 1086 | "version": 2, 1087 | "changeset": 49071406, 1088 | "user": "Sergioni145", 1089 | "uid": 2436512, 1090 | "type": "way", 1091 | "tags": { 1092 | "highway": "unclassified" 1093 | }, 1094 | "nodes": [ 1095 | 1244184441, 1096 | 4883483007, 1097 | 1155159949 1098 | ] 1099 | }, 1100 | { 1101 | "id": 496671186, 1102 | "timestamp": "2017-05-29T12:05:00Z", 1103 | "version": 1, 1104 | "changeset": 49071406, 1105 | "user": "Sergioni145", 1106 | "uid": 2436512, 1107 | "type": "way", 1108 | "tags": { 1109 | "building:levels": "4", 1110 | "building:part": "yes" 1111 | }, 1112 | "nodes": [ 1113 | 4883483005, 1114 | 4883483006, 1115 | 1264073666, 1116 | 4883482994, 1117 | 4883482995, 1118 | 1264073630, 1119 | 4883482996, 1120 | 1264073686, 1121 | 4883482997, 1122 | 4883482998, 1123 | 1264073606, 1124 | 4883482999, 1125 | 1264073621, 1126 | 4883483000, 1127 | 4883483001, 1128 | 1264073645, 1129 | 1264073651, 1130 | 4883483002, 1131 | 4883483003, 1132 | 1264073609, 1133 | 4883483004, 1134 | 1264073662, 1135 | 4883482993, 1136 | 4883483005 1137 | ] 1138 | }, 1139 | { 1140 | "id": 496671187, 1141 | "timestamp": "2017-05-29T12:05:00Z", 1142 | "version": 1, 1143 | "changeset": 49071406, 1144 | "user": "Sergioni145", 1145 | "uid": 2436512, 1146 | "type": "way", 1147 | "tags": { 1148 | "highway": "service" 1149 | }, 1150 | "nodes": [ 1151 | 4883483007, 1152 | 4883483008 1153 | ] 1154 | }, 1155 | { 1156 | "id": 496671188, 1157 | "timestamp": "2017-05-29T12:05:00Z", 1158 | "version": 1, 1159 | "changeset": 49071406, 1160 | "user": "Sergioni145", 1161 | "uid": 2436512, 1162 | "type": "way", 1163 | "tags": { 1164 | "landuse": "grass" 1165 | }, 1166 | "nodes": [ 1167 | 4883483009, 1168 | 4883483010, 1169 | 4883483011, 1170 | 4883483012, 1171 | 4883483009 1172 | ] 1173 | }, 1174 | { 1175 | "id": 496671189, 1176 | "timestamp": "2017-05-29T12:05:00Z", 1177 | "version": 1, 1178 | "changeset": 49071406, 1179 | "user": "Sergioni145", 1180 | "uid": 2436512, 1181 | "type": "way", 1182 | "tags": { 1183 | "landuse": "grass" 1184 | }, 1185 | "nodes": [ 1186 | 4883483013, 1187 | 4883483014, 1188 | 4883483015, 1189 | 4883483016, 1190 | 4883483013 1191 | ] 1192 | }, 1193 | { 1194 | "id": 1562147, 1195 | "timestamp": "2017-03-21T16:01:43Z", 1196 | "version": 2, 1197 | "changeset": 47044280, 1198 | "user": "Aurimas Fi\u0161eras", 1199 | "uid": 651869, 1200 | "type": "relation", 1201 | "members": [ 1202 | { 1203 | "ref": 110768786, 1204 | "role": "inner", 1205 | "type": "way" 1206 | }, 1207 | { 1208 | "ref": 110768781, 1209 | "role": "outer", 1210 | "type": "way" 1211 | } 1212 | ], 1213 | "tags": { 1214 | "building": "yes", 1215 | "type": "multipolygon" 1216 | } 1217 | }, 1218 | { 1219 | "id": 1802915, 1220 | "timestamp": "2018-02-04T12:21:30Z", 1221 | "version": 9, 1222 | "changeset": 56053504, 1223 | "user": "\u0421\u0435\u0440\u0433\u0456\u0439 \u0414\u0443\u0431\u0438\u043a", 1224 | "uid": 423716, 1225 | "type": "relation", 1226 | "members": [ 1227 | { 1228 | "ref": 115743110, 1229 | "role": "house", 1230 | "type": "way" 1231 | }, 1232 | { 1233 | "ref": 115956152, 1234 | "role": "house", 1235 | "type": "way" 1236 | }, 1237 | { 1238 | "ref": 115970309, 1239 | "role": "house", 1240 | "type": "way" 1241 | }, 1242 | { 1243 | "ref": 115970320, 1244 | "role": "house", 1245 | "type": "way" 1246 | }, 1247 | { 1248 | "ref": 115956118, 1249 | "role": "house", 1250 | "type": "way" 1251 | }, 1252 | { 1253 | "ref": 115970300, 1254 | "role": "house", 1255 | "type": "way" 1256 | }, 1257 | { 1258 | "ref": 115956136, 1259 | "role": "house", 1260 | "type": "way" 1261 | }, 1262 | { 1263 | "ref": 115956150, 1264 | "role": "house", 1265 | "type": "way" 1266 | }, 1267 | { 1268 | "ref": 115970297, 1269 | "role": "house", 1270 | "type": "way" 1271 | }, 1272 | { 1273 | "ref": 45544258, 1274 | "role": "house", 1275 | "type": "way" 1276 | }, 1277 | { 1278 | "ref": 45544261, 1279 | "role": "house", 1280 | "type": "way" 1281 | }, 1282 | { 1283 | "ref": 115970284, 1284 | "role": "house", 1285 | "type": "way" 1286 | }, 1287 | { 1288 | "ref": 202526676, 1289 | "role": "house", 1290 | "type": "way" 1291 | }, 1292 | { 1293 | "ref": 202526677, 1294 | "role": "house", 1295 | "type": "way" 1296 | }, 1297 | { 1298 | "ref": 115970251, 1299 | "role": "house", 1300 | "type": "way" 1301 | }, 1302 | { 1303 | "ref": 202526679, 1304 | "role": "house", 1305 | "type": "way" 1306 | }, 1307 | { 1308 | "ref": 115970267, 1309 | "role": "house", 1310 | "type": "way" 1311 | }, 1312 | { 1313 | "ref": 115970299, 1314 | "role": "house", 1315 | "type": "way" 1316 | }, 1317 | { 1318 | "ref": 110768775, 1319 | "role": "house", 1320 | "type": "way" 1321 | }, 1322 | { 1323 | "ref": 45544257, 1324 | "role": "house", 1325 | "type": "way" 1326 | }, 1327 | { 1328 | "ref": 99870273, 1329 | "role": "street", 1330 | "type": "way" 1331 | }, 1332 | { 1333 | "ref": 110281352, 1334 | "role": "street", 1335 | "type": "way" 1336 | } 1337 | ], 1338 | "tags": { 1339 | "name": "\u0414\u0440\u0430\u0433\u043e\u043c\u0430\u043d\u043e\u0432\u0430 \u0432\u0443\u043b\u0438\u0446\u044f", 1340 | "type": "associatedStreet" 1341 | } 1342 | } 1343 | ], 1344 | "generator": "CGImap 0.7.0 (14106 thorn-01.openstreetmap.org)" 1345 | } 1346 | -------------------------------------------------------------------------------- /tests/data/map.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /tests/data/meta.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "node", 8 | "id": 4883483009, 9 | "timestamp": "2017-05-29T12:04:57Z", 10 | "user": "Sergioni145", 11 | "uid": 2436512, 12 | "version": 1 13 | }, 14 | "geometry": { 15 | "type": "Point", 16 | "coordinates": [26.2433173, 50.6136731] 17 | } 18 | }, 19 | { 20 | "type": "Feature", 21 | "properties": { 22 | "type": "node", 23 | "id": 6657522239, 24 | "tags": { 25 | "historic": "memorial", 26 | "inscription": "\u0417\u043d\u0430\u043a \u0440\u043e\u0434\u0443 \u041b\u0438\u043c\u0438\u0447", 27 | "memorial": "obelisk", 28 | "name": "\u0417\u043d\u0430\u043a \u0440\u043e\u0434\u0443 \u041b\u0438\u043c\u0438\u0447" 29 | }, 30 | "timestamp": "2019-08-01T07:00:26Z", 31 | "user": "K2_UNDERGROUND", 32 | "uid": 561874, 33 | "version": 2 34 | }, 35 | "geometry": { 36 | "type": "Point", 37 | "coordinates": [26.2449014, 50.6148747] 38 | } 39 | }, 40 | { 41 | "type": "Feature", 42 | "properties": { 43 | "type": "way", 44 | "id": 110768779, 45 | "tags": { 46 | "addr:housenumber": "2", 47 | "building": "yes" 48 | }, 49 | "nodes": [ 50 | 1264073649, 51 | 1264073685, 52 | 1264073637, 53 | 1264073623, 54 | 1264073654, 55 | 1264073664, 56 | 7803367415, 57 | 1264073668, 58 | 1264073598, 59 | 1264073674, 60 | 1264073603, 61 | 1264073649 62 | ], 63 | "timestamp": "2020-08-11T09:10:37Z", 64 | "user": "ShawIsland", 65 | "uid": 11387832, 66 | "version": 4 67 | }, 68 | "geometry": { 69 | "type": "Polygon", 70 | "coordinates": [ 71 | [ 72 | [26.2455724, 50.6144299], 73 | [26.2454372, 50.6143776], 74 | [26.2453093, 50.6145108], 75 | [26.2449465, 50.6143705], 76 | [26.2449104, 50.6144082], 77 | [26.2448618, 50.6143894], 78 | [26.2448498, 50.6144015], 79 | [26.2448339, 50.6144184], 80 | [26.2448825, 50.6144372], 81 | [26.2448291, 50.6144928], 82 | [26.2453272, 50.6146854], 83 | [26.2455724, 50.6144299] 84 | ] 85 | ] 86 | } 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /tests/data/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0.6, 3 | "generator": "Overpass API 0.7.57.1 74a55df1", 4 | "osm3s": { 5 | "timestamp_osm_base": "2021-11-22T23:48:40Z", 6 | "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." 7 | }, 8 | "elements": [ 9 | { 10 | "type": "node", 11 | "id": 4883483009, 12 | "lat": 50.6136731, 13 | "lon": 26.2433173, 14 | "timestamp": "2017-05-29T12:04:57Z", 15 | "version": 1, 16 | "changeset": 49071406, 17 | "user": "Sergioni145", 18 | "uid": 2436512 19 | }, 20 | { 21 | "type": "node", 22 | "id": 6657522239, 23 | "lat": 50.6148747, 24 | "lon": 26.2449014, 25 | "timestamp": "2019-08-01T07:00:26Z", 26 | "version": 2, 27 | "changeset": 72883450, 28 | "user": "K2_UNDERGROUND", 29 | "uid": 561874, 30 | "tags": { 31 | "historic": "memorial", 32 | "inscription": "Знак роду Лимич", 33 | "memorial": "obelisk", 34 | "name": "Знак роду Лимич" 35 | } 36 | }, 37 | { 38 | "type": "way", 39 | "id": 110768779, 40 | "timestamp": "2020-08-11T09:10:37Z", 41 | "version": 4, 42 | "changeset": 89240515, 43 | "user": "ShawIsland", 44 | "uid": 11387832, 45 | "nodes": [ 46 | 1264073649, 1264073685, 1264073637, 1264073623, 1264073654, 1264073664, 47 | 7803367415, 1264073668, 1264073598, 1264073674, 1264073603, 1264073649 48 | ], 49 | "tags": { 50 | "addr:housenumber": "2", 51 | "building": "yes" 52 | } 53 | }, 54 | { 55 | "type": "node", 56 | "id": 1264073598, 57 | "lat": 50.6144372, 58 | "lon": 26.2448825 59 | }, 60 | { 61 | "type": "node", 62 | "id": 1264073603, 63 | "lat": 50.6146854, 64 | "lon": 26.2453272 65 | }, 66 | { 67 | "type": "node", 68 | "id": 1264073623, 69 | "lat": 50.6143705, 70 | "lon": 26.2449465 71 | }, 72 | { 73 | "type": "node", 74 | "id": 1264073637, 75 | "lat": 50.6145108, 76 | "lon": 26.2453093 77 | }, 78 | { 79 | "type": "node", 80 | "id": 1264073649, 81 | "lat": 50.6144299, 82 | "lon": 26.2455724 83 | }, 84 | { 85 | "type": "node", 86 | "id": 1264073654, 87 | "lat": 50.6144082, 88 | "lon": 26.2449104 89 | }, 90 | { 91 | "type": "node", 92 | "id": 1264073664, 93 | "lat": 50.6143894, 94 | "lon": 26.2448618 95 | }, 96 | { 97 | "type": "node", 98 | "id": 1264073668, 99 | "lat": 50.6144184, 100 | "lon": 26.2448339 101 | }, 102 | { 103 | "type": "node", 104 | "id": 1264073674, 105 | "lat": 50.6144928, 106 | "lon": 26.2448291 107 | }, 108 | { 109 | "type": "node", 110 | "id": 1264073685, 111 | "lat": 50.6143776, 112 | "lon": 26.2454372 113 | }, 114 | { 115 | "type": "node", 116 | "id": 7803367415, 117 | "lat": 50.6144015, 118 | "lon": 26.2448498 119 | } 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /tests/data/node.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "node", 8 | "id": 1 9 | }, 10 | "geometry": { 11 | "type": "Point", 12 | "coordinates": [ 13 | 4.321, 14 | 1.234 15 | ] 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [ 3 | { 4 | "type": "node", 5 | "id": 1, 6 | "lat": 1.234, 7 | "lon": 4.321 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/node.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/data/relation.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "relation", 8 | "id": 1, 9 | "tags": { 10 | "type": "multipolygon" 11 | } 12 | }, 13 | "geometry": { 14 | "type": "MultiPolygon", 15 | "coordinates": [ 16 | [ 17 | [ 18 | [ 19 | -1.0, 20 | 1.0 21 | ], 22 | [ 23 | -1.0, 24 | -1.0 25 | ], 26 | [ 27 | 1.0, 28 | -1.0 29 | ], 30 | [ 31 | 1.0, 32 | 1.0 33 | ], 34 | [ 35 | -1.0, 36 | 1.0 37 | ] 38 | ], 39 | [ 40 | [ 41 | 0.5, 42 | 0.0 43 | ], 44 | [ 45 | 0.0, 46 | -0.5 47 | ], 48 | [ 49 | 0.0, 50 | 0.5 51 | ], 52 | [ 53 | 0.5, 54 | 0.0 55 | ] 56 | ] 57 | ] 58 | ] 59 | } 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /tests/data/relation.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [ 3 | { 4 | "type": "relation", 5 | "id": 1, 6 | "tags": { 7 | "type": "multipolygon" 8 | }, 9 | "members": [ 10 | { 11 | "type": "way", 12 | "ref": 2, 13 | "role": "outer" 14 | }, 15 | { 16 | "type": "way", 17 | "ref": 3, 18 | "role": "inner" 19 | } 20 | ] 21 | }, 22 | { 23 | "type": "way", 24 | "id": 2, 25 | "nodes": [ 26 | 4, 27 | 5, 28 | 6, 29 | 7, 30 | 4 31 | ], 32 | "tags": { 33 | "area": "yes" 34 | } 35 | }, 36 | { 37 | "type": "way", 38 | "id": 3, 39 | "nodes": [ 40 | 8, 41 | 9, 42 | 10, 43 | 8 44 | ] 45 | }, 46 | { 47 | "type": "node", 48 | "id": 4, 49 | "lat": -1, 50 | "lon": -1 51 | }, 52 | { 53 | "type": "node", 54 | "id": 5, 55 | "lat": -1, 56 | "lon": 1 57 | }, 58 | { 59 | "type": "node", 60 | "id": 6, 61 | "lat": 1, 62 | "lon": 1 63 | }, 64 | { 65 | "type": "node", 66 | "id": 7, 67 | "lat": 1, 68 | "lon": -1 69 | }, 70 | { 71 | "type": "node", 72 | "id": 8, 73 | "lat": -0.5, 74 | "lon": 0 75 | }, 76 | { 77 | "type": "node", 78 | "id": 9, 79 | "lat": 0.5, 80 | "lon": 0 81 | }, 82 | { 83 | "type": "node", 84 | "id": 10, 85 | "lat": 0, 86 | "lon": 0.5 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /tests/data/relation.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/data/way.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "way", 8 | "id": 1, 9 | "nodes": [ 10 | 2, 11 | 3, 12 | 4 13 | ] 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 1.0, 20 | 0.0 21 | ], 22 | [ 23 | 1.1, 24 | 0.0 25 | ], 26 | [ 27 | 1.2, 28 | 0.1 29 | ] 30 | ] 31 | } 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /tests/data/way.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [ 3 | { 4 | "type": "way", 5 | "id": 1, 6 | "nodes": [ 7 | 2, 8 | 3, 9 | 4 10 | ] 11 | }, 12 | { 13 | "type": "node", 14 | "id": 2, 15 | "lat": 0, 16 | "lon": 1 17 | }, 18 | { 19 | "type": "node", 20 | "id": 3, 21 | "lat": 0, 22 | "lon": 1.1 23 | }, 24 | { 25 | "type": "node", 26 | "id": 4, 27 | "lat": 0.1, 28 | "lon": 1.2 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tests/data/way.osm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | 4 | from osm2geojson import overpass_call, read_data_file, xml2geojson, json2geojson 5 | 6 | def get_osm_and_geojson_data(name): 7 | xml_data = read_data_file(name + '.osm') 8 | geojson_data = read_data_file(name + '.geojson') 9 | data = xml2geojson(xml_data) 10 | saved_geojson = json.loads(geojson_data) 11 | return (data, saved_geojson) 12 | 13 | def get_json_and_geojson_data(name): 14 | json_data = read_data_file(name + '.json') 15 | geojson_data = read_data_file(name + '.geojson') 16 | data = json.loads(json_data) 17 | saved_geojson = json.loads(geojson_data) 18 | return (data, saved_geojson) 19 | 20 | class TestOsm2GeoJsonMethods(unittest.TestCase): 21 | def test_files_convertation(self): 22 | """ 23 | Test how xml2geojson converts saved files 24 | """ 25 | for name in ['empty', 'node', 'way', 'relation', 'map']: 26 | (data, saved_geojson) = get_osm_and_geojson_data(name) 27 | self.assertDictEqual(saved_geojson, data) 28 | 29 | @unittest.skip('Overpass API returns 504 error') 30 | def test_parsing_from_overpass(self): 31 | """ 32 | Test city border convertation to MultiPolygon 33 | """ 34 | xml = overpass_call('rel(448930); out geom;') 35 | data = xml2geojson(xml) 36 | self.assertEqual(len(data['features']), 1) 37 | 38 | def test_issue_4(self): 39 | (data, saved_geojson) = get_osm_and_geojson_data('issue-4') 40 | self.assertDictEqual(saved_geojson, data) 41 | 42 | def test_issue_6(self): 43 | (data, saved_geojson) = get_json_and_geojson_data('issue-6') 44 | self.assertDictEqual(saved_geojson, json2geojson(data)) 45 | 46 | def test_issue_7(self): 47 | (data, saved_geojson) = get_json_and_geojson_data('issue-7') 48 | self.assertDictEqual(saved_geojson, json2geojson(data)) 49 | 50 | def test_barrier_wall(self): 51 | # https://wiki.openstreetmap.org/wiki/Tag:barrier%3Dwall 52 | (data, saved_geojson) = get_osm_and_geojson_data('barrier-wall') 53 | self.assertEqual(data['features'][0]['geometry']['type'], 'LineString') 54 | self.assertDictEqual(saved_geojson, data) 55 | 56 | def test_issue_9(self): 57 | (data, saved_geojson) = get_json_and_geojson_data('issue-9') 58 | all_geojson = json.loads(read_data_file('issue-9-all.geojson')) 59 | self.assertDictEqual(saved_geojson, json2geojson(data)) 60 | self.assertDictEqual(all_geojson, json2geojson(data, filter_used_refs=False)) 61 | 62 | def test_center_feature(self): 63 | (data, saved_geojson) = get_json_and_geojson_data('center-feature') 64 | self.assertDictEqual(saved_geojson, json2geojson(data)) 65 | 66 | def test_issue_16(self): 67 | (data, saved_geojson) = get_json_and_geojson_data('issue-16') 68 | self.assertDictEqual(saved_geojson, json2geojson(data)) 69 | 70 | def test_meta_tags(self): 71 | (data, saved_geojson) = get_json_and_geojson_data('meta') 72 | self.assertDictEqual(saved_geojson, json2geojson(data)) 73 | 74 | def test_issue_35(self): 75 | (data, saved_geojson) = get_json_and_geojson_data('issue-35') 76 | self.assertDictEqual(saved_geojson, json2geojson(data)) 77 | 78 | def test_raise_on_failure(self): 79 | xml_data = read_data_file('map.osm') 80 | saved_geojson = json.loads(read_data_file('map.geojson')) 81 | 82 | with self.assertRaises(Exception): 83 | xml2geojson(xml_data, raise_on_failure=True) 84 | 85 | self.assertDictEqual(saved_geojson, xml2geojson(xml_data)) 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /tests/parse_xml.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | 4 | from osm2geojson import parse_xml, overpass_call, read_data_file 5 | 6 | class TestParseXmlMethods(unittest.TestCase): 7 | def test_empty_tag(self): 8 | """ 9 | Test parsing of empty tag 10 | """ 11 | xml = read_data_file('empty.osm') 12 | data = parse_xml(xml) 13 | 14 | self.assertIsInstance(data, dict) 15 | # Use version 0.6 as default 16 | self.assertEqual(data['version'], 0.6) 17 | self.assertEqual(data['elements'], []) 18 | 19 | def test_node(self): 20 | """ 21 | Test parsing node 22 | """ 23 | xml = read_data_file('node.osm') 24 | data = parse_xml(xml) 25 | 26 | self.assertIsInstance(data, dict) 27 | self.assertIsInstance(data['elements'], list) 28 | self.assertEqual(len(data['elements']), 1) 29 | 30 | node = data['elements'][0] 31 | self.assertIsInstance(node, dict) 32 | self.assertEqual(node['type'], 'node') 33 | self.assertEqual(node['id'], 1) 34 | self.assertAlmostEqual(node['lat'], 1.234) 35 | self.assertAlmostEqual(node['lon'], 4.321) 36 | 37 | def test_way(self): 38 | """ 39 | Test parsing way 40 | """ 41 | xml = read_data_file('way.osm') 42 | data = parse_xml(xml) 43 | 44 | self.assertIsInstance(data, dict) 45 | self.assertIsInstance(data['elements'], list) 46 | self.assertEqual(len(data['elements']), 4) 47 | 48 | way = data['elements'][0] 49 | 50 | self.assertIsInstance(way, dict) 51 | self.assertEqual(way['type'], 'way') 52 | self.assertEqual(way['id'], 1) 53 | self.assertIsInstance(way['nodes'], list) 54 | self.assertEqual(way['nodes'], [2, 3, 4]) 55 | 56 | def test_relation(self): 57 | """ 58 | Test parsing relation 59 | """ 60 | xml = read_data_file('relation.osm') 61 | data = parse_xml(xml) 62 | 63 | self.assertIsInstance(data, dict) 64 | self.assertIsInstance(data['elements'], list) 65 | self.assertEqual(len(data['elements']), 10) 66 | 67 | relation = data['elements'][0] 68 | 69 | self.assertIsInstance(relation, dict) 70 | self.assertEqual(relation['type'], 'relation') 71 | self.assertEqual(relation['id'], 1) 72 | self.assertIsInstance(relation['members'], list) 73 | self.assertEqual(len(relation['members']), 2) 74 | self.assertEqual(relation['tags'], { 'type': 'multipolygon' }) 75 | 76 | def test_map(self): 77 | """ 78 | Test parsing of usual map 79 | """ 80 | xml = read_data_file('map.osm') 81 | data = parse_xml(xml) 82 | 83 | self.assertIsInstance(data, dict) 84 | self.assertEqual(data['version'], 0.6) 85 | 86 | def test_all_files(self): 87 | """ 88 | Test parsing of all files and compare with json version 89 | """ 90 | for name in ['empty', 'node', 'way', 'map', 'relation']: 91 | xml_data = read_data_file(name + '.osm') 92 | json_data = read_data_file(name + '.json') 93 | parsed_json = parse_xml(xml_data) 94 | saved_json = json.loads(json_data) 95 | 96 | if 'version' not in saved_json: 97 | del parsed_json['version'] 98 | 99 | self.assertDictEqual(saved_json, parsed_json) 100 | 101 | @unittest.skip('This test takes a lot of time. Also overpass API returns 504 error') 102 | def test_overpass_queries(self): 103 | """ 104 | Test several queries to overpass 105 | """ 106 | queries = [ 107 | 'rel(448930); out geom;', 108 | 'rel(448930); out meta;', 109 | 'rel(448930); out bb;', 110 | 'rel(448930); out count;' 111 | ] 112 | 113 | for query in queries: 114 | print('Test query:', query) 115 | query_json = '[out:json];' + query 116 | data_xml = overpass_call(query) 117 | data_json = overpass_call(query_json) 118 | overpass_json = json.loads(data_json) 119 | parsed_json = parse_xml(data_xml) 120 | # Ignore different time for queries 121 | del overpass_json['osm3s']['timestamp_osm_base'] 122 | del parsed_json['osm3s']['timestamp_osm_base'] 123 | # Ignore generator (overpass use different versions of generators?) 124 | del overpass_json['generator'] 125 | del parsed_json['generator'] 126 | self.assertDictEqual(overpass_json, parsed_json) 127 | 128 | if __name__ == '__main__': 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /update-osm-poygon-features.sh: -------------------------------------------------------------------------------- 1 | git submodule init 2 | git submodule update 3 | cp osm-polygon-features/polygon-features.json osm2geojson 4 | cp id-area-keys/areaKeys.json osm2geojson 5 | 6 | # TODO: Drop these patch lines when osm-polygon-features sub-module is updated 7 | # Remove the "wall" value from the "barrier" key using jq (https://github.com/aspectumapp/osm2geojson/issues/7#issuecomment-1967830306) 8 | jq --indent 4 '(.[] | select(.key == "barrier" and .polygon == "whitelist") | .values) -= ["wall"]' osm2geojson/polygon-features.json > tmp1.json 9 | # Add `highway=steps` to the blacklist (#7) 10 | jq --indent 4 '(.[] | select(.key == "highway" and .polygon == "blacklist") | .values) += ["steps"] | if map(.key == "highway" and .polygon == "blacklist") | any | not then . += [{"key": "highway", "polygon": "blacklist", "values": ["steps"]}] else . end' tmp1.json > tmp2.json 11 | 12 | mv tmp2.json osm2geojson/polygon-features.json 13 | rm tmp1.json 14 | --------------------------------------------------------------------------------