import requests
from purl import URL
from typing import overload, ClassVar, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Collection, Iterable
from onegov.gis.models.coordinates import AnyCoordinates, RealCoordinates
[docs]
Endpoint = Literal['directions', 'geocoding']
[docs]
GeocodeProfile = Literal['places']
[docs]
DirectionsProfile = Literal[
'driving-traffic',
'driving',
'walking',
'cycling'
]
[docs]
class MapboxRequests:
[docs]
host: ClassVar[str] = 'https://api.mapbox.com'
[docs]
endpoints: ClassVar[tuple[Endpoint, ...]] = ('directions', 'geocoding')
[docs]
geocode_profiles: ClassVar[tuple[GeocodeProfile, ...]] = ('places', )
[docs]
directions_profiles: ClassVar[tuple[DirectionsProfile, ...]] = (
'driving-traffic',
'driving',
'walking',
'cycling'
)
@overload
def __init__(
self,
access_token: str | None,
endpoint: Literal['geocoding'] = 'geocoding',
profile: GeocodeProfile = 'places',
api_version: str = 'v5'
): ...
@overload
def __init__(
self,
access_token: str | None,
endpoint: Literal['directions'],
profile: DirectionsProfile,
api_version: str = 'v5'
): ...
def __init__(
self,
# NOTE: This is technically not optional, but for testing purposes
# we allow it to be optional
access_token: str | None,
endpoint: Endpoint = 'geocoding',
profile: str = 'places',
api_version: str = 'v5'
):
assert endpoint in self.endpoints
if endpoint == 'directions':
assert profile in self.directions_profiles
profile = f'mapbox/{profile}'
if endpoint == 'geocoding':
assert profile in self.geocode_profiles
profile = f'mapbox.{profile}'
[docs]
self.access_token = access_token
[docs]
self.endpoint = endpoint
[docs]
self.api_version = api_version
@property
[docs]
def base_url(self) -> URL:
url = URL(
f'{self.host}/{self.endpoint}/{self.api_version}/{self.profile}')
if self.access_token is not None:
url = url.query_param('access_token', self.access_token)
return url
@overload
[docs]
def geocode(
self,
text: str | None = None,
street: str | None = None,
zip_code: str | None = None,
city: str | None = None,
# FIXME: Why is this abbreviated...
ctry: str | None = None,
locale: str | None = None,
as_url: Literal[False] = False
) -> requests.Response: ...
@overload
def geocode(
self,
text: str | None = None,
street: str | None = None,
zip_code: str | None = None,
city: str | None = None,
# FIXME: Why is this abbreviated...
ctry: str | None = None,
locale: str | None = None,
*,
as_url: Literal[True]
) -> URL: ...
@overload
def geocode(
self,
text: str | None = None,
street: str | None = None,
zip_code: str | None = None,
city: str | None = None,
# FIXME: Why is this abbreviated...
ctry: str | None = None,
locale: str | None = None,
*,
as_url: bool
) -> requests.Response | URL: ...
def geocode(
self,
text: str | None = None,
street: str | None = None,
zip_code: str | None = None,
city: str | None = None,
# FIXME: Why is this abbreviated...
ctry: str | None = None,
locale: str | None = None,
as_url: bool = False
) -> requests.Response | URL:
if not ctry:
ctry = 'Schweiz'
if not text:
assert street
assert zip_code
assert city
address = text or f'{street}, {zip_code} {city}, {ctry}'
url = self.base_url
url = url.add_path_segment(f'{address}.json')
url = url.query_param('types', 'address')
locale = locale and locale.replace('_CH', '')
if locale:
url = url.query_param('language', locale)
if as_url:
return url
return requests.get(url.as_string(), timeout=60)
@overload
[docs]
def directions(
self,
coordinates: 'Iterable[tuple[str | float, str | float]]',
as_url: Literal[False] = False
) -> requests.Response: ...
@overload
def directions(
self,
coordinates: 'Iterable[tuple[str | float, str | float]]',
as_url: Literal[True]
) -> URL: ...
@overload
def directions(
self,
coordinates: 'Iterable[tuple[str | float, str | float]]',
as_url: bool
) -> requests.Response | URL: ...
def directions(
self,
coordinates: 'Iterable[tuple[str | float, str | float]]',
as_url: bool = False
) -> requests.Response | URL:
"""
coordinates: iterable of tuples of (lat, lon)
"""
url = self.base_url.add_path_segment(
';'.join(f'{c[1]},{c[0]}' for c in coordinates)
)
if as_url:
return url
return requests.get(url.as_string(), timeout=60)
[docs]
def outside_bbox(
coordinate: 'AnyCoordinates | None',
bbox: 'Collection[RealCoordinates] | None'
) -> bool:
"""Checks if the Coordinates instance is inside the bounding box defined
by the most outward sitting points in an iterable of two+ Coordinates.
"""
if not coordinate or not bbox:
return False
assert len(bbox) >= 2
max_lat = max(c.lat for c in bbox)
min_lat = min(c.lat for c in bbox)
max_lon = max(c.lon for c in bbox)
min_lon = min(c.lon for c in bbox)
return not (
(max_lat >= coordinate.lat >= min_lat)
and (max_lon >= coordinate.lon >= min_lon)
)