Skip to content

Commit

Permalink
Merge pull request #4183 from hove-io/add_forseti_in_car_parking_spac…
Browse files Browse the repository at this point in the history
…e_availability

[jormungandr] Add connector Forseti for car parking
  • Loading branch information
kadhikari authored Dec 7, 2023
2 parents cda4eed + 3d302ad commit b005ecc
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 5 deletions.
21 changes: 21 additions & 0 deletions source/jormungandr/jormungandr/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from jormungandr.equipments import EquipmentProviderManager
from jormungandr.external_services import ExternalServiceManager
from jormungandr.parking_space_availability.bss.bss_provider_manager import BssProviderManager
from jormungandr.parking_space_availability.car.car_park_provider_manager import CarParkingProviderManager
from jormungandr.utils import (
can_connect_to_database,
make_origin_destination_key,
Expand Down Expand Up @@ -171,6 +172,7 @@ def __init__(
resp_content_limit_bytes=None,
resp_content_limit_endpoints_whitelist=None,
individual_bss_provider=[],
individual_car_parking_provider=[],
):
super(Instance, self).__init__(
name=name,
Expand Down Expand Up @@ -268,6 +270,14 @@ def __init__(
individual_bss_provider, self.get_bss_stations_services_from_db
)

# Init CAR provider manager from config from external services in bdd
if disable_database:
self.car_parking_provider_manager = CarParkingProviderManager(individual_car_parking_provider)
else:
self.car_parking_provider_manager = CarParkingProviderManager(
individual_car_parking_provider, self.get_car_parking_services_from_db
)

self.external_service_provider_manager.init_external_services()
self.instance_db = instance_db
self._ghost_words = ghost_words or []
Expand Down Expand Up @@ -335,6 +345,14 @@ def get_bss_stations_services_from_db(self):
result = models.external_services if models else []
return [res for res in result if res.navitia_service == 'bss_stations']

def get_car_parking_services_from_db(self):
"""
:return: a callable query of external services associated to the current instance in db
"""
models = self._get_models()
result = models.external_services if models else []
return [res for res in result if res.navitia_service == 'car_parkings']

@property
def autocomplete(self):
if self._autocomplete_type:
Expand Down Expand Up @@ -1015,6 +1033,9 @@ def get_all_ridesharing_services(self):
def get_all_bss_providers(self):
return self.bss_provider_manager.get_providers()

def get_all_car_parking_providers(self):
return self.car_parking_provider_manager.get_providers()

def get_autocomplete(self, requested_autocomplete):
if not requested_autocomplete:
return self.autocomplete
Expand Down
1 change: 1 addition & 0 deletions source/jormungandr/jormungandr/instance_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def register_instance(self, config):
resp_content_limit_bytes=config.get('resp_content_limit_bytes', None),
resp_content_limit_endpoints_whitelist=config.get('resp_content_limit_endpoints_whitelist', None),
individual_bss_provider=config.get('individual_bss_provider', []),
individual_car_parking_provider=config.get('individual_car_parking_provider', []),
)

self.instances[instance.name] = instance
Expand Down
4 changes: 4 additions & 0 deletions source/jormungandr/jormungandr/interfaces/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ def add_common_status(response, instance):
for bp in instance.get_all_bss_providers():
response['status']['bss_providers'].append(bp.status())

response['status']['car_parking_providers'] = []
for cpp in instance.get_all_car_parking_providers():
response['status']['car_parking_providers'].append(cpp.status())

response['status']['equipment_providers_services'] = {}
response['status']['equipment_providers_services'][
'equipment_providers_keys'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class CarParkSerializer(PbNestedSerializer):
available_electric_vehicle = jsonschema.IntField()
occupied_electric_vehicle = jsonschema.IntField()
state = jsonschema.Field(schema_type=str)
availability = jsonschema.Field(schema_type=bool, display_none=True)


class AdminSerializer(SortedGenericSerializer, PbGenericSerializer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ class BSSStationsServiceSerializer(OutsideServiceCommon):
class_ = Field(schema_type=str, label='class', attr='class')


class CarParkingServiceSerializer(OutsideServiceCommon):
id = Field(display_none=True)
url = Field(display_none=True)
class_ = Field(schema_type=str, label='class', attr='class')


class EquipmentProvidersSerializer(NullableDictSerializer):
key = Field(schema_type=str, display_none=False)
codes_types = Field(schema_type=str, many=True, display_none=True)
Expand Down Expand Up @@ -262,6 +268,7 @@ class CommonStatusSerializer(NullableDictSerializer):
street_networks = StreetNetworkSerializer(many=True, display_none=False)
ridesharing_services = RidesharingServicesSerializer(many=True, display_none=False)
bss_providers = BSSStationsServiceSerializer(many=True, display_none=False)
car_parking_providers = CarParkingServiceSerializer(many=True, display_none=False)
equipment_providers_services = EquipmentProvidersServicesSerializer(display_none=False)
external_providers_services = ExternalServiceProvidersServicesSerializer(display_none=False)
start_production_date = Field(schema_type=str, display_none=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ def support_poi(self, poi):
return True

def status(self):
# return {'network': self.network, 'operators': self.operators}
return {
'id': six.text_type(self.network),
'url': self.service_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,94 @@

from __future__ import absolute_import, print_function, unicode_literals, division
from jormungandr.parking_space_availability.abstract_provider_manager import AbstractProviderManager
from jormungandr.utils import can_connect_to_database
import logging
import datetime

POI_TYPE_ID = 'poi_type:amenity:parking'


class CarParkingProviderManager(AbstractProviderManager):
def __init__(self, car_park_providers_configurations):
def __init__(self, car_park_providers_configurations, providers_getter=None, update_interval=60):
super(CarParkingProviderManager, self).__init__()
self.car_park_providers = []
self._db_car_park_providers = {}
self._db_car_park_providers_last_update = {}
self._update_interval = update_interval
self._db_providers_getter = providers_getter
self._last_update = datetime.datetime(1970, 1, 1)
for configuration in car_park_providers_configurations:
arguments = configuration.get('args', {})
self.car_park_providers.append(self._init_class(configuration['class'], arguments))

def update_config(self):
if (
self._last_update + datetime.timedelta(seconds=self._update_interval) > datetime.datetime.utcnow()
or not self._db_providers_getter
):
return

logger = logging.getLogger(__name__)

# If database is not accessible we update the value of self._last_update and exit
if not can_connect_to_database():
logger.debug('Database is not accessible')
self._last_update = datetime.datetime.utcnow()
return

logger.debug('updating car parking providers from database')
self._last_update = datetime.datetime.utcnow()

try:
# car parking provider list from the database (external_service)
db_providers = self._db_providers_getter()
except Exception as e:
logger.exception('No access to table external_service (error: {})'.format(e))
# database is not accessible, so let's use the values already present in self.car_park_providers and
# avoid sending query to the database for another update_interval
self._last_update = datetime.datetime.utcnow()
return

if not db_providers:
logger.debug('No provider active in the database')
self._db_car_park_providers = {}
self._db_car_park_providers_last_update = {}
return

if not self._need_update(db_providers):
return

for provider in db_providers:
# it's a new car parking provider or it has been updated, we add it
if (
provider.id not in self._db_car_park_providers_last_update
or provider.last_update() > self._db_car_park_providers_last_update[provider.id]
):
self.update_provider(provider)

# remove deleted car parking providers in the database
for to_delete in set(self._db_car_park_providers.keys()) - {p.id for p in db_providers}:
del self._db_car_park_providers[to_delete]
del self._db_car_park_providers_last_update[to_delete]
logger.info('deleting par parking provider %s', to_delete)

def _need_update(self, providers):
for provider in providers:
if (
provider.id not in self._db_car_park_providers_last_update
or provider.last_update() > self._db_car_park_providers_last_update[provider.id]
):
return True
return False

def update_provider(self, provider):
logger = logging.getLogger(__name__)
try:
self._db_car_park_providers[provider.id] = self._init_class(provider.klass, provider.full_args())
self._db_car_park_providers_last_update[provider.id] = provider.last_update()
except Exception:
logger.exception('impossible to initialize car parking provider from the database')

def _handle_poi(self, item):
if 'poi_type' in item and item['poi_type']['id'] == POI_TYPE_ID:
provider = self._find_provider(item)
Expand All @@ -50,4 +126,12 @@ def _handle_poi(self, item):
return None

def _get_providers(self):
return self.car_park_providers
self.update_config()
return self.car_park_providers + list(self._db_car_park_providers.values())

def get_providers(self):
return self._get_providers()

def exist_provider(self):
self.update_config()
return any(self.get_providers())
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# coding: utf-8

# Copyright (c) 2001-2022, Hove and/or its affiliates. All rights reserved.
#
# This file is part of Navitia,
# the software to build cool stuff with public transport.
#
# Hope you'll enjoy and contribute to this project,
# powered by Hove (www.hove.com).
# Help us simplify mobility and open public transport:
# a non ending quest to the responsive locomotion way of traveling!
#
# LICENCE: This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Stay tuned using
# twitter @navitia
# channel `#navitia` on riot https://riot.im/app/#/room/#navitia:matrix.org
# https://groups.google.com/d/forum/navitia
# www.navitia.io
from __future__ import absolute_import, print_function, unicode_literals, division
import jmespath

from jormungandr.parking_space_availability.car.parking_places import ParkingPlaces
from jormungandr.parking_space_availability.car.common_car_park_provider import CommonCarParkProvider
from jormungandr.street_network.utils import crowfly_distance_between
from jormungandr.utils import Coords
import six

DEFAULT_FORSETI_FEED_PUBLISHER = None


class ForsetiProvider(CommonCarParkProvider):
"""
class managing calls to Forseti external service providing real-time car parking availability
"""

def __init__(
self, service_url, distance=50, timeout=2, feed_publisher=DEFAULT_FORSETI_FEED_PUBLISHER, **kwargs
):
self.provider_name = "FORSETI"
self.service_url = service_url
self.distance = distance
super(ForsetiProvider, self).__init__(service_url, [], [], timeout, feed_publisher, **kwargs)

def status(self):
return {
'id': six.text_type(self.provider_name),
'url': self.service_url,
'class': self.__class__.__name__,
}

def support_poi(self, poi):
return True

def get_informations(self, poi):
if not poi.get('properties', {}).get('amenity'):
return None

data = self._call_webservice(self.ws_service_template.format(self.dataset))

if data:
return self.process_data(data, poi)

def process_data(self, data, poi):
poi_coord = Coords(poi.get('coord').get('lat'), poi.get('coord').get('lon'))
for parking in data.get('parkings', []):
parking_coord = Coords(parking.get('coord').get('lat'), parking.get('coord').get('lon'))
distance = crowfly_distance_between(poi_coord, parking_coord)
if distance < self.distance:
return ParkingPlaces(availability=parking.get('availability'))
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(
available_electric_vehicle=None,
occupied_electric_vehicle=None,
state=None,
availability=None,
):
if available is not None:
self.available = available
Expand All @@ -63,7 +64,8 @@ def __init__(
self.occupied_electric_vehicle = occupied_electric_vehicle
if state is not None:
self.state = state

if availability is not None:
self.availability = availability
if not total_places and any(n is not None for n in [available, occupied, available_PRM, occupied_PRM]):
self.total_places = (available or 0) + (occupied or 0) + (available_PRM or 0) + (occupied_PRM or 0)

Expand All @@ -79,6 +81,7 @@ def __eq__(self, other):
"available_electric_vehicle",
"occupied_electric_vehicle",
"state",
"availability",
"total_places",
]:
if hasattr(other, item):
Expand Down
Loading

0 comments on commit b005ecc

Please sign in to comment.