Skip to content

Commit f6a1d62

Browse files
authored
Merge branch 'master' into tox-and-gha
2 parents 66b35e7 + be52585 commit f6a1d62

File tree

5 files changed

+126
-39
lines changed

5 files changed

+126
-39
lines changed

.travis.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
language: python
22
python:
3-
- "3.4"
4-
- "3.5"
5-
- "3.6"
6-
- "3.7"
7-
- "3.8"
3+
- 3.6
4+
- 3.7
5+
- 3.8
6+
- 3.9
87
install:
9-
- "pip install ."
10-
# command to run tests
11-
script: py.test
8+
- sudo apt-get install libgeos-dev
9+
- pip install -r requirements-dev.txt
10+
- pip install -r requirements.txt
11+
script: python -m pytest
1212
notifications:
1313
email: false

overpass/api.py

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@
33
# which is licensed under Apache 2.0.
44
# See LICENSE.txt for the full license text.
55

6-
import requests
7-
import json
86
import csv
9-
import geojson
7+
import json
108
import logging
9+
import re
1110
from datetime import datetime
12-
from shapely.geometry import Polygon, Point
1311
from io import StringIO
12+
13+
import geojson
14+
import requests
15+
from shapely.geometry import Point, Polygon
16+
1417
from .errors import (
15-
OverpassSyntaxError,
16-
TimeoutError,
1718
MultipleRequestsError,
19+
OverpassSyntaxError,
1820
ServerLoadError,
19-
UnknownOverpassError,
2021
ServerRuntimeError,
22+
TimeoutError,
23+
UnknownOverpassError,
2124
)
2225

2326

@@ -109,14 +112,10 @@ def get(self, query, responseformat="geojson", verbosity="body", build=True, dat
109112
if self.debug:
110113
print(content_type)
111114
if content_type == "text/csv":
112-
result = []
113-
reader = csv.reader(StringIO(r.text), delimiter="\t")
114-
for row in reader:
115-
result.append(row)
116-
return result
115+
return list(csv.reader(StringIO(r.text), delimiter="\t"))
117116
elif content_type in ("text/xml", "application/xml", "application/osm3s+xml"):
118117
return r.text
119-
elif content_type == "application/json":
118+
else:
120119
response = json.loads(r.text)
121120

122121
if not build:
@@ -132,12 +131,76 @@ def get(self, query, responseformat="geojson", verbosity="body", build=True, dat
132131
if overpass_remark and overpass_remark.startswith("runtime error"):
133132
raise ServerRuntimeError(overpass_remark)
134133

135-
if responseformat is not "geojson":
134+
if responseformat != "geojson":
136135
return response
137136

138137
# construct geojson
139138
return self._as_geojson(response["elements"])
140139

140+
@staticmethod
141+
def _api_status() -> dict:
142+
"""
143+
:returns: dict describing the client's status with the API
144+
"""
145+
endpoint = "https://overpass-api.de/api/status"
146+
147+
r = requests.get(endpoint)
148+
lines = tuple(r.text.splitlines())
149+
150+
available_re = re.compile(r'\d(?= slots? available)')
151+
available_slots = int(
152+
available_re.search(lines[3]).group()
153+
if available_re.search(lines[3])
154+
else 0
155+
)
156+
157+
waiting_re = re.compile(r'(?<=Slot available after: )[\d\-TZ:]{20}')
158+
waiting_slots = tuple(
159+
datetime.strptime(
160+
waiting_re.search(line).group(), "%Y-%m-%dT%H:%M:%S%z"
161+
)
162+
for line in lines if waiting_re.search(line)
163+
)
164+
165+
current_idx = next(
166+
i for i, word in enumerate(lines)
167+
if word.startswith('Currently running queries')
168+
)
169+
running_slots = tuple(tuple(line.split()) for line in lines[current_idx + 1:])
170+
running_slots_datetimes = tuple(
171+
datetime.strptime(
172+
slot[3], "%Y-%m-%dT%H:%M:%S%z"
173+
)
174+
for slot in running_slots
175+
)
176+
177+
return {
178+
"available_slots": available_slots,
179+
"waiting_slots": waiting_slots,
180+
"running_slots": running_slots_datetimes,
181+
}
182+
183+
@property
184+
def slots_available(self) -> int:
185+
"""
186+
:returns: count of open slots the client has on the server
187+
"""
188+
return self._api_status()["available_slots"]
189+
190+
@property
191+
def slots_waiting(self) -> tuple:
192+
"""
193+
:returns: tuple of datetimes representing waiting slots and when they will be available
194+
"""
195+
return self._api_status()["waiting_slots"]
196+
197+
@property
198+
def slots_running(self) -> tuple:
199+
"""
200+
:returns: tuple of datetimes representing running slots and when they will be freed
201+
"""
202+
return self._api_status()["running_slots"]
203+
141204
def search(self, feature_type, regex=False):
142205
"""Search for something."""
143206
raise NotImplementedError()
@@ -236,21 +299,18 @@ def _as_geojson(self, elements):
236299
if member["role"] == "inner":
237300
points = [(coords["lon"], coords["lat"]) for coords in member.get("geometry", [])]
238301
# Check that the inner polygon is complete
239-
if points and points[-1] == points[0]:
240-
# We need to check to which outer polygon the inner polygon belongs
241-
point = Point(points[0])
242-
check = False
243-
for poly in polygons:
244-
polygon = Polygon(poly[0])
245-
if polygon.contains(point):
246-
poly.append(points)
247-
check = True
248-
break
249-
if not check:
250-
raise UnknownOverpassError("Received corrupt data from Overpass (inner polygon cannot "
251-
"be matched to outer polygon).")
252-
else:
302+
if not points or points[-1] != points[0]:
253303
raise UnknownOverpassError("Received corrupt data from Overpass (incomplete polygon).")
304+
# We need to check to which outer polygon the inner polygon belongs
305+
point = Point(points[0])
306+
for poly in polygons:
307+
polygon = Polygon(poly[0])
308+
if polygon.contains(point):
309+
poly.append(points)
310+
break
311+
else:
312+
raise UnknownOverpassError("Received corrupt data from Overpass (inner polygon cannot "
313+
"be matched to outer polygon).")
254314
# Finally create MultiPolygon geometry
255315
if polygons:
256316
geometry = geojson.MultiPolygon(polygons)

overpass/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
class Utils(object):
88

9-
@classmethod
10-
def to_overpass_id(cls, osmid, area=False):
9+
@staticmethod
10+
def to_overpass_id(osmid, area=False):
1111
area_base = 2400000000
1212
relation_base = 3600000000
1313
if area:

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pytest>=6.2.0
2-
tox>=3.20.1
2+
tox>=3.20.1

tests/test_api.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,30 @@ def _get_from_overpass(self, query):
5353
osm_geo = api.get("rel(6518385);out body geom;way(10322303);out body geom;node(4927326183);", verbosity='body geom')
5454
ref_geo = geojson.load(open(os.path.join(os.path.dirname(__file__), "example.json"), "r"))
5555
assert osm_geo==ref_geo
56+
57+
58+
def test_slots_available():
59+
api = overpass.API(debug=True)
60+
61+
map_query = overpass.MapQuery(37.86517, -122.31851, 37.86687, -122.31635)
62+
api.get(map_query)
63+
64+
assert api.slots_available <= 2 and api.slots_available >= 0
65+
66+
67+
def test_slots_running():
68+
api = overpass.API(debug=True)
69+
70+
map_query = overpass.MapQuery(37.86517, -122.31851, 37.86687, -122.31635)
71+
api.get(map_query)
72+
73+
assert isinstance(api.slots_running, tuple)
74+
75+
76+
def test_slots_waiting():
77+
api = overpass.API(debug=True)
78+
79+
map_query = overpass.MapQuery(37.86517, -122.31851, 37.86687, -122.31635)
80+
api.get(map_query)
81+
82+
assert isinstance(api.slots_waiting, tuple)

0 commit comments

Comments
 (0)