diff --git a/mds/api/__init__.py b/mds/api/__init__.py index 1013089..123aeb9 100644 --- a/mds/api/__init__.py +++ b/mds/api/__init__.py @@ -2,5 +2,4 @@ Module implementing the MDS Provider API. """ -from mds.api.client import ProviderClient - +from mds.api.client import ProviderClient, MultipleProviderClient diff --git a/mds/api/client.py b/mds/api/client.py index bb27cdc..2e20073 100644 --- a/mds/api/client.py +++ b/mds/api/client.py @@ -1,30 +1,15 @@ """ -MDS Provider API client implementation. +MDS Provider API client implementation. """ from datetime import datetime import json +import requests import mds from mds.api.auth import OAuthClientCredentialsAuth from mds.providers import get_registry, Provider - -class ProviderClient(OAuthClientCredentialsAuth): - """ - Client for MDS Provider APIs - """ - def __init__(self, providers=None, ref=None): - """ - Initialize a new ProviderClient object. - - :providers: is a list of Providers this client tracks by default. If None is given, downloads and uses the official Provider registry. - - When using the official Providers registry, :ref: could be any of: - - git branch name - - commit hash (long or short) - - git tag - """ - self.providers = providers if providers is not None else get_registry(ref) +class ProviderClientBase(OAuthClientCredentialsAuth): def _auth_session(self, provider): """ @@ -50,23 +35,90 @@ def _build_url(self, provider, endpoint): return url - def _request(self, providers, endpoint, params, paging): + def _date_format(self, dt): """ - Internal helper for sending requests. - - Returns a dict of provider => payload(s). + Internal helper to format datetimes for querystrings. """ - def __describe(res): - """ - Prints details about the given response. - """ - print(f"Requested {res.url}, Response Code: {res.status_code}") - print("Response Headers:") - for k,v in res.headers.items(): - print(f"{k}: {v}") + return int(dt.timestamp()) if isinstance(dt, datetime) else int(dt) - if r.status_code is not 200: - print(r.text) + def _prepare_status_changes_params( + self, + start_time=None, + end_time=None, + bbox=None, + **kwargs): + + # convert datetimes to querystring friendly format + if start_time is not None: + start_time = self._date_format(start_time) + if end_time is not None: + end_time = self._date_format(end_time) + + # gather all the params together + return { + **dict(start_time=start_time, end_time=end_time, bbox=bbox), + **kwargs + } + + def _prepare_trips_params( + self, + device_id=None, + vehicle_id=None, + start_time=None, + end_time=None, + bbox=None, + **kwargs): + + # convert datetimes to querystring friendly format + if start_time is not None: + start_time = self._date_format(start_time) + if end_time is not None: + end_time = self._date_format(end_time) + + # gather all the params togethers + return { + **dict(device_id=device_id, vehicle_id=vehicle_id, start_time=start_time, end_time=end_time, bbox=bbox), + **kwargs + } + + +class ProviderClient(ProviderClientBase): + def __init__(self, provider): + self.provider = provider + + def iterate_trips(self, **kwargs): + return self.iterate_items(mds.TRIPS, **kwargs) + + def iterate_status_changes(self, **kwargs): + return self.iterate_items(mds.STATUS_CHANGES, **kwargs) + + def iterate_items(self, endpoint, **kwargs): + for page in self.iterate_pages(endpoint, **kwargs): + for item in page['data'][endpoint]: + yield item + + def iterate_pages_of_trips(self, paging=True, **kwargs): + return self.iterate_pages(mds.TRIPS, paging=paging, **kwargs) + + def iterate_pages_of_status_changes(self, paging=True, **kwargs): + return self.iterate_pages(mds.STATUS_CHANGES, paging=paging, **kwargs) + + def iterate_pages(self, endpoint, paging=True, **kwargs): + params = getattr(self, f'_prepare_{endpoint}_params')(**kwargs) + return self._request(endpoint, params, paging) + + def _request(self, endpoint, params, paging): + url = self._build_url(self.provider, endpoint) + session = self._auth_session(self.provider) + for page in self._iterate_pages_from_session(session, endpoint, url, params): + yield page + if not paging: + break + + def _iterate_pages_from_session(self, session, endpoint, url, params): + """ + Request items from endpoint, following pages + """ def __has_data(page): """ @@ -83,61 +135,73 @@ def __next_url(page): """ return page["links"].get("next") if "links" in page else None - # create a request url for each provider - urls = [self._build_url(p, endpoint) for p in providers] - - # keyed by provider - results = {} + response = session.get(url, params=params) + response.raise_for_status() - for i in range(len(providers)): - provider, url = providers[i], urls[i] + this_page = response.json() + if __has_data(this_page): + yield this_page - # establish an authenticated session - session = self._auth_session(provider) + next_url = __next_url(this_page) + while next_url is not None: + response = session.get(next_url) + response.raise_for_status() + this_page = response.json() + if __has_data(this_page): + yield this_page + next_url = __next_url(this_page) + else: + break - # get the initial page of data - r = session.get(url, params=params) - if r.status_code is not 200: - __describe(r) - continue +class MultipleProviderClient(ProviderClientBase): + """ + Client for MDS Provider APIs + """ + def __init__(self, providers=None, ref=None): + """ + Initialize a new MultipleProviderClient object. - this_page = r.json() + :providers: is a list of Providers this client tracks by default. If None is given, downloads and uses the official Provider registry. - # track the list of pages per provider - results[provider] = [this_page] if __has_data(this_page) else [] + When using the official Providers registry, :ref: could be any of: + - git branch name + - commit hash (long or short) + - git tag + """ + self.providers = providers if providers is not None else get_registry(ref) - # get subsequent pages of data - next_url = __next_url(this_page) - while paging and next_url: - r = session.get(next_url) + def _request_from_providers(self, providers, endpoint, params, paging): + """ + Internal helper for sending requests. - if r.status_code is not 200: - __describe(r) - break + Returns a dict of provider => payload(s). + """ + def __describe(res): + """ + Prints details about the given response. + """ + print(f"Requested {res.url}, Response Code: {res.status_code}") + print("Response Headers:") + for k,v in res.headers.items(): + print(f"{k}: {v}") - this_page = r.json() + if r.status_code is not 200: + print(r.text) - if __has_data(this_page): - results[provider].append(this_page) - next_url = __next_url(this_page) - else: - break + results = {} + for provider in providers: + client = ProviderClient(provider) + try: + results[provider] = list(client.request(endpoint, params, paging)) + except requests.RequestException as exc: + __describe(exc.response) return results - def _date_format(self, dt): - """ - Internal helper to format datetimes for querystrings. - """ - return int(dt.timestamp()) if isinstance(dt, datetime) else int(dt) - def get_status_changes( self, providers=None, - start_time=None, - end_time=None, - bbox=None, paging=True, **kwargs): """ @@ -155,7 +219,7 @@ def get_status_changes( Should be a datetime object or numeric representation of UNIX seconds - `bbox`: Filters for status changes where `event_location` is within defined bounding-box. - The order is defined as: southwest longitude, southwest latitude, + The order is defined as: southwest longitude, southwest latitude, northeast longitude, northeast latitude (separated by commas). e.g. @@ -168,31 +232,16 @@ def get_status_changes( if providers is None: providers = self.providers - # convert datetimes to querystring friendly format - if start_time is not None: - start_time = self._date_format(start_time) - if end_time is not None: - end_time = self._date_format(end_time) - - # gather all the params together - params = { - **dict(start_time=start_time, end_time=end_time, bbox=bbox), - **kwargs - } + params = self._prepare_status_changes_params(**kwargs) # make the request(s) - status_changes = self._request(providers, mds.STATUS_CHANGES, params, paging) + status_changes = self._request_from_providers(providers, mds.STATUS_CHANGES, params, paging) return status_changes def get_trips( self, providers=None, - device_id=None, - vehicle_id=None, - start_time=None, - end_time=None, - bbox=None, paging=True, **kwargs): """ @@ -214,7 +263,7 @@ def get_trips( Should be a datetime object or numeric representation of UNIX seconds - `bbox`: Filters for trips where and point within `route` is within defined bounding-box. - The order is defined as: southwest longitude, southwest latitude, + The order is defined as: southwest longitude, southwest latitude, northeast longitude, northeast latitude (separated by commas). e.g. @@ -227,19 +276,9 @@ def get_trips( if providers is None: providers = self.providers - # convert datetimes to querystring friendly format - if start_time is not None: - start_time = self._date_format(start_time) - if end_time is not None: - end_time = self._date_format(end_time) - - # gather all the params togethers - params = { - **dict(device_id=device_id, vehicle_id=vehicle_id, start_time=start_time, end_time=end_time, bbox=bbox), - **kwargs - } + params = self._prepare_trips_params(**kwargs) # make the request(s) - trips = self._request(providers, mds.TRIPS, params, paging) + trips = self._request_from_providers(providers, mds.TRIPS, params, paging) return trips diff --git a/mds/fake/server.py b/mds/fake/server.py new file mode 100644 index 0000000..bb3e8e2 --- /dev/null +++ b/mds/fake/server.py @@ -0,0 +1,144 @@ + +from flask import Flask, request, jsonify, url_for +import mds.json +import json, base64 +from datetime import datetime +import pytz + +epoch = pytz.utc.localize(datetime(1970, 1, 1, 0, 0, 0)) + +def ensure_unixtime(dt_or_float): + if isinstance(dt_or_float, datetime): + return (dt_or_float - epoch).total_seconds() + return dt_or_float + +class UnixtimeJSONEncoder(mds.json.CustomJsonEncoder): + date_format = 'unix' + +class PaginationCursor(object): + def __init__(self, serialized_cursor=None, offset=None): + if offset is not None and serialized_cursor is not None: + raise RuntimeError('Cannot initialize with non-None offset AND non-None cursor') + + if serialized_cursor is not None: + data = json.loads(base64.b64decode(serialized_cursor).decode('utf-8')) + self.offset = data['o'] + else: + self.offset = 0 if offset is None else offset + + def serialize(self): + return base64.b64encode(json.dumps({ 'o': self.offset }).encode('utf-8')) + +class InMemoryPaginator(object): + def __init__(self, all_items, serialized_cursor=None, page_size=20, next_page_shortness=0): + self.items = all_items + self.cursor = PaginationCursor(serialized_cursor) + self.page_size = page_size + self.next_page_shortness = next_page_shortness + + def next_cursor_serialized(self): + offset = self.cursor.offset + return PaginationCursor(offset=offset+self.page_size-self.next_page_shortness).serialize() + + def get_page(self): + offset = self.cursor.offset + return self.items[offset:offset+self.page_size] + +def make_mds_response_data(version, resource_name, paginator, **params): + return { + 'version': version, + 'links': { + 'next': url_for(resource_name, + cursor=paginator.next_cursor_serialized(), + _external=True, + **params), + }, + 'data': { + resource_name: paginator.get_page(), + } + } + +def params_match_trip(params, trip): + vehicle_id = params.get('vehicle_id') + if vehicle_id and trip['vehicle_id'] != vehicle_id: + return False + + device_id = params.get('device_id') + if device_id and trip['device_id'] != device_id: + return False + + start_time = params.get('start_time') + if start_time and ensure_unixtime(trip['start_time']) < float(start_time): + return False + + end_time = params.get('end_time') + if end_time and ensure_unixtime(trip['end_time']) > float(end_time): + return False + + bbox = params.get('bbox') + if bbox is not None: + raise NotImplementedError('fake server does not support bbox queries') + + return True + +def params_match_status_change(params, sc): + start_time = params.get('start_time') + if start_time and ensure_unixtime(sc['event_time']) < float(start_time): + return False + + end_time = params.get('end_time') + if end_time and ensure_unixtime(sc['event_time']) > float(end_time): + return False + + bbox = params.get('bbox') + if bbox is not None: + raise NotImplementedError('fake server does not support bbox queries') + + return True + +def make_static_server_app(trips=[], + status_changes=[], + version='0.2.0', + page_size=20, + next_page_shortness=0): + app = Flask('mds_static') + + options = { + 'next_page_shortness': next_page_shortness, + 'page_size': page_size, + } + + store = { + 'trips': trips, + 'status_changes': status_changes, + } + app.config['STATIC_MDS_DATA'] = store + app.config['STATIC_MDS_OPTIONS'] = options + app.json_encoder = UnixtimeJSONEncoder + + @app.route('/trips') + def trips(): + supported_param_keys = ('device_id', 'vehicle_id', 'start_time', 'end_time', 'bbox') + params = { k: request.args.get(k) for k in request.args if k in supported_param_keys} + selected_trips = [t for t in store['trips'] if params_match_trip(params, t)] + paginator = InMemoryPaginator(selected_trips, + serialized_cursor=request.args.get('cursor'), + page_size=page_size, + next_page_shortness=next_page_shortness) + return jsonify(make_mds_response_data( + version, 'trips', paginator, **params + )) + + @app.route('/status_changes') + def status_changes(): + supported_param_keys = ('start_time', 'end_time', 'bbox') + params = { k: request.args.get(k) for k in request.args if k in supported_param_keys} + selected_items = [sc for sc in store['status_changes'] if params_match_status_change(params, sc)] + paginator = InMemoryPaginator(selected_items, + serialized_cursor=request.args.get('cursor'), + page_size=page_size) + return jsonify(make_mds_response_data( + version, 'status_changes', paginator, **params + )) + + return app diff --git a/mds/json.py b/mds/json.py index 4c723b0..1537298 100644 --- a/mds/json.py +++ b/mds/json.py @@ -2,16 +2,15 @@ Work with MDS Provider data as (Geo)JSON files and objects. """ -import fiona from datetime import datetime -import geopandas +import fiona import json import os -import pandas as pd +import pandas from pathlib import Path import requests import shapely.geometry -from shapely.geometry import Point, Polygon +import shapely.ops from uuid import UUID @@ -43,7 +42,7 @@ def extract_point(feature): Extract the coordinates from the given GeoJSON :feature: as a shapely.geometry.Point """ coords = feature["geometry"]["coordinates"] - return Point(coords[0], coords[1]) + return shapely.geometry.Point(coords[0], coords[1]) def to_feature(shape, properties={}): """ @@ -51,24 +50,16 @@ def to_feature(shape, properties={}): Optionally give the Feature a :properties: dict. """ - collection = to_feature_collection(shape) - feature = collection["features"][0] + feature = shapely.geometry.mapping(shape) feature["properties"] = properties - # remove some unnecessary and redundant data - if "id" in feature: - del feature["id"] - if isinstance(shape, Point) and "bbox" in feature: - del feature["bbox"] + if isinstance(shape, shapely.geometry.Point): + feature["coordinates"] = list(feature["coordinates"]) + else: + # assume shape is polygon (multipolygon will break) + feature["coordinates"] = [list(list(coords) for coords in part) for part in feature["coordinates"]] - return dict(feature) - -def to_feature_collection(shape): - """ - Create a GeoJSON FeatureCollection object for the given shapely.geometry :shape:. - """ - collection = geopandas.GeoSeries([shape]).__geo_interface__ - return dict(collection) + return feature def read_data_file(src, record_type): """ @@ -132,4 +123,3 @@ def default(self, obj): return str(obj) return json.JSONEncoder.default(self, obj) - diff --git a/mds/providers.py b/mds/providers.py index ac04113..d6983d4 100644 --- a/mds/providers.py +++ b/mds/providers.py @@ -56,23 +56,30 @@ def configure(self, config, use_id=False): return Provider(**_kwargs) -def get_registry(ref=DEFAULT_REF): +def get_registry(ref=DEFAULT_REF, file=None): """ - Download and parse the official Provider registry from GitHub. + Parse a Provider registry file; by default, download the official registry from GitHub `master`. Optionally download from the specified :ref:, which could be any of: - git branch name - commit hash (long or short) - git tag - By default, downloads from `master`. + Or use the :file: kwarg to skip the download and parse a local registry file. """ providers = [] - url = PROVIDER_REGISTRY.format(ref or DEFAULT_REF) - with requests.get(url, stream=True) as r: - lines = (line.decode("utf-8").replace(", ", ",") for line in r.iter_lines()) + def __parse(lines): for record in csv.DictReader(lines): providers.append(Provider(**record)) + if file: + with open(file, "r") as f: + __parse(f.readlines()) + else: + url = PROVIDER_REGISTRY.format(ref or DEFAULT_REF) + with requests.get(url, stream=True) as r: + lines = (line.decode("utf-8").replace(", ", ",") for line in r.iter_lines()) + __parse(line) + return providers diff --git a/mds/tests/__init__.py b/mds/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mds/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/mds/tests/fixtures/mds_tiny_0.2.0_status_changes_only.json b/mds/tests/fixtures/mds_tiny_0.2.0_status_changes_only.json new file mode 100644 index 0000000..5831641 --- /dev/null +++ b/mds/tests/fixtures/mds_tiny_0.2.0_status_changes_only.json @@ -0,0 +1,1972 @@ +[ + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 1.0, + "event_type": "available", + "event_type_reason": "service_start", + "event_time": 1544507545.035672, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67997436226106, + 45.391839383840626 + ] + }, + "properties": { + "timestamp": 1544507545.035672 + } + }, + "associated_trips": null + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 1.0, + "event_type": "available", + "event_type_reason": "service_start", + "event_time": 1544508752.886242, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64865572278676, + 45.62786565607143 + ] + }, + "properties": { + "timestamp": 1544508752.886242 + } + }, + "associated_trips": [] + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "service_start", + "event_time": 1544506620.654661, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.75271238600399, + 45.533356175822696 + ] + }, + "properties": { + "timestamp": 1544506620.654661 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 1.0, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544514202.526848, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67997436226106, + 45.391839383840626 + ] + }, + "properties": { + "timestamp": 1544507545.035672 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.743646770793564, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544514674.345742, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68127293484119, + 45.40876863084977 + ] + }, + "properties": { + "timestamp": 1544514674.345742 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 1.0, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544512582.743511, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64865572278676, + 45.62786565607143 + ] + }, + "properties": { + "timestamp": 1544508752.886242 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.722909191600399, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544513154.15886, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67795737547921, + 45.62917352136674 + ] + }, + "properties": { + "timestamp": 1544513154.15886 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544512665.207284, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.75271238600399, + 45.533356175822696 + ] + }, + "properties": { + "timestamp": 1544506620.654661 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544513401.253133, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.7769407303836, + 45.553643341722626 + ] + }, + "properties": { + "timestamp": 1544513401.253133 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.722909191600399, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544513474.724775, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67795737547921, + 45.62917352136674 + ] + }, + "properties": { + "timestamp": 1544513154.15886 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.5395137954967761, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544513867.703248, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68240767746428, + 45.61539998944145 + ] + }, + "properties": { + "timestamp": 1544513867.703248 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544516134.323204, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.7769407303836, + 45.553643341722626 + ] + }, + "properties": { + "timestamp": 1544513401.253133 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544517234.006464, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.83328028078024, + 45.555895592688536 + ] + }, + "properties": { + "timestamp": 1544517234.006464 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.7304434320301758, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544518081.39188, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68127293484119, + 45.40876863084977 + ] + }, + "properties": { + "timestamp": 1544514674.345742 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.5683005817866493, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544518353.010243, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.66742695137913, + 45.40789201095082 + ] + }, + "properties": { + "timestamp": 1544518353.010243 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.5395137954967761, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544515776.172573, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68240767746428, + 45.61539998944145 + ] + }, + "properties": { + "timestamp": 1544513867.703248 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.40073000473637027, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544516105.128334, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.6660734429102, + 45.61237104329849 + ] + }, + "properties": { + "timestamp": 1544516105.128334 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544517782.470735, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.83328028078024, + 45.555895592688536 + ] + }, + "properties": { + "timestamp": 1544517234.006464 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544518298.717708, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.82751792436189, + 45.574001814832904 + ] + }, + "properties": { + "timestamp": 1544518298.717708 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.5683005817866493, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544521527.648049, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.66742695137913, + 45.40789201095082 + ] + }, + "properties": { + "timestamp": 1544518353.010243 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.35444352815301383, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544522527.111438, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67594669992188, + 45.372480382250686 + ] + }, + "properties": { + "timestamp": 1544522527.111438 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.40073000473637027, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544518699.896466, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.6660734429102, + 45.61237104329849 + ] + }, + "properties": { + "timestamp": 1544516105.128334 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.23435464619692414, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544519800.868333, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64182314343618, + 45.64811323594745 + ] + }, + "properties": { + "timestamp": 1544519800.868333 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544521139.263082, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.82751792436189, + 45.574001814832904 + ] + }, + "properties": { + "timestamp": 1544518298.717708 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544522532.928287, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.89521702571024, + 45.557830244263144 + ] + }, + "properties": { + "timestamp": 1544522532.928287 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.35444352815301383, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544522609.178432, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67594669992188, + 45.372480382250686 + ] + }, + "properties": { + "timestamp": 1544522527.111438 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.2390659508492765, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544523070.330492, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.65954861789315, + 45.384392927209326 + ] + }, + "properties": { + "timestamp": 1544523070.330492 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.23435464619692414, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544522531.937509, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64182314343618, + 45.64811323594745 + ] + }, + "properties": { + "timestamp": 1544519800.868333 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.11628428784405657, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544523895.289194, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.61094063801208, + 45.6920929799008 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544524929.072807, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.89521702571024, + 45.557830244263144 + ] + }, + "properties": { + "timestamp": 1544522532.928287 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544526326.262263, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.84460425889422, + 45.52227949382284 + ] + }, + "properties": { + "timestamp": 1544526326.262263 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.2390659508492765, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544524979.371422, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.65954861789315, + 45.384392927209326 + ] + }, + "properties": { + "timestamp": 1544523070.330492 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.15168197930174143, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544525370.342137, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67470279243341, + 45.393563529325434 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.11628428784405657, + "event_type": "unavailable", + "event_type_reason": "low_battery", + "event_time": 1544523895.289194, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.61094063801208, + 45.6920929799008 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544526840.204412, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.84460425889422, + 45.52227949382284 + ] + }, + "properties": { + "timestamp": 1544526326.262263 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544529061.564238, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.93448732972134, + 45.47327039995191 + ] + }, + "properties": { + "timestamp": 1544529061.564238 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 1.0, + "event_time": 1544523895.289194, + "event_type": "available", + "event_type_reason": "maintenance_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.5839543781992, + 45.41886094957372 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.15168197930174143, + "event_type": "unavailable", + "event_type_reason": "low_battery", + "event_time": 1544525370.342137, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67470279243341, + 45.393563529325434 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544530632.851557, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.93448732972134, + 45.47327039995191 + ] + }, + "properties": { + "timestamp": 1544529061.564238 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544531757.224281, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.91989130184035, + 45.512355211025046 + ] + }, + "properties": { + "timestamp": 1544531757.224281 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544531884.296473, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.91989130184035, + 45.512355211025046 + ] + }, + "properties": { + "timestamp": 1544531757.224281 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544532608.997121, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95048486816896, + 45.49757769346626 + ] + }, + "properties": { + "timestamp": 1544532608.997121 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.9528126761931214, + "event_time": 1544524911.79314, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.5839543781992, + 45.41886094957372 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.6848731548685033, + "event_time": 1544525494.535635, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.60194046954449, + 45.40215663796214 + ] + }, + "properties": { + "timestamp": 1544525494.535635 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544533183.582276, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95048486816896, + 45.49757769346626 + ] + }, + "properties": { + "timestamp": 1544532608.997121 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544533352.477051, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95516638619833, + 45.502682996786234 + ] + }, + "properties": { + "timestamp": 1544533352.477051 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.6848731548685033, + "event_time": 1544525959.436043, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.60194046954449, + 45.40215663796214 + ] + }, + "properties": { + "timestamp": 1544525494.535635 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.47032715669997205, + "event_time": 1544526631.175288, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.58401644545066, + 45.38156100599546 + ] + }, + "properties": { + "timestamp": 1544526631.175288 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544535300.206669, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95516638619833, + 45.502682996786234 + ] + }, + "properties": { + "timestamp": 1544533352.477051 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544536372.807792, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.9671041982852, + 45.46506117576508 + ] + }, + "properties": { + "timestamp": 1544536372.807792 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.47032715669997205, + "event_time": 1544526915.35278, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.58401644545066, + 45.38156100599546 + ] + }, + "properties": { + "timestamp": 1544526631.175288 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.300655567676586, + "event_time": 1544527725.95393, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.59220212065318, + 45.4101153957808 + ] + }, + "properties": { + "timestamp": 1544527725.95393 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544537985.891405, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.9671041982852, + 45.46506117576508 + ] + }, + "properties": { + "timestamp": 1544536372.807792 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544538978.147272, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95307878341613, + 45.43079123703573 + ] + }, + "properties": { + "timestamp": 1544538978.147272 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.300655567676586, + "event_time": 1544530232.090513, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.59220212065318, + 45.4101153957808 + ] + }, + "properties": { + "timestamp": 1544527725.95393 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.1909912478703445, + "event_time": 1544530798.688042, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.57206109636685, + 45.42476572267878 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544540732.024549, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95307878341613, + 45.43079123703573 + ] + }, + "properties": { + "timestamp": 1544538978.147272 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544541016.157797, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.96630204136562, + 45.43504940676683 + ] + }, + "properties": { + "timestamp": 1544541016.157797 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.1909912478703445, + "event_time": 1544530798.688042, + "event_type": "unavailable", + "event_type_reason": "low_battery", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.57206109636685, + 45.42476572267878 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 1.0, + "event_time": 1544530798.688042, + "event_type": "available", + "event_type_reason": "maintenance_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74501544569743, + 45.584081389720325 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544543333.322718, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.96630204136562, + 45.43504940676683 + ] + }, + "properties": { + "timestamp": 1544541016.157797 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544543992.008955, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.97542650695328, + 45.45783572874558 + ] + }, + "properties": { + "timestamp": 1544543992.008955 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 1.0, + "event_time": 1544534381.467905, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74501544569743, + 45.584081389720325 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.6987625842572468, + "event_time": 1544535080.861136, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74242275595007, + 45.55901583998692 + ] + }, + "properties": { + "timestamp": 1544535080.861136 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 1.0, + "event_time": 1544525370.342137, + "event_type": "available", + "event_type_reason": "maintenance_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.52473802698886, + 45.5107476683009 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_time": 1544544926.916215, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.97542650695328, + 45.45783572874558 + ] + }, + "properties": { + "timestamp": 1544543992.008955 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_time": 1544545518.131431, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -123.00519791899471, + 45.453937990334 + ] + }, + "properties": { + "timestamp": 1544545518.131431 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.6987625842572468, + "event_time": 1544536658.790359, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74242275595007, + 45.55901583998692 + ] + }, + "properties": { + "timestamp": 1544535080.861136 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.5427396714515282, + "event_time": 1544536925.86206, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.72926028591347, + 45.56169249183718 + ] + }, + "properties": { + "timestamp": 1544536925.86206 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 1.0, + "event_time": 1544528868.478639, + "event_type": "reserved", + "event_type_reason": "user_pick_up", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.52473802698886, + 45.5107476683009 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.6003953385105786, + "event_time": 1544530222.754754, + "event_type": "available", + "event_type_reason": "user_drop_off", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.46367196310324, + 45.53393270847247 + ] + }, + "properties": { + "timestamp": 1544530222.754754 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "event_type": "removed", + "event_type_reason": "service_end", + "event_time": 1544562371.205162, + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -123.00519791899471, + 45.453937990334 + ] + }, + "properties": { + "timestamp": 1544562371.205162 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "battery_pct": 0.5427396714515282, + "event_time": 1544563729.440334, + "event_type": "removed", + "event_type_reason": "service_end", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.72926028591347, + 45.56169249183718 + ] + }, + "properties": { + "timestamp": 1544563729.440334 + } + } + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "battery_pct": 0.6003953385105786, + "event_time": 1544562102.389788, + "event_type": "removed", + "event_type_reason": "service_end", + "event_location": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.46367196310324, + 45.53393270847247 + ] + }, + "properties": { + "timestamp": 1544562102.389788 + } + } + } +] diff --git a/mds/tests/fixtures/mds_tiny_0.2.0_trips_only.json b/mds/tests/fixtures/mds_tiny_0.2.0_trips_only.json new file mode 100644 index 0000000..daef3b3 --- /dev/null +++ b/mds/tests/fixtures/mds_tiny_0.2.0_trips_only.json @@ -0,0 +1,1505 @@ +[ + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 12, + "trip_id": "cedaca1c-5e12-41ce-9635-c0afe6121aad", + "trip_duration": 471, + "trip_distance": 1887, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67997436226106, + 45.391839383840626 + ] + }, + "properties": { + "timestamp": 1544507545.035672 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68127293484119, + 45.40876863084977 + ] + }, + "properties": { + "timestamp": 1544514674.345742 + } + } + ] + }, + "start_time": 1544514202.526848, + "end_time": 1544514674.345742 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 4, + "trip_id": "801e3dbc-d47b-4e20-9862-eca76f379526", + "trip_duration": 571, + "trip_distance": 2285, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64865572278676, + 45.62786565607143 + ] + }, + "properties": { + "timestamp": 1544508752.886242 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67795737547921, + 45.62917352136674 + ] + }, + "properties": { + "timestamp": 1544513154.15886 + } + } + ] + }, + "start_time": 1544512582.743511, + "end_time": 1544513154.15886, + "parking_verification_url": "https://fake_operator.co/haznu57.jpg", + "actual_cost": 261 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 2, + "trip_id": "5c09b6f7-6f14-4a4e-87df-02227c937fa7", + "trip_duration": 736, + "trip_distance": 2944, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.75271238600399, + 45.533356175822696 + ] + }, + "properties": { + "timestamp": 1544506620.654661 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.7769407303836, + 45.553643341722626 + ] + }, + "properties": { + "timestamp": 1544513401.253133 + } + } + ] + }, + "start_time": 1544512665.207284, + "end_time": 1544513401.253133, + "parking_verification_url": "https://fake_operator.co/0axkyv3.jpg", + "standard_cost": 265, + "actual_cost": 275 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 7, + "trip_id": "67e60a31-e9df-422c-a513-a131bfa132e7", + "trip_duration": 392, + "trip_distance": 1571, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67795737547921, + 45.62917352136674 + ] + }, + "properties": { + "timestamp": 1544513154.15886 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68240767746428, + 45.61539998944145 + ] + }, + "properties": { + "timestamp": 1544513867.703248 + } + } + ] + }, + "start_time": 1544513474.724775, + "end_time": 1544513867.703248, + "parking_verification_url": "https://fake_operator.co/00lw84b.jpg", + "standard_cost": 175 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 6, + "trip_id": "e5f40e23-ebb6-4494-9ea6-581454019818", + "trip_duration": 1099, + "trip_distance": 4398, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.7769407303836, + 45.553643341722626 + ] + }, + "properties": { + "timestamp": 1544513401.253133 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.83328028078024, + 45.555895592688536 + ] + }, + "properties": { + "timestamp": 1544517234.006464 + } + } + ] + }, + "start_time": 1544516134.323204, + "end_time": 1544517234.006464 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 3, + "trip_id": "0804b38d-35c6-46e6-b75e-e46e6eaaea7e", + "trip_duration": 271, + "trip_distance": 1086, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68127293484119, + 45.40876863084977 + ] + }, + "properties": { + "timestamp": 1544514674.345742 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.66742695137913, + 45.40789201095082 + ] + }, + "properties": { + "timestamp": 1544518353.010243 + } + } + ] + }, + "start_time": 1544518081.39188, + "end_time": 1544518353.010243, + "actual_cost": 174 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 0, + "trip_id": "eca11d06-8fab-41f9-b80b-cfb3c03d8aee", + "trip_duration": 328, + "trip_distance": 1315, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.68240767746428, + 45.61539998944145 + ] + }, + "properties": { + "timestamp": 1544513867.703248 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.6660734429102, + 45.61237104329849 + ] + }, + "properties": { + "timestamp": 1544516105.128334 + } + } + ] + }, + "start_time": 1544515776.172573, + "end_time": 1544516105.128334, + "parking_verification_url": "https://fake_operator.co/eqlgrv3.jpg", + "standard_cost": 160 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 4, + "trip_id": "a0a9afef-0f4a-412c-bbdc-879e2a7e5842", + "trip_duration": 516, + "trip_distance": 2064, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.83328028078024, + 45.555895592688536 + ] + }, + "properties": { + "timestamp": 1544517234.006464 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.82751792436189, + 45.574001814832904 + ] + }, + "properties": { + "timestamp": 1544518298.717708 + } + } + ] + }, + "start_time": 1544517782.470735, + "end_time": 1544518298.717708 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 8, + "trip_id": "5a036f40-72a0-4479-a6d3-8ea5e37cb404", + "trip_duration": 999, + "trip_distance": 3997, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.66742695137913, + 45.40789201095082 + ] + }, + "properties": { + "timestamp": 1544518353.010243 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67594669992188, + 45.372480382250686 + ] + }, + "properties": { + "timestamp": 1544522527.111438 + } + } + ] + }, + "start_time": 1544521527.648049, + "end_time": 1544522527.111438, + "standard_cost": 325, + "actual_cost": 320 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 1, + "trip_id": "6e113c47-13f7-4f41-8564-cb471f95307c", + "trip_duration": 1100, + "trip_distance": 4403, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.6660734429102, + 45.61237104329849 + ] + }, + "properties": { + "timestamp": 1544516105.128334 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64182314343618, + 45.64811323594745 + ] + }, + "properties": { + "timestamp": 1544519800.868333 + } + } + ] + }, + "start_time": 1544518699.896466, + "end_time": 1544519800.868333, + "parking_verification_url": "https://fake_operator.co/kxmo9pz.jpg" + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 15, + "trip_id": "c6f0f967-21c8-4164-87b0-9d6c707c82da", + "trip_duration": 1393, + "trip_distance": 5574, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.82751792436189, + 45.574001814832904 + ] + }, + "properties": { + "timestamp": 1544518298.717708 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.89521702571024, + 45.557830244263144 + ] + }, + "properties": { + "timestamp": 1544522532.928287 + } + } + ] + }, + "start_time": 1544521139.263082, + "end_time": 1544522532.928287, + "parking_verification_url": "https://fake_operator.co/3p1rzv5.jpg", + "actual_cost": 515 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 0, + "trip_id": "17068a42-de69-4b93-a43e-fe9f964285f2", + "trip_duration": 461, + "trip_distance": 1844, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67594669992188, + 45.372480382250686 + ] + }, + "properties": { + "timestamp": 1544522527.111438 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.65954861789315, + 45.384392927209326 + ] + }, + "properties": { + "timestamp": 1544523070.330492 + } + } + ] + }, + "start_time": 1544522609.178432, + "end_time": 1544523070.330492, + "actual_cost": 235 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 9, + "trip_id": "ce5b9eb9-7949-487f-a1e1-4115be5e4a92", + "trip_duration": 1363, + "trip_distance": 5453, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.64182314343618, + 45.64811323594745 + ] + }, + "properties": { + "timestamp": 1544519800.868333 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.61094063801208, + 45.6920929799008 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + } + ] + }, + "start_time": 1544522531.937509, + "end_time": 1544523895.289194, + "standard_cost": 415 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 1, + "trip_id": "4eee4310-b6f3-4117-a651-2daf21bea701", + "trip_duration": 1397, + "trip_distance": 5588, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.89521702571024, + 45.557830244263144 + ] + }, + "properties": { + "timestamp": 1544522532.928287 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.84460425889422, + 45.52227949382284 + ] + }, + "properties": { + "timestamp": 1544526326.262263 + } + } + ] + }, + "start_time": 1544524929.072807, + "end_time": 1544526326.262263, + "standard_cost": 430 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 3, + "trip_id": "cd3eac05-6cac-45c5-8170-5e31e3c8c6ac", + "trip_duration": 390, + "trip_distance": 1563, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.65954861789315, + 45.384392927209326 + ] + }, + "properties": { + "timestamp": 1544523070.330492 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.67470279243341, + 45.393563529325434 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + } + ] + }, + "start_time": 1544524979.371422, + "end_time": 1544525370.342137, + "standard_cost": 175 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 3, + "trip_id": "f551ad4b-d548-4cea-811f-47ec28c0b26e", + "trip_duration": 2221, + "trip_distance": 8885, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.84460425889422, + 45.52227949382284 + ] + }, + "properties": { + "timestamp": 1544526326.262263 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.93448732972134, + 45.47327039995191 + ] + }, + "properties": { + "timestamp": 1544529061.564238 + } + } + ] + }, + "start_time": 1544526840.204412, + "end_time": 1544529061.564238, + "standard_cost": 640 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 10, + "trip_id": "b530ad04-848f-41b1-88a1-741a44a8849c", + "trip_duration": 1124, + "trip_distance": 4497, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.93448732972134, + 45.47327039995191 + ] + }, + "properties": { + "timestamp": 1544529061.564238 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.91989130184035, + 45.512355211025046 + ] + }, + "properties": { + "timestamp": 1544531757.224281 + } + } + ] + }, + "start_time": 1544530632.851557, + "end_time": 1544531757.224281 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 6, + "trip_id": "685fb95a-8354-4597-8e71-035c49789b7c", + "trip_duration": 724, + "trip_distance": 2898, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.91989130184035, + 45.512355211025046 + ] + }, + "properties": { + "timestamp": 1544531757.224281 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95048486816896, + 45.49757769346626 + ] + }, + "properties": { + "timestamp": 1544532608.997121 + } + } + ] + }, + "start_time": 1544531884.296473, + "end_time": 1544532608.997121, + "parking_verification_url": "https://fake_operator.co/sl0ceth.jpg", + "standard_cost": 265, + "actual_cost": 308 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 17, + "trip_id": "303919d8-b8f3-4171-a2fc-f1b88d3909e2", + "trip_duration": 582, + "trip_distance": 2330, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.5839543781992, + 45.41886094957372 + ] + }, + "properties": { + "timestamp": 1544523895.289194 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.60194046954449, + 45.40215663796214 + ] + }, + "properties": { + "timestamp": 1544525494.535635 + } + } + ] + }, + "start_time": 1544524911.79314, + "end_time": 1544525494.535635, + "parking_verification_url": "https://fake_operator.co/7b5m5ok.jpg" + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 9, + "trip_id": "d1c1eb56-2810-4c9f-a831-e6e64238b32f", + "trip_duration": 168, + "trip_distance": 675, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95048486816896, + 45.49757769346626 + ] + }, + "properties": { + "timestamp": 1544532608.997121 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95516638619833, + 45.502682996786234 + ] + }, + "properties": { + "timestamp": 1544533352.477051 + } + } + ] + }, + "start_time": 1544533183.582276, + "end_time": 1544533352.477051, + "standard_cost": 115, + "actual_cost": 137 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 11, + "trip_id": "6191234b-f643-42a6-a4b1-28d8a7c3f9bc", + "trip_duration": 671, + "trip_distance": 2686, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.60194046954449, + 45.40215663796214 + ] + }, + "properties": { + "timestamp": 1544525494.535635 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.58401644545066, + 45.38156100599546 + ] + }, + "properties": { + "timestamp": 1544526631.175288 + } + } + ] + }, + "start_time": 1544525959.436043, + "end_time": 1544526631.175288, + "parking_verification_url": "https://fake_operator.co/m3hat33.jpg", + "standard_cost": 250, + "actual_cost": 281 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 4, + "trip_id": "287b44b1-3075-4bae-83b6-f842f96b6e60", + "trip_duration": 1072, + "trip_distance": 4290, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95516638619833, + 45.502682996786234 + ] + }, + "properties": { + "timestamp": 1544533352.477051 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.9671041982852, + 45.46506117576508 + ] + }, + "properties": { + "timestamp": 1544536372.807792 + } + } + ] + }, + "start_time": 1544535300.206669, + "end_time": 1544536372.807792, + "parking_verification_url": "https://fake_operator.co/cwmzwxu.jpg", + "standard_cost": 340, + "actual_cost": 374 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 5, + "trip_id": "e6ba2fa8-ded5-438f-9050-d0e5cd430c67", + "trip_duration": 810, + "trip_distance": 3242, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.58401644545066, + 45.38156100599546 + ] + }, + "properties": { + "timestamp": 1544526631.175288 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.59220212065318, + 45.4101153957808 + ] + }, + "properties": { + "timestamp": 1544527725.95393 + } + } + ] + }, + "start_time": 1544526915.35278, + "end_time": 1544527725.95393, + "parking_verification_url": "https://fake_operator.co/3u2l74y.jpg", + "actual_cost": 262 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 9, + "trip_id": "78952a58-0c4e-406f-b9be-553fd19afd17", + "trip_duration": 992, + "trip_distance": 3969, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.9671041982852, + 45.46506117576508 + ] + }, + "properties": { + "timestamp": 1544536372.807792 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95307878341613, + 45.43079123703573 + ] + }, + "properties": { + "timestamp": 1544538978.147272 + } + } + ] + }, + "start_time": 1544537985.891405, + "end_time": 1544538978.147272, + "standard_cost": 325, + "actual_cost": 341 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 7, + "trip_id": "84b9bb93-afe7-44de-8dfd-5509ef99c7e3", + "trip_duration": 566, + "trip_distance": 2266, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.59220212065318, + 45.4101153957808 + ] + }, + "properties": { + "timestamp": 1544527725.95393 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.57206109636685, + 45.42476572267878 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + } + ] + }, + "start_time": 1544530232.090513, + "end_time": 1544530798.688042, + "standard_cost": 220, + "actual_cost": 233 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 0, + "trip_id": "6c9fa7a5-0eed-4660-abdf-a34c33f9eaee", + "trip_duration": 284, + "trip_distance": 1136, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.95307878341613, + 45.43079123703573 + ] + }, + "properties": { + "timestamp": 1544538978.147272 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.96630204136562, + 45.43504940676683 + ] + }, + "properties": { + "timestamp": 1544541016.157797 + } + } + ] + }, + "start_time": 1544540732.024549, + "end_time": 1544541016.157797, + "actual_cost": 203 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 5, + "trip_id": "6bc8f10a-6102-4a75-951a-134ecd2d87c8", + "trip_duration": 658, + "trip_distance": 2634, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.96630204136562, + 45.43504940676683 + ] + }, + "properties": { + "timestamp": 1544541016.157797 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.97542650695328, + 45.45783572874558 + ] + }, + "properties": { + "timestamp": 1544543992.008955 + } + } + ] + }, + "start_time": 1544543333.322718, + "end_time": 1544543992.008955, + "standard_cost": 235, + "actual_cost": 305 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 3, + "trip_id": "78a2e9d9-8253-4190-b41d-1797e8ca96ca", + "trip_duration": 699, + "trip_distance": 2797, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74501544569743, + 45.584081389720325 + ] + }, + "properties": { + "timestamp": 1544530798.688042 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74242275595007, + 45.55901583998692 + ] + }, + "properties": { + "timestamp": 1544535080.861136 + } + } + ] + }, + "start_time": 1544534381.467905, + "end_time": 1544535080.861136, + "parking_verification_url": "https://fake_operator.co/wieg1sn.jpg", + "standard_cost": 250 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "b46288dd-4cb3-47ae-85ef-20e63cc04e11", + "vehicle_id": "W0P1DD", + "vehicle_type": "scooter", + "propulsion_type": [ + "combustion" + ], + "accuracy": 6, + "trip_id": "3c0be442-fed5-43c2-9a38-f3d6640940ea", + "trip_duration": 591, + "trip_distance": 2364, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.97542650695328, + 45.45783572874558 + ] + }, + "properties": { + "timestamp": 1544543992.008955 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -123.00519791899471, + 45.453937990334 + ] + }, + "properties": { + "timestamp": 1544545518.131431 + } + } + ] + }, + "start_time": 1544544926.916215, + "end_time": 1544545518.131431, + "parking_verification_url": "https://fake_operator.co/h1j65ll.jpg" + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "9f02e03a-399e-4024-b2d3-2088de141f22", + "vehicle_id": "VDYTFN", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric_assist" + ], + "accuracy": 5, + "trip_id": "c486a4ef-f6bd-4976-8e0b-2eb7dc20873f", + "trip_duration": 267, + "trip_distance": 1068, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.74242275595007, + 45.55901583998692 + ] + }, + "properties": { + "timestamp": 1544535080.861136 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.72926028591347, + 45.56169249183718 + ] + }, + "properties": { + "timestamp": 1544536925.86206 + } + } + ] + }, + "start_time": 1544536658.790359, + "end_time": 1544536925.86206 + }, + { + "provider_id": "6d9dbb76-8a81-4045-8866-3f2a36f8858d", + "provider_name": "fake_operator", + "device_id": "08bfc097-70b6-41f7-b9e7-238b71b49250", + "vehicle_id": "D8PMNS", + "vehicle_type": "bicycle", + "propulsion_type": [ + "electric" + ], + "accuracy": 2, + "trip_id": "23232bd8-358b-4848-9e15-c063bfc695e7", + "trip_duration": 1354, + "trip_distance": 5417, + "route": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.52473802698886, + 45.5107476683009 + ] + }, + "properties": { + "timestamp": 1544525370.342137 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.46367196310324, + 45.53393270847247 + ] + }, + "properties": { + "timestamp": 1544530222.754754 + } + } + ] + }, + "start_time": 1544528868.478639, + "end_time": 1544530222.754754, + "parking_verification_url": "https://fake_operator.co/wjjrs3q.jpg", + "actual_cost": 401 + } +] diff --git a/mds/tests/test_api.py b/mds/tests/test_api.py new file mode 100644 index 0000000..6adca8e --- /dev/null +++ b/mds/tests/test_api.py @@ -0,0 +1,111 @@ +import unittest, re, uuid, os, json +from contextlib import contextmanager +import requests_mock +from urllib3.util import parse_url + +from mds.fake.server import make_static_server_app +from mds.providers import Provider +from mds.api import ProviderClient + + +def requests_mock_with_app(app, netloc='testserver', scheme='https'): + client = app.test_client() + def get_app_response(request, response_context): + url_object = parse_url(request.url) + app_response = client.get(url_object.request_uri, base_url=f'{scheme}://{netloc}/') + response_context.status_code = app_response.status_code + response_context.headers = app_response.headers + return app_response.data + + mock = requests_mock.Mocker() + matcher = re.compile(f'^{scheme}://{netloc}/') + mock.register_uri('GET', matcher, content=get_app_response) + return mock + +@contextmanager +def mock_provider(app): + with requests_mock_with_app(app, netloc='testserver') as mock: + provider = Provider( + 'test', + uuid.uuid4(), + url='', + auth_type='Bearer', + token='', # enable simple token auth + mds_api_url='https://testserver') + yield provider + + +def get_fixture_json(filename): + filename = os.path.join(os.path.dirname(__file__), 'fixtures', filename) + with open(filename) as f: + return json.load(f) + +class APITest(unittest.TestCase): + def setUp(self): + self.empty_app = make_static_server_app( + trips=[], + status_changes=[], + version='0.2.0', + page_size=20, + ) + + self.bogus_data_app = make_static_server_app( + trips=list(range(100)), + status_changes=list(range(100)), + version='0.2.0', + page_size=20, + ) + + self.small_app = make_static_server_app( + trips=get_fixture_json('mds_tiny_0.2.0_trips_only.json'), + status_changes=get_fixture_json('mds_tiny_0.2.0_status_changes_only.json'), + version='0.2.0', + page_size=20, + next_page_shortness=1, + ) + + def _items_from_app(self, app, endpoint='trips', **kwargs): + with mock_provider(app) as provider: + client = ProviderClient(provider) + return list(client.iterate_items(endpoint, **kwargs)) + + def test_single_provider_paging_enabled(self): + # empty provider should return zero trips + trips = self._items_from_app(self.empty_app, 'trips') + self.assertEqual(len(trips), 0) + + # 100-trip provider should return all trips + trips = self._items_from_app(self.bogus_data_app, 'trips') + self.assertEqual(len(trips), 100) + + def test_single_provider_disable_paging(self): + # Turn off paging; should get just first 20 trips + trips = self._items_from_app(self.bogus_data_app, 'trips', paging=False) + self.assertEqual(len(trips), 20) + + def test_nonoverlapping_trip_query_window_misses_trips(self): + """Verify spec-compliant trip filtering on fake server + + The MDS Provider spec requires that `start_time` parameter apply to + `start_time` property and `end_time` parameter apply to the `end_time` + property. This test verifies that our fake server follows the spec, and + that the client issues request parameters correctly. + + c.f. https://github.com/CityOfLosAngeles/mobility-data-specification/blob/0.2.x/provider/README.md#trips-query-parameters + """ + def get_trips(**api_params): + return self._items_from_app(self.small_app, 'trips', **api_params) + + # this timestamp chosen so that it's during a trip from the fixture + pivot = 1544512585 + two_hours = get_trips(start_time=pivot - 3600, end_time=pivot + 3600) + hour1 = get_trips(start_time=pivot - 3600, end_time=pivot) + hour2 = get_trips(start_time=pivot, end_time=pivot + 3600) + + self.assertLess(len(hour1) + len(hour2), len(two_hours)) + + # just for good measure, this is a trip id from the fixture that we + # expect to miss. + self.assertIn('801e3dbc-d47b-4e20-9862-eca76f379526', [t['trip_id'] for t in two_hours]) + self.assertNotIn('801e3dbc-d47b-4e20-9862-eca76f379526', [t['trip_id'] for t in hour1]) + self.assertNotIn('801e3dbc-d47b-4e20-9862-eca76f379526', [t['trip_id'] for t in hour2]) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8e6a319 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ + +Fiona +geopandas +jsonschema >= 3.0.0a2 +numpy +pandas +psycopg2-binary +requests +scipy +Shapely +sqlalchemy + +flask +urllib3 +requests +requests_mock +ipdb diff --git a/setup.py b/setup.py index 1884724..a6f8101 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,6 @@ def get_version(): include_package_data=True, install_requires=[ "Fiona", - "geopandas", "jsonschema >= 3.0.0a2", "numpy", "pandas",