├── tests ├── __init__.py ├── test_core.py └── test_osm_road_length.py ├── osmpy ├── __init__.py ├── queries.py └── core.py ├── requirements.txt ├── requirements_dev.txt ├── .vscode └── settings.json ├── Makefile ├── .github └── ISSUE_TEMPLATE.md ├── LICENSE.md ├── pyproject.toml ├── setup.py ├── .gitignore └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /osmpy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .core import get, list_queries 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | shapely==1.7.0 2 | geojson==2.5.0 3 | requests==2.23.0 4 | retry 5 | pyproj 6 | pandas -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | shapely==1.7.0 2 | geojson==2.5.0 3 | requests==2.23.0 4 | ipykernel 5 | retry 6 | pyproj 7 | pandas 8 | pytest -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "restructuredtext.linter.disabled": true, 3 | "restructuredtext.confPath": "", 4 | "python.pythonPath": ".osm_overpass_py/bin/python3" 5 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPO=$(shell basename $(CURDIR)) 2 | 3 | create-env: 4 | python3 -m venv .$(REPO); 5 | source .$(REPO)/bin/activate; \ 6 | pip3 install --upgrade -r requirements_dev.txt; \ 7 | python -m ipykernel install --user --name=$(REPO); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * S2-py version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, João Carabetta 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | authors = ["Joao Carabetta "] 3 | classifiers = [ 4 | "Topic :: Software Development :: Build Tools", 5 | "Topic :: Software Development :: Libraries :: Python Modules", 6 | ] 7 | description = "Powerfull wrapper around OSM Overpass Turbo to query regions of any size and shape" 8 | homepage = "https://github.com/JoaoCarabetta/osmpy" 9 | license = "AM-331-A3 Licencia de Software" 10 | name = "osmpy" 11 | readme = "README.md" 12 | repository = "https://github.com/JoaoCarabetta/osmpy" 13 | version = "0.1.2" 14 | 15 | [tool.poetry.dependencies] 16 | python = "^3.6" 17 | shapely = "1.7.0" 18 | geojson = "2.5.0" 19 | requests = "2.23.0" 20 | retry = "*" 21 | pyproj = "*" 22 | pandas = "^1.0.0" 23 | 24 | [tool.poetry.dev-dependencies] 25 | python = "^3.6" 26 | shapely = "1.7.0" 27 | geojson = "2.5.0" 28 | requests = "2.23.0" 29 | retry = "*" 30 | pyproj = "*" 31 | pandas = "^1.0.0" 32 | black = "*" 33 | poetry = "*" 34 | 35 | [tool.black] 36 | # Use the more relaxed max line length permitted in PEP8. 37 | exclude = ''' 38 | /( 39 | \.eggs 40 | | \.git 41 | | \.mypy_cache 42 | | \.tox 43 | | \venv 44 | | build 45 | | dist 46 | | htmlcov 47 | )/ 48 | ''' 49 | line-length = 88 50 | target-version = ["py36", "py37", "py38"] 51 | [build-system] 52 | build-backend = "poetry.masonry.api" 53 | requires = ["poetry>=0.12"] 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open("README.rst") as readme_file: 9 | readme = readme_file.read() 10 | 11 | with open("HISTORY.rst") as history_file: 12 | history = history_file.read() 13 | 14 | 15 | requirements = [ 16 | "shapely==1.7.0", 17 | "geojson==2.5.0", 18 | "requests==2.23.0", 19 | "retry", 20 | "pyproj", 21 | "pandas", 22 | ] 23 | 24 | setup( 25 | author="Joao Carabetta", 26 | author_email="joao.carabetta@gmail.com", 27 | python_requires=">=3", 28 | classifiers=[ 29 | "Development Status :: 2 - Pre-Alpha", 30 | "Intended Audience :: Developers", 31 | "License :: OSI Approved :: MIT License", 32 | "Natural Language :: English", 33 | "Programming Language :: Python :: 3", 34 | ], 35 | description="Calculate Open Street Maps road length for any polygon", 36 | entry_points={}, 37 | install_requires=requirements, 38 | license="MIT license", 39 | long_description=readme + "\n\n" + history, 40 | include_package_data=True, 41 | name="osm-road-length", 42 | packages=find_packages(include=["osmpy", "osmpy.*"]), 43 | test_suite="tests", 44 | url="https://github.com/JoaoCarabetta/osm-road-length", 45 | version="0.1.4", 46 | zip_safe=False, 47 | ) 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | 51 | # PyInstaller 52 | # Usually these files are written by a python script from a template 53 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 54 | *.manifest 55 | *.spec 56 | 57 | # Installer logs 58 | pip-log.txt 59 | pip-delete-this-directory.txt 60 | 61 | # Unit test / coverage reports 62 | htmlcov/ 63 | .tox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *,cover 70 | .hypothesis/ 71 | .pytest_cache/ 72 | 73 | # Translations 74 | *.mo 75 | *.pot 76 | 77 | # Django stuff: 78 | *.log 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # IntelliJ Idea family of suites 84 | .idea 85 | *.iml 86 | ## File-based project format: 87 | *.ipr 88 | *.iws 89 | ## mpeltonen/sbt-idea plugin 90 | .idea_modules/ 91 | 92 | # PyBuilder 93 | target/ 94 | 95 | # Cookiecutter 96 | output/ 97 | python_boilerplate/ 98 | 99 | .env/ 100 | 101 | notebooks/ 102 | .osm-road-length/ 103 | .osmpy/ 104 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from shapely.geometry import Polygon 3 | from osmpy.core import simplify, check_length, get_area, threshold_func, katana, to_geojson, swipe, flatten, to_overpass_coords, _get_queries_names 4 | 5 | def test_simplify(): 6 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) # roughly 111 x 111 km square 7 | simplified = simplify(polygon) 8 | assert isinstance(simplified, Polygon) 9 | 10 | def test_check_length(): 11 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 12 | assert check_length(polygon) is True 13 | 14 | def test_get_area(): 15 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 16 | area = get_area(polygon) 17 | assert 11000 <= area <= 16298 # considering a roughly 111 x 111 km square 18 | 19 | def test_threshold_func(): 20 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 21 | assert threshold_func(polygon, 16298) is True # considering a roughly 111 x 111 km square 22 | 23 | def test_katana(): 24 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 25 | result = katana(polygon, threshold_func, 15000) 26 | assert isinstance(result, list) 27 | assert all(isinstance(r, Polygon) for r in result) 28 | 29 | def test_to_geojson(): 30 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 31 | result = to_geojson(polygon) 32 | assert isinstance(result, list) 33 | assert len(result) == 5 # 5 points in a Square, last one equals to the first 34 | 35 | def test_swipe(): 36 | coords = [(-70, 40), (-70, 41), (-69, 41), (-70, 40)] 37 | result = swipe(coords) 38 | assert result == [[40, -70], [41, -70], [41, -69], [40, -70]] 39 | 40 | def test_flatten(): 41 | coords = [[-70, 40], [-70, 41], [-69, 41], [-69, 40]] 42 | result = flatten(coords) 43 | assert result == ['-70', '40', '-70', '41', '-69', '41', '-69', '40'] 44 | 45 | def test_to_overpass_coords(): 46 | polygon = Polygon([(-70, 40), (-70, 41), (-69, 41), (-69, 40), (-70, 40)]) 47 | result = to_overpass_coords(polygon) 48 | assert isinstance(result, str) -------------------------------------------------------------------------------- /osmpy/queries.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | class QueryType: 4 | 5 | def postprocess(self, df): 6 | return df 7 | 8 | class POIs(QueryType): 9 | 10 | query = """ 11 | [out:json]; 12 | node["shop"](poly:"{boundary}"); 13 | node["amenity"](poly:"{boundary}"); 14 | out body geom; 15 | """ 16 | 17 | docstring = """ 18 | Location of Points of Interest within a boundary. 19 | Points of Interest being all shops and amenities. 20 | """ 21 | 22 | def prep_pois(self, x): 23 | 24 | if x.get('amenity'): 25 | return f'amenity:{x["amenity"]}' 26 | elif x.get('shop'): 27 | return f'shop:{x["shop"]}' 28 | else: 29 | return None 30 | 31 | def postprocess(self, df): 32 | """Post process API result 33 | """ 34 | df['poi'] = df['tags'].apply(self.prep_pois) 35 | 36 | return df 37 | 38 | class Amenities(QueryType): 39 | 40 | query = """ 41 | [out:json]; 42 | node["amenity"](poly:"{boundary}"); 43 | out body geom; 44 | """ 45 | 46 | docstring = """ 47 | Location of amenities within a boundary 48 | """ 49 | 50 | def postprocess(self, df): 51 | return df 52 | 53 | class AmentiesCount(QueryType): 54 | 55 | query = """ 56 | [out:json]; 57 | node["amenity"](poly:"{boundary}"); 58 | for (t["amenity"]) 59 | {{ 60 | make stat amenity=_.val, 61 | count=count(nodes); 62 | out; 63 | }} 64 | """ 65 | 66 | docstring = """ 67 | Number of amenities per type within a boundary 68 | """ 69 | 70 | def postprocess(self, df): 71 | return df['tags'].apply(pd.Series).groupby('amenity').sum() 72 | 73 | class RoadLength(QueryType): 74 | 75 | query = """ 76 | [out:json]; 77 | way["highway"](poly:"{boundary}"); 78 | for (t["highway"]) 79 | {{ 80 | make stat highway=_.val, 81 | count=count(ways),length=sum(length()); 82 | out; 83 | }}""" 84 | 85 | docstring = """ 86 | Length of road by roadtype within a boundary 87 | """ 88 | 89 | def postprocess(self, df): 90 | if 'tags' in df.columns: 91 | return df['tags'].apply(pd.Series).astype({ # It seems API may generate dataframe with strings 92 | 'highway': 'str', 93 | 'count': 'int', 94 | 'length': 'float' 95 | }).groupby('highway').sum() 96 | else: 97 | empty = pd.DataFrame(columns=['count', 'length']).astype({'count': 'int', 'length': 'float'}) 98 | empty.index.name = 'highway' 99 | return empty 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Powerfull wrapper around OSM Overpass Turbo to query regions of any size and shape** 2 | 3 | ```bash 4 | pip install osmpy 5 | ``` 6 | 7 | #### List precooked queries 8 | ```python 9 | osmpy.list_queries() 10 | 11 | name docstring 12 | 0 Amenities Location of amenities within a boundary 13 | 1 AmentiesCount Number of amenities per type within a boundary 14 | 2 POIs Location of Points of Interest within a bound... 15 | 3 RoadLength Length of road by roadtype within a boundary 16 | ``` 17 | 18 | #### Get all Points of Interest (POIs) within a boundary 19 | ```python 20 | import osmpy 21 | from shapely import wkt 22 | 23 | boundary = wkt.loads('POLYGON((-46.63 -23.54,-46.6 -23.54,-46.62 -23.55,-46.63 -23.55,-46.63 -23.54))') 24 | osmpy.get('POIs', boundary) 25 | 26 | type id lat lon tags poi 27 | 0 node 661212030 -23.544739 -46.626160 {'amenity': 'fuel', 'name': 'Posto NGM'} amenity:fuel 28 | 1 node 661212089 -23.547450 -46.626073 {'amenity': 'fuel', 'name': 'Posto Maserati', ... amenity:fuel 29 | 2 node 745733280 -23.541411 -46.613930 {'addr:city': 'São Paulo', 'addr:housenumber':... amenity:bank 30 | 3 node 745733292 -23.542070 -46.614916 {'addr:city': 'São Paulo', 'addr:housenumber':... amenity:bank 31 | 4 node 889763809 -23.542558 -46.620360 {'addr:housenumber': '110/C9', 'addr:street': ... amenity:social_centre 32 | .. ... ... ... ... ... ... 33 | 82 node 5663737625 -23.540027 -46.605425 {'access': 'yes', 'addr:city': 'São Paulo', 'a... amenity:parking 34 | 83 node 5990269247 -23.540650 -46.607532 {'addr:city': 'São Paulo', 'addr:housenumber':... amenity:fast_food 35 | 84 node 6621564995 -23.543880 -46.626414 {'access': 'yes', 'addr:city': 'São Paulo', 'a... amenity:parking 36 | 85 node 6625433725 -23.546727 -46.623956 {'access': 'yes', 'addr:city': 'São Paulo', 'a... amenity:parking 37 | 86 node 6625433753 -23.547111 -46.624790 {'access': 'yes', 'addr:city': 'São Paulo', 'a... amenity:bicycle_parking 38 | ``` 39 | 40 | #### Total road length in meters by road type 41 | ```python 42 | osmpy.get('RoadLength', boundary) 43 | 44 | count length 45 | highway 46 | bus_stop 1 82.624 47 | corridor 2 482.195 48 | cycleway 1 134.197 49 | footway 116 5473.419 50 | living_street 3 422.378 51 | path 4 735.539 52 | pedestrian 3 90.327 53 | platform 3 239.206 54 | primary 28 2067.562 55 | primary_link 12 1123.544 56 | ``` 57 | 58 | #### You can use your own query 59 | 60 | ```python 61 | 62 | ## Use `{boundary}` as a placeholder. 63 | query = """ 64 | [out:json]; 65 | node["amenity"](poly:"{boundary}"); 66 | out body geom; 67 | """ 68 | 69 | osmpy.get(query, boundary) 70 | ``` 71 | 72 | ## Create a precooked query 73 | 74 | ```python 75 | class YourPrecookedQuery(osmpy.queries.QueryType): 76 | 77 | query = """ 78 | 79 | """ 80 | 81 | docstring = """ 82 | 83 | """ 84 | 85 | def postprocess(self, df): 86 | """Post process API result 87 | """ 88 | return df['tags'].apply(pd.Series).groupby('amenity').sum() 89 | 90 | osmpy.get(YourPrecookedQuery, boundary) 91 | ``` 92 | 93 | :point_right: Leave an issue or PR if you want to add a new query to the package 94 | 95 | ## Credits 96 | 97 | Function `katana` from @snorfalorpagus_. 98 | -------------------------------------------------------------------------------- /osmpy/core.py: -------------------------------------------------------------------------------- 1 | import geojson 2 | import requests 3 | from shapely.geometry import box, Polygon, MultiPolygon, GeometryCollection, shape 4 | from shapely import wkt 5 | from shapely.ops import transform 6 | import shapely 7 | from functools import partial 8 | import pyproj 9 | import time 10 | import json 11 | from retry import retry 12 | import pandas as pd 13 | import warnings 14 | import re 15 | from .queries import * 16 | import sys 17 | import inspect 18 | 19 | warnings.filterwarnings("ignore", category=FutureWarning) 20 | 21 | def simplify(s, delta=0.05): 22 | 23 | while not check_length(s): 24 | s = s.simplify(delta, False) 25 | delta = delta + 0.05 26 | 27 | return s 28 | 29 | def check_length(s, threshold=3000): 30 | 31 | return len(str(s)) < threshold 32 | 33 | def get_area(s): 34 | """Get the area of a shapely polygon in sq km""" 35 | s = shape(s) 36 | proj = partial( 37 | pyproj.transform, pyproj.Proj(init="epsg:4326"), pyproj.Proj(init="epsg:3857") 38 | ) 39 | area = transform(proj, s).area / 1e6 # km 40 | 41 | return area 42 | 43 | 44 | def threshold_func(g, value): 45 | 46 | return get_area(g) < value 47 | 48 | 49 | def katana(geometry, threshold_func, threshold_value, count=0, urllen_threshold=7648): 50 | """Split a Polygon into two parts across it's shortest dimension 51 | 52 | KUDOS https://snorfalorpagus.net/blog/2016/03/13/splitting-large-polygons-for-faster-intersections/ 53 | """ 54 | bounds = geometry.bounds 55 | width = bounds[2] - bounds[0] 56 | height = bounds[3] - bounds[1] 57 | if (threshold_func(geometry, threshold_value) and (len(to_overpass_coords(geometry)) < urllen_threshold)) or (count == 250): 58 | # either the polygon is smaller than the threshold 59 | # AND the length of the expected url is short enough to avoid Error 414 60 | # OR the maximum number of recursions has been reached 61 | return [geometry] 62 | if height >= width: 63 | # split left to right 64 | a = box(bounds[0], bounds[1], bounds[2], bounds[1] + height / 2) 65 | b = box(bounds[0], bounds[1] + height / 2, bounds[2], bounds[3]) 66 | else: 67 | # split top to bottom 68 | a = box(bounds[0], bounds[1], bounds[0] + width / 2, bounds[3]) 69 | b = box(bounds[0] + width / 2, bounds[1], bounds[2], bounds[3]) 70 | result = [] 71 | for d in ( 72 | a, 73 | b, 74 | ): 75 | c = geometry.intersection(d) 76 | if not isinstance(c, GeometryCollection): 77 | c = [c] 78 | for e in c: 79 | if isinstance(e, (Polygon, MultiPolygon)): 80 | result.extend(katana(e, threshold_func, threshold_value, count + 1, urllen_threshold=urllen_threshold)) 81 | if count > 0: 82 | return result 83 | # convert multipart into singlepart 84 | final_result = [] 85 | for g in result: 86 | if isinstance(g, MultiPolygon): 87 | final_result.extend(g) 88 | else: 89 | final_result.append(g) 90 | return final_result 91 | 92 | 93 | def to_geojson(x): 94 | 95 | if isinstance(x, shapely.geometry.multipolygon.MultiPolygon): 96 | x = max(x, key=lambda a: a.area) 97 | 98 | g = geojson.Feature(geometry=x, properties={}).geometry 99 | 100 | return g["coordinates"][0] 101 | 102 | 103 | def swipe(x): 104 | return [[c[1], c[0]] for c in x] 105 | 106 | 107 | def flatten(l): 108 | return [str(round(item, 4)) for sublist in l for item in sublist] 109 | 110 | 111 | def to_overpass_coords(x): 112 | 113 | coords = to_geojson(x) 114 | coords = swipe(coords) 115 | coords = flatten(coords) 116 | coords = " ".join(coords) 117 | return coords 118 | 119 | 120 | @retry(tries=5) 121 | def overpass_request(query, boundary): 122 | 123 | overpass_url = "http://overpass-api.de/api/interpreter" 124 | 125 | overpass_query = query.query.format(boundary=boundary) 126 | 127 | response = requests.get(overpass_url, params={"data": overpass_query}).json() 128 | 129 | return pd.DataFrame(response['elements']) 130 | 131 | 132 | def get(query, boundary, threshold_value=1000000, urllen_threshold=7648): 133 | """Get Open Street Maps Turbo Query for a given boundary 134 | 135 | It splits the regions to manage overpass turbo limits. 136 | 137 | For MultiPolygons, only the biggest polygon will be considered. 138 | 139 | Parameters 140 | ---------- 141 | boundary : shapely.geometry 142 | A shapely polygon 143 | threshold_value : int, optional 144 | Maximum area in sq km to split the polygons, by default 1000000 145 | urllen_threshold : int, optional 146 | Maximum length of the url to send to Overpass API, by default 7648 (found experimentally) 147 | 148 | Returns 149 | ------- 150 | pd.DataFrame 151 | Table indexed by highway with length sum in meters and observation count 152 | """ 153 | 154 | if isinstance(query, str): 155 | if query in _get_queries_names(): 156 | query_obj = getattr(sys.modules['osmpy.queries'], query)() 157 | else: 158 | query_obj = QueryType() 159 | query_obj.query = query 160 | elif isinstance(query, type): 161 | query_obj = query() 162 | 163 | boundaries = katana(boundary, threshold_func, threshold_value, urllen_threshold=urllen_threshold) 164 | 165 | # Looking for the boundaries which generate too long URLs resulting in Error 414. 166 | # If found the boundaries will be processed again by `katana()` 167 | while True: 168 | for geo in boundaries: 169 | urllen = len(to_overpass_coords(geo)) 170 | if urllen >= urllen_threshold: 171 | boundaries.remove(geo) 172 | boundaries.extend(katana(geo, threshold_func, threshold_value, urllen_threshold=urllen_threshold)) 173 | break 174 | else: 175 | break 176 | 177 | responses = [] 178 | for bound in boundaries: 179 | 180 | bound = to_overpass_coords(bound) 181 | responses.append(overpass_request(query_obj, bound)) 182 | 183 | data = pd.concat([pd.DataFrame(d) for d in responses]) 184 | 185 | data = query_obj.postprocess(data) 186 | 187 | return data 188 | 189 | def _get_queries_names(): 190 | query_classes = [c[0] for c in inspect.getmembers(sys.modules[__name__], inspect.isclass) if QueryType in c[1].__bases__] 191 | return query_classes 192 | 193 | def list_queries(): 194 | 195 | return pd.DataFrame([ 196 | {'name': t, 197 | 'docstring': re.sub('\s+',' ', 198 | getattr(sys.modules['osmpy.queries'], t)().docstring)} 199 | for t in _get_queries_names()]) 200 | 201 | 202 | -------------------------------------------------------------------------------- /tests/test_osm_road_length.py: -------------------------------------------------------------------------------- 1 | import osmpy 2 | from shapely import wkt 3 | import pandas 4 | 5 | 6 | def test_get(): 7 | 8 | geometry = wkt.loads( 9 | "MULTIPOLYGON (((-116.958747158774 32.5487689268803,-114.725413971817 32.716253029889,-114.808747299689 32.4912530145175,-111.100414209405 31.3329196020495,-108.208747732263 31.3329196020495,-108.208394146121 31.7829196327925,-106.52544985856 31.7829196327925,-104.925414614124 30.6051089936204,-104.517081307554 29.6329194859095,-103.28593 28.97678,-102.867081415697 29.2162527907771,-102.675414761593 29.7412528266439,-102.318872777119 29.8745861690862,-101.400414845158 29.7662528283518,-100.675414892676 29.1011469060081,-100.283748251679 28.2579193919727,-99.5170816352613 27.5716568597988,-99.44583 27.0216,-99.0920816631165 26.3995859316823,-98.19492 26.05339,-97.6670817565132 26.0329192399658,-97.4337484384729 25.8412525602049,-97.1420817909225 25.957919234842,-97.6754150893003 24.516252469684,-97.8670817434049 22.4162523262169,-97.7004150876618 21.9579189615713,-97.3254151122399 21.5745856020495,-97.4170817728986 21.2245855781383,-97.1587484564968 20.6329188710504,-96.4504151695887 19.8662521520068,-96.2920818466328 19.3162521144321,-95.9170818712109 18.8745854175918,-94.8087486105194 18.5412520614859,-94.5254152957562 18.1579187019641,-92.9087487350483 18.4412520546541,-92.3837487694576 18.6745854039283,-91.9420821317385 18.6995854056362,-91.8504154710798 18.5079187258753,-91.5254154923808 18.4495853885568,-91.2420821776176 18.7079187395389,-91.3670821694249 18.8662520836892,-91.4754154956579 18.77458541076,-91.5004154940193 18.799585412468,-90.725415544814 19.3579187839453,-90.7087488792397 19.6829188061486,-90.4587488956251 19.9412521571307,-90.508748892348 20.5079188625107,-90.3337489038178 21.0245855644748,-89.8504156021629 21.2579189137489,-88.1670823791578 21.6245856054654,-87.2420824397837 21.4245855918019,-87.1087491151893 21.5412522664389,-87.3837490971653 21.4995855969257,-87.4004157627396 21.5245855986336,-87.0837491168278 21.6079189376601,-86.8504157987875 21.4412522596072,-86.8420824660003 21.2495855798463,-86.7920824692774 21.4079189239966,-86.7337491397673 21.1329189052092,-87.4254157611011 20.216252175918,-87.4670824250369 19.7745854790777,-87.7420824070129 19.6579188044406,-87.6587490791414 19.507918794193,-87.4587490922497 19.649585470538,-87.4087490955268 19.5579187976089,-87.6504157463542 19.3412521161401,-87.6670824119286 19.1912521058924,-87.5254157545469 19.3745854517506,-87.4420824266754 19.2995854466268,-87.8420824004588 18.1855876003555,-88.0837490512862 18.49125205807,-88.0087490562019 18.6495854022203,-88.0670823857119 18.8579187497865,-88.860277 17.894105,-89.037521 18.005122,-89.151901 17.815629,-90.987716 17.815344,-90.987447 17.251299,-91.434554 17.232897,-90.714251 16.727064,-90.631162 16.478371,-90.389878 16.413297,-90.426501 16.09888,-91.731518 16.073893,-92.210531 15.260619,-92.05933 15.069636,-92.2337487792889 14.5329184543126,-94.1254153219728 16.2162519026473,-94.2087486498443 16.1579185653288,-93.9670819990169 15.9912518872758,-94.5337486285433 16.1912519009394,-95.1504152547927 16.1829185670367,-96.2337485171227 15.6745851989752,-96.575415161396 15.6579185311699,-97.2170817860069 15.9245852160547,-97.7920817483205 15.9745852194705,-98.5587483647386 16.3079185755764,-98.7754150172047 16.5495852587532,-99.6420816270686 16.6829186011956,-101.067081533672 17.2579186404782,-101.942081476323 17.9579186883006,-102.192081459938 17.9079186848847,-103.367081382926 18.2579187087959,-103.850414684581 18.7829187446627,-105.042081273144 19.3829187856533,-105.508747909225 19.9995854944492,-105.70041456333 20.4162521895816,-105.250414592823 20.5745855337319,-105.333747920695 20.7495855456874,-105.542081240374 20.782918881298,-105.242081260036 21.0579189000854,-105.192081263313 21.4662522613151,-105.625414568245 21.9329189598634,-105.825414555137 22.6579190093937,-106.433747848599 23.1745857113578,-106.933747815828 23.8829190930828,-108.017081078158 24.657919146029,-108.067081074881 25.082919175064,-108.342081056857 25.1579191801879,-108.783747694576 25.3745858616567,-108.775414361789 25.532919205807,-109.050414343765 25.4579192006832,-109.06708100934 25.5829192092229,-109.400414320826 25.6329192126388,-109.283747661806 25.7079192177626,-109.400414320826 25.674585882152,-109.450414317549 25.9329192331341,-109.283747661806 26.5329192741247,-109.517080979846 26.7495859555935,-109.783747629035 26.7079192860803,-109.975414283139 27.0579193099915,-110.592080909389 27.2995859931682,-110.483747583156 27.3245859948762,-110.633747573325 27.6079193475662,-110.550414245453 27.8579193646456,-111.108747542192 27.9329193697694,-112.175414138948 28.957919439795,-112.233747468458 29.3079194637062,-112.417080789776 29.3412527993168,-112.383747458627 29.4912528095645,-112.750414101262 29.9079195046968,-112.758747434049 30.1912528573868,-113.100414078322 30.6912528915457,-113.100414078322 31.1829195918019,-113.6337473767 31.3245862681469,-113.642080709487 31.4745862783945,-113.900414025889 31.6162529547395,-114.175414007865 31.4912529461998,-114.583747314436 31.7495862971819,-114.642080643946 31.6579196242528,-114.808747299689 31.7412529632792,-114.883747294773 31.1245862544833,-114.708747306243 30.9245862408198,-114.575413981648 30.0329195132365,-113.6337473767 29.2912527959009,-113.533747383254 28.9079194363792,-113.225414070129 28.841252765158,-113.092080745535 28.4995860751494,-112.84208076192 28.4412527378309,-112.692080771752 27.7412526900085,-111.950414153695 27.0995859795047,-112.008747483205 26.9662526370623,-111.75874749959 26.54958594193,-111.825414161888 26.8995859658412,-111.442080853679 26.524585940222,-111.308747529084 25.7829192228864,-111.008747548747 25.5162525380017,-110.650414238899 24.8079191562767,-110.67541423726 24.3495857916311,-110.3254142602 24.1662524457728,-110.333747592987 24.3412524577284,-110.225414266754 24.3495857916311,-109.975414283139 24.0412524372331,-109.825414292971 24.0662524389411,-109.683747635589 23.6495857438087,-109.467080983123 23.5662524047822,-109.450414317549 23.1912523791631,-109.992080948714 22.8745856908625,-110.3254142602 23.5579190708796,-111.250414199574 24.232919116994,-111.675414171719 24.3495857916311,-111.483747517614 24.3662524594364,-111.683747504506 24.5829191409052,-111.825414161888 24.5079191357814,-112.133747475012 24.8495858257899,-112.058747479928 24.541252471392,-112.108747476651 24.5579191391973,-112.308747463543 24.8079191562767,-112.092080811077 25.2745858548249,-112.100414143864 25.7412525533732,-112.350414127478 26.1829192502135,-113.133747409471 26.6329192809564,-113.083747412748 26.6745859504697,-113.158747407832 26.7912526251067,-113.133747409471 26.6579192826644,-113.267080734065 26.6995859521776,-113.142080742258 26.8579192963279,-113.158747407832 26.9829193048676,-113.258747401278 26.7495859555935,-113.617080711126 26.7079192860803,-113.833747363592 26.9579193031597,-114.433747324267 27.1662526507259,-114.533747317713 27.4329193356106,-115.025413952155 27.7329193561059,-115.06708061609 27.8662526985482,-114.492080653777 27.7745860256191,-114.292080666885 27.8745860324509,-114.117080678355 27.6245860153715,-113.958747355399 27.6579193509821,-114.275414001311 27.8829193663535,-114.142080676716 28.0912527139197,-114.075414014419 27.9995860409906,-114.042080683271 28.0245860426985,-114.050414016058 28.4662527395389,-114.967080622645 29.3745861349274,-115.700413907914 29.7495861605465,-115.808747234147 30.2912528642186,-116.050413884975 30.4579195422716,-116.067080550549 30.8079195661827,-116.342080532525 30.9579195764304,-116.317080534164 31.1662529239966,-116.692080509585 31.5495862835184,-116.742080506308 31.7495862971819,-116.658747178437 31.724586295474,-116.617080514501 31.8495863040137,-116.88374716369 32.007919648164,-117.100413816156 32.3995863415884,-117.125413814517 32.5329196840307,-116.958747158774 32.5487689268803)))" 10 | ) 11 | data = osmpy.get(geometry) 12 | 13 | assert isinstance(data, pandas.core.frame.DataFrame) 14 | assert len(data) > 0 15 | --------------------------------------------------------------------------------