33# which is licensed under Apache 2.0.
44# See LICENSE.txt for the full license text.
55
6- import requests
7- import json
86import csv
9- import geojson
7+ import json
108import logging
9+ import re
1110from datetime import datetime
12- from shapely .geometry import Polygon , Point
1311from io import StringIO
12+
13+ import geojson
14+ import requests
15+ from shapely .geometry import Point , Polygon
16+
1417from .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 )
0 commit comments