Skip to content

Commit

Permalink
Merge pull request #79 from traveltime-dev/add-time-map-geojson
Browse files Browse the repository at this point in the history
Added support for time-map geojson response
  • Loading branch information
arnasbr authored Oct 3, 2023
2 parents 96d94cc + fe463b4 commit 1173800
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 3 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Given origin coordinates, find shapes of zones reachable within corresponding tr
* search_range: Range - When enabled, range adds an arrival window to the arrival time, and results are returned for any
journeys that arrive during this window.

### JSON response

#### Returns:

* results: List[TimeMapResult] - The list of isochrone shapes.
Expand All @@ -93,6 +95,35 @@ async def main():
print(results)


asyncio.run(main())
```

### GEOJSON response

#### Returns:

* results: FeatureCollection - The list of Features.

#### Example:

```python
import asyncio
from datetime import datetime

from traveltimepy import Driving, Coordinates, TravelTimeSdk


async def main():
sdk = TravelTimeSdk("YOUR_APP_ID", "YOUR_APP_KEY")

results = await sdk.time_map_geojson_async(
coordinates=[Coordinates(lat=51.507609, lng=-0.128315), Coordinates(lat=51.517609, lng=-0.138315)],
arrival_time=datetime.now(),
transportation=Driving()
)
print(results)


asyncio.run(main())
```

Expand Down
28 changes: 28 additions & 0 deletions tests/time_map_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ async def test_departures(sdk):
assert len(results) == 2


@pytest.mark.asyncio
async def test_departures_geojson(sdk):
results = await sdk.time_map_geojson_async(
coordinates=[
Coordinates(lat=51.507609, lng=-0.128315),
Coordinates(lat=51.517609, lng=-0.138315),
],
departure_time=datetime.now(),
travel_time=900,
transportation=Driving(),
)
assert len(results) == 2


@pytest.mark.asyncio
async def test_arrivals(sdk):
results = await sdk.time_map_async(
Expand All @@ -32,6 +46,20 @@ async def test_arrivals(sdk):
assert len(results) == 2


@pytest.mark.asyncio
async def test_arrivals_geojson(sdk):
results = await sdk.time_map_geojson_async(
coordinates=[
Coordinates(lat=51.507609, lng=-0.128315),
Coordinates(lat=51.517609, lng=-0.138315),
],
arrival_time=datetime.now(),
travel_time=900,
transportation=Driving(),
)
assert len(results) == 2


@pytest.mark.asyncio
async def test_union_departures(sdk):
result = await sdk.union_async(
Expand Down
63 changes: 63 additions & 0 deletions traveltimepy/dto/requests/time_map_geojson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import typing
from datetime import datetime

from typing import List, Optional

from geojson_pydantic import FeatureCollection
from pydantic.main import BaseModel

from traveltimepy import (
Coordinates,
Range,
PublicTransport,
Driving,
Ferry,
Walking,
Cycling,
DrivingTrain,
)
from traveltimepy.dto.requests.request import TravelTimeRequest
from traveltimepy.itertools import split, flatten


class DepartureSearch(BaseModel):
id: str
coords: Coordinates
departure_time: datetime
travel_time: int
transportation: typing.Union[
PublicTransport, Driving, Ferry, Walking, Cycling, DrivingTrain
]
range: Optional[Range] = None


class ArrivalSearch(BaseModel):
id: str
coords: Coordinates
arrival_time: datetime
travel_time: int
transportation: typing.Union[
PublicTransport, Driving, Ferry, Walking, Cycling, DrivingTrain
]
range: Optional[Range] = None


class TimeMapRequestGeojson(TravelTimeRequest[FeatureCollection]):
departure_searches: List[DepartureSearch]
arrival_searches: List[ArrivalSearch]

def split_searches(self, window_size: int) -> List[FeatureCollection]:
return [
FeatureCollection(
type="FeatureCollection",
departure_searches=departures,
arrival_searches=arrivals,
)
for departures, arrivals in split(
self.departure_searches, self.arrival_searches, window_size
)
]

def merge(self, responses: List[FeatureCollection]) -> FeatureCollection:
merged_features = flatten([response.features for response in responses])
return FeatureCollection(type="FeatureCollection", features=merged_features)
32 changes: 32 additions & 0 deletions traveltimepy/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ async def send_post_async(
return request.merge(responses)


async def send_post_geojson_async(
response_class: Type[T],
path: str,
headers: Dict[str, str],
request: TravelTimeRequest,
sdk_params: SdkParams,
) -> T:
window_size = _window_size(sdk_params.rate_limit)
async with ClientSession(
connector=TCPConnector(ssl=False, limit_per_host=sdk_params.limit_per_host)
) as session:
retry_options = ExponentialRetry(attempts=sdk_params.retry_attempts)
async with RetryClient(
client_session=session, retry_options=retry_options
) as client:
rate_limit = AsyncLimiter(
sdk_params.rate_limit // window_size, sdk_params.time_window
)
tasks = [
send_post_request_async(
client,
response_class,
f"https://{sdk_params.host}/v4/{path}",
headers,
request,
rate_limit,
)
]
responses = await asyncio.gather(*tasks)
return request.merge(responses)


def _window_size(rate_limit: int):
if rate_limit >= DEFAULT_SPLIT_SIZE:
return DEFAULT_SPLIT_SIZE
Expand Down
49 changes: 48 additions & 1 deletion traveltimepy/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime
from typing import Dict, Union, List, Optional

from traveltimepy.dto.requests.time_map_geojson import TimeMapRequestGeojson
from traveltimepy.errors import ApiError
from traveltimepy import TimeFilterFastRequest_pb2

Expand All @@ -28,7 +29,6 @@
)
from traveltimepy.dto.requests.time_map import TimeMapRequest


from traveltimepy.dto.requests import (
time_filter,
time_filter_fast,
Expand Down Expand Up @@ -364,6 +364,53 @@ def create_time_map(
raise ApiError("arrival_time or departure_time should be specified")


def create_time_map_geojson(
coordinates: List[Coordinates],
transportation: Union[
PublicTransport, Driving, Ferry, Walking, Cycling, DrivingTrain
],
travel_time: int,
departure_time: Optional[datetime],
arrival_time: Optional[datetime],
search_range: Optional[Range],
) -> TimeMapRequestGeojson:
if arrival_time is not None and departure_time is not None:
raise ApiError("arrival_time and departure_time cannot be both specified")

if arrival_time is not None:
return TimeMapRequestGeojson(
arrival_searches=[
time_map.ArrivalSearch(
id=f"Search {ind}",
coords=cur_coordinates,
travel_time=travel_time,
arrival_time=arrival_time,
transportation=transportation,
range=search_range,
)
for ind, cur_coordinates in enumerate(coordinates)
],
departure_searches=[]
)
elif departure_time is not None:
return TimeMapRequestGeojson(
departure_searches=[
time_map.DepartureSearch(
id=f"Search {ind}",
coords=cur_coordinates,
travel_time=travel_time,
departure_time=departure_time,
transportation=transportation,
range=search_range,
)
for ind, cur_coordinates in enumerate(coordinates)
],
arrival_searches=[]
)
else:
raise ApiError("arrival_time or departure_time should be specified")


def create_intersection(
coordinates: List[Coordinates],
transportation: Union[
Expand Down
31 changes: 29 additions & 2 deletions traveltimepy/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@
create_proto_request,
create_time_map,
create_intersection,
create_union,
create_union, create_time_map_geojson,
)

from traveltimepy.proto_http import send_proto_async
from traveltimepy.http import (
send_get_async,
send_post_async,
SdkParams,
SdkParams, send_post_geojson_async,
)

from geojson_pydantic import FeatureCollection
Expand Down Expand Up @@ -419,6 +419,33 @@ async def time_map_async(
)
return resp.results

async def time_map_geojson_async(
self,
coordinates: List[Coordinates],
transportation: Union[
PublicTransport, Driving, Ferry, Walking, Cycling, DrivingTrain
],
arrival_time: Optional[datetime] = None,
departure_time: Optional[datetime] = None,
travel_time: int = 3600,
search_range: Optional[Range] = None,
) -> FeatureCollection:
resp = await send_post_geojson_async(
FeatureCollection,
"time-map",
self._headers(AcceptType.GEO_JSON),
create_time_map_geojson(
coordinates,
transportation,
travel_time,
arrival_time,
departure_time,
search_range,
),
self._sdk_params,
)
return resp

@staticmethod
def _geocoding_reverse_params(lat: float, lng: float) -> Dict[str, str]:
full_query = {"lat": lat, "lng": lng}
Expand Down

0 comments on commit 1173800

Please sign in to comment.