Compare commits

..

7 Commits

Author SHA1 Message Date
f76cd603f3 Merge pull request 'Better landmark finding' (#27) from fix/backend/moore-tags into main
All checks were successful
Build and deploy the backend to production / Build and push image (push) Successful in 1m38s
/ push-to-remote (push) Successful in 13s
Build and deploy the backend to production / Deploy to production (push) Successful in 14s
Reviewed-on: #27
2024-10-22 09:20:53 +00:00
67f748244f update deployment config
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
2024-10-15 10:30:20 +02:00
66fa55e8c3 remove boring bridges
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m40s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 14s
2024-10-14 19:10:31 +00:00
f4729a2de7 fix pipfile mismatch
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m8s
Build and release APK / Build APK (pull_request) Failing after 5m41s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 14s
2024-10-09 23:41:58 +02:00
40edd923c3 remove geopy dependency
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Failing after 53s
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
Build and release APK / Build APK (pull_request) Failing after 6m44s
2024-10-09 16:12:41 +02:00
6ad749eeed fix? bug where landmarks are returned as none 2024-10-09 16:10:40 +02:00
c20ebf3d63 add more tags and filter more restrictively
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m57s
Build and release APK / Build APK (pull_request) Failing after 5m40s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 19s
2024-10-01 16:10:52 +02:00
12 changed files with 1201 additions and 1137 deletions

View File

@ -9,9 +9,9 @@ name = "pypi"
numpy = "*" numpy = "*"
fastapi = "*" fastapi = "*"
pydantic = "*" pydantic = "*"
geopy = "*"
shapely = "*" shapely = "*"
scipy = "*" scipy = "*"
osmpythontools = "*" osmpythontools = "*"
pywikibot = "*" pywikibot = "*"
pymemcache = "*" pymemcache = "*"
fastapi-cli = "*"

2208
backend/Pipfile.lock generated

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit 8927f278f32bf0eca169ce4b13fbde8a4ed57274 Subproject commit 718df09e88b63c9524c882ccbb8247ca1448d3ff

View File

@ -16,7 +16,7 @@ OSM_CACHE_DIR = Path(cache_dir_string)
import logging import logging
# if we are in a debug session, set verbose and rich logging # if we are in a debug session, set verbose and rich logging
if os.getenv('DEBUG', False): if os.getenv('DEBUG', "false") == "true":
from rich.logging import RichHandler from rich.logging import RichHandler
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,

View File

@ -1,3 +1,6 @@
# Tags were picked mostly arbitrarily, based on the OSM wiki and the OSM tags page.
# See https://taginfo.openstreetmap.org for more inspiration.
nature: nature:
leisure: park leisure: park
geological: '' geological: ''
@ -11,7 +14,24 @@ nature:
- alpine_hut - alpine_hut
- viewpoint - viewpoint
- zoo - zoo
waterway: waterfall - resort
- picnic_site
water:
- pond
- lake
- river
- basin
- stream
- lagoon
- rapids
waterway:
- waterfall
- river
- canal
- dam
- dock
- boatyard
shopping: shopping:
shop: shop:
@ -23,10 +43,48 @@ sightseeing:
- museum - museum
- attraction - attraction
- gallery - gallery
- artwork
- aquarium
historic: '' historic: ''
amenity: amenity:
- planetarium - planetarium
- place_of_worship - place_of_worship
- fountain - fountain
- townhall
water: water:
- reflecting_pool - reflecting_pool
bridge:
- aqueduct
- viaduct
- boardwalk
- cantilever
- abandoned
building:
- church
- chapel
- mosque
- synagogue
- ruins
- temple
- government
- cathedral
- castle
- museum
# to be used later on
restauration:
shop:
- coffee
- bakery
- restaurant
- pastry
amenity:
- restaurant
- cafe
- ice_cream
- food_court
- biergarten

View File

@ -21,8 +21,8 @@ if constants.MEMCACHED_HOST_PATH is None:
else: else:
client = Client( client = Client(
constants.MEMCACHED_HOST_PATH, constants.MEMCACHED_HOST_PATH,
timeout=1, timeout = 1,
allow_unicode_keys=True, allow_unicode_keys = True,
encoding='utf-8', encoding = 'utf-8',
serde=serde.pickle_serde serde = serde.pickle_serde
) )

View File

@ -5,7 +5,7 @@ from uuid import uuid4
# Output to frontend # Output to frontend
class Landmark(BaseModel) : class Landmark(BaseModel) :
# Properties of the landmark # Properties of the landmark
name : str name : str
type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish'] type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
@ -22,22 +22,22 @@ class Landmark(BaseModel) :
# Unique ID of a given landmark # Unique ID of a given landmark
uuid: str = Field(default_factory=uuid4) uuid: str = Field(default_factory=uuid4)
# Additional properties depending on specific tour # Additional properties depending on specific tour
must_do : Optional[bool] = False must_do : Optional[bool] = False
must_avoid : Optional[bool] = False must_avoid : Optional[bool] = False
is_secondary : Optional[bool] = False # TODO future is_secondary : Optional[bool] = False # TODO future
time_to_reach_next : Optional[int] = 0 time_to_reach_next : Optional[int] = 0
next_uuid : Optional[str] = None next_uuid : Optional[str] = None
def __str__(self) -> str: def __str__(self) -> str:
time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else "" time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
is_secondary_str = f", secondary" if self.is_secondary else "" is_secondary_str = f", secondary" if self.is_secondary else ""
type_str = '(' + self.type + ')' type_str = '(' + self.type + ')'
if self.type in ["start", "finish", "nature", "shopping"] : type_str += '\t ' if self.type in ["start", "finish", "nature", "shopping"] : type_str += '\t '
return f'Landmark{type_str}: [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}{is_secondary_str}]' return f'Landmark{type_str}: [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}{is_secondary_str}]'
def distance(self, value: 'Landmark') -> float: def distance(self, value: 'Landmark') -> float:
return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2 return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2

View File

@ -22,7 +22,8 @@ class Trip(BaseModel):
# Store the trip in the cache # Store the trip in the cache
cache_client.set(f"trip_{trip.uuid}", trip) cache_client.set(f"trip_{trip.uuid}", trip)
cache_client.set_many({f"landmark_{landmark.uuid}": landmark for landmark in landmarks}, expire=3600) # make sure to await the result (noreply=False). Otherwise the cache might not be inplace when the trip is actually requested
cache_client.set_many({f"landmark_{landmark.uuid}": landmark for landmark in landmarks}, expire=3600, noreply=False)
# is equivalent to: # is equivalent to:
# for landmark in landmarks: # for landmark in landmarks:
# cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600) # cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600)

View File

@ -1,5 +1,5 @@
import yaml import yaml
from geopy.distance import geodesic from math import sin, cos, sqrt, atan2, radians
import constants import constants
@ -8,6 +8,7 @@ with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
DETOUR_FACTOR = parameters['detour_factor'] DETOUR_FACTOR = parameters['detour_factor']
AVERAGE_WALKING_SPEED = parameters['average_walking_speed'] AVERAGE_WALKING_SPEED = parameters['average_walking_speed']
EARTH_RADIUS_KM = 6373
def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int: def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int:
""" """
@ -22,16 +23,28 @@ def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int:
""" """
# Compute the straight-line distance in km if p1 == p2:
if p1 == p2 :
return 0 return 0
else: else:
dist = geodesic(p1, p2).kilometers # Compute the distance in km along the surface of the Earth
# (assume spherical Earth)
# this is the haversine formula, stolen from stackoverflow
# in order to not use any external libraries
lat1, lon1 = radians(p1[0]), radians(p1[1])
lat2, lon2 = radians(p2[0]), radians(p2[1])
# Consider the detour factor for average cityto deterline walking distance (in km) dlon = lon2 - lon1
walk_dist = dist*DETOUR_FACTOR dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = EARTH_RADIUS_KM * c
# Consider the detour factor for average an average city
walk_distance = distance * DETOUR_FACTOR
# Time to walk this distance (in minutes) # Time to walk this distance (in minutes)
walk_time = walk_dist/AVERAGE_WALKING_SPEED*60 walk_time = walk_distance / AVERAGE_WALKING_SPEED * 60
return round(walk_time) return round(walk_time)

View File

@ -10,7 +10,8 @@ from structs.landmark import Landmark
from .take_most_important import take_most_important from .take_most_important import take_most_important
import constants import constants
# silence the overpass logger
logging.getLogger('OSMPythonTools').setLevel(level=logging.CRITICAL)
class LandmarkManager: class LandmarkManager:
@ -206,11 +207,15 @@ class LandmarkManager:
query = overpassQueryBuilder( query = overpassQueryBuilder(
bbox = bbox, bbox = bbox,
elementType = ['way', 'relation'], elementType = ['way', 'relation'],
# selector can in principle be a list already,
# but it generates the intersection of the queries
# we want the union
selector = sel, selector = sel,
# conditions = [], conditions = ['count_tags()>5'],
includeCenter = True, includeCenter = True,
out = 'body' out = 'body'
) )
self.logger.debug(f"Query: {query}")
try: try:
result = self.overpass.query(query) result = self.overpass.query(query)
@ -336,7 +341,7 @@ def dict_to_selector_list(d: dict) -> list:
for key, value in d.items(): for key, value in d.items():
if type(value) == list: if type(value) == list:
val = '|'.join(value) val = '|'.join(value)
return_list.append(f'{key}~"{val}"') return_list.append(f'{key}~"^({val})$"')
elif type(value) == str and len(value) == 0: elif type(value) == str and len(value) == 0:
return_list.append(f'{key}') return_list.append(f'{key}')
else: else:

View File

@ -3,7 +3,6 @@ import numpy as np
from scipy.optimize import linprog from scipy.optimize import linprog
from collections import defaultdict, deque from collections import defaultdict, deque
from geopy.distance import geodesic
from structs.landmark import Landmark from structs.landmark import Landmark
from .get_time_separation import get_time from .get_time_separation import get_time

View File

@ -42,7 +42,7 @@ jobs:
- name: Load secrets from github - name: Load secrets from github
run: | run: |
echo "${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}" | base64 -d > secrets.properties echo "${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}" | base64 -d > secrets.properties
echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}" | base64 -d > fastlane/google-key.json echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}" | base64 -d > google-key.json
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
working-directory: android working-directory: android