12 Commits

Author SHA1 Message Date
6f1a019d4f fixes for fastlane and gitea actions
All checks were successful
Build and release debug APK / Build APK (pull_request) Successful in 7m36s
Build and deploy the backend to production / Build and push image (push) Successful in 1m44s
/ push-to-remote (push) Successful in 12s
Build and deploy the backend to production / Deploy to production (push) Successful in 15s
2024-10-22 15:11:38 +02:00
e6ccb7078b Merge pull request 'also upload aab' (#29) from fix/frontend/fastlane-config into main
Some checks are pending
Build and deploy the backend to production / Deploy to production (push) Blocked by required conditions
Build and deploy the backend to production / Build and push image (push) Waiting to run
/ push-to-remote (push) Successful in 12s
Reviewed-on: #29
2024-10-22 12:51:22 +00:00
84839c5a02 also upload aab
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
2024-10-22 14:50:59 +02:00
9850e949c3 Merge pull request 'remove unneeded screenshots' (#28) from frontend/fix/remove-screenshots into main
Some checks failed
Build and deploy the backend to production / Build and push image (push) Has been cancelled
Build and deploy the backend to production / Deploy to production (push) Has been cancelled
/ push-to-remote (push) Successful in 13s
Reviewed-on: #28
2024-10-22 09:54:44 +00:00
5fc25a3c39 remove unneeded screenshots
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
2024-10-22 11:52:59 +02:00
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
28 changed files with 1219 additions and 1140 deletions

View File

@@ -6,7 +6,7 @@ on:
- frontend/** - frontend/**
name: Build and release APK name: Build and release debug APK
jobs: jobs:
build: build:
@@ -55,7 +55,7 @@ jobs:
ls -lah android ls -lah android
working-directory: ./frontend working-directory: ./frontend
- run: flutter build apk --release --split-per-abi --build-number=${{ gitea.run_number }} - run: flutter build apk --debug --split-per-abi --build-number=${{ gitea.run_number }}
working-directory: ./frontend working-directory: ./frontend
- name: Upload APKs to artifacts - name: Upload APKs to artifacts

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

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

View File

@@ -30,14 +30,19 @@ if (flutterVersionName == null) {
def secretPropertiesFile = rootProject.file('secrets.properties') def secretPropertiesFile = rootProject.file('secrets.properties')
def fallbackPropertiesFile = rootProject.file('fallback.properties')
def secretProperties = new Properties() def secretProperties = new Properties()
if (secretPropertiesFile.exists()) { if (secretPropertiesFile.exists()) {
secretPropertiesFile.withReader('UTF-8') { reader -> secretPropertiesFile.withReader('UTF-8') { reader ->
secretProperties.load(reader) secretProperties.load(reader)
} }
} else if (fallbackPropertiesFile.exists()) {
fallbackPropertiesFile.withReader('UTF-8') { reader ->
secretProperties.load(reader)
}
} else { } else {
throw new GradleException("Secrets file secrets.properties not found") throw new GradleException("Secrets file (secrets.properties, fallback.properties) not found")
} }

View File

@@ -1 +1,3 @@
# This file mirrors the state of secrets.properties as a reference for the developer.
# And as a fallback for build.gradle
MAPS_API_KEY=Key MAPS_API_KEY=Key

View File

@@ -21,6 +21,10 @@ platform :android do
track: 'alpha', track: 'alpha',
skip_upload_apk: true, skip_upload_apk: true,
skip_upload_changelogs: true, skip_upload_changelogs: true,
aab: "../../build/app/outputs/bundle/release/app-release.aab",
# this is the default output of flutter build ... --release
# in particular this the build folder lies in the flutter root folder
# hence the relative path
) )
end end
@@ -37,6 +41,10 @@ platform :android do
track: "production", track: "production",
skip_upload_apk: true, skip_upload_apk: true,
skip_upload_changelogs: true, skip_upload_changelogs: true,
aab: "../../build/app/outputs/bundle/release/app-release.aab",
# this is the default output of flutter build ... --release
# in particular this the build folder lies in the flutter root folder
# hence the relative path
) )
end end
end end