selene-backend/api/public/public_api/endpoints/geolocation.py

135 lines
5.0 KiB
Python

"""Call this endpoint to retrieve the timezone for a given location"""
from http import HTTPStatus
from selene.api import PublicEndpoint
from selene.data.geography import CityRepository
from selene.util.log import get_selene_logger
ONE_HUNDRED_MILES = 100
_log = get_selene_logger(__name__)
class GeolocationEndpoint(PublicEndpoint):
"""Selene endpoint that will search for a geography give a city name."""
def __init__(self):
super().__init__()
self.device_id = None
self.request_geolocation = None
self.cities = None
self._city_repo = None
@property
def city_repo(self):
"""Lazy load the CityRepository."""
if self._city_repo is None:
self._city_repo = CityRepository(self.db)
return self._city_repo
def get(self):
"""Handle a HTTP GET request."""
self.request_geolocation = self.request.args["location"].lower()
response_geolocation = self._get_geolocation()
return dict(data=response_geolocation), HTTPStatus.OK
def _get_geolocation(self):
"""Try our best to find a geolocation matching the request."""
self._get_cities()
if self.cities:
selected_geolocation = self._select_geolocation_from_cities()
else:
selected_geolocation = self.city_repo.get_biggest_city_in_region(
self.request_geolocation
)
if selected_geolocation is None:
selected_geolocation = self.city_repo.get_biggest_city_in_country(
self.request_geolocation
)
if selected_geolocation is not None:
selected_geolocation.latitude = float(selected_geolocation.latitude)
selected_geolocation.longitude = float(selected_geolocation.longitude)
return selected_geolocation
def _get_cities(self):
"""Retrieve a list of cities matching the requested location.
City names can be a single word (e.g. Seattle) or multiple words
(e.g. Kansas City). Query the database for all permutations of words
in the location passed in the request. For example, a request for
"Kansas City Missouri" will pass "Kansas" and "Kansas City" and
"Kansas City Missouri"
This logic assumes that it will not find a match when a city and
region/country are included in the request. For example, a request for
"Kansas City Missouri" should only find a match for "Kansas City".
"""
possible_city_names = []
geolocation_words = self.request_geolocation.split()
for index, _ in enumerate(geolocation_words):
possible_city_name = " ".join(geolocation_words[: index + 1])
possible_city_names.append(possible_city_name)
self.cities = self.city_repo.get_geographic_location_by_city(
possible_city_names
)
def _select_geolocation_from_cities(self):
"""Select one of the cities returned by the database.
If a single match is found, select it. If multiple matches are found,
return the city with the biggest population. If multiple matches are
found and a region or country is included in the requested location,
attempt to match based on the extra criteria.
"""
selected_geolocation = None
if len(self.cities) == 1:
selected_geolocation = self.cities[0]
elif len(self.cities) > 1:
biggest_city = self.cities[0]
if biggest_city.city.lower() == self.request_geolocation:
selected_geolocation = biggest_city
else:
city_in_region = self._get_city_for_requested_region()
city_in_country = self._get_city_for_requested_country()
selected_geolocation = city_in_region or city_in_country
return selected_geolocation
def _get_city_for_requested_region(self):
"""If a region is in the request, get the city in that region.
Example:
A request for "Kansas City Missouri" should return the city of
Kansas City in the state of Missouri
"""
city_in_requested_region = None
for city in self.cities:
location_without_city = self.request_geolocation[len(city.city) :]
if city.region.lower() in location_without_city.strip():
city_in_requested_region = city
break
return city_in_requested_region
def _get_city_for_requested_country(self):
"""If a country is in the request, get the city in that country.
Examples:
A request for "Sydney Australia" should return the city of Syndey
in the country of Australia.
"""
selected_city = None
for city in self.cities:
location_without_city = self.request_geolocation[len(city.city) :]
if city.country.lower() in location_without_city.strip():
selected_city = city
break
return selected_city