From 49ce8527a36abded3d564f6abf60eecd149c2405 Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Sun, 30 Jun 2024 18:42:59 +0200 Subject: [PATCH 1/6] cleanup path handling for easier dockerization --- ...ld-image.yaml => backend_build-image.yaml} | 2 + backend/Dockerfile | 9 +- backend/src/constants.py | 9 ++ backend/src/landmarks_manager.py | 84 ++++++++----------- backend/src/main_example.py | 23 ----- backend/src/optimizer.py | 39 +++++---- backend/src/parameters/amenity_selectors.yaml | 26 ++++++ .../src/parameters/landmark_parameters.yaml | 6 ++ .../src/parameters/landmarks_manager.params | 8 -- backend/src/parameters/optimizer.params | 5 -- .../src/parameters/optimizer_parameters.yaml | 3 + backend/src/refiner.py | 30 ++++--- 12 files changed, 126 insertions(+), 118 deletions(-) rename .gitea/workflows/{backed_build-image.yaml => backend_build-image.yaml} (95%) create mode 100644 backend/src/constants.py delete mode 100644 backend/src/main_example.py create mode 100644 backend/src/parameters/amenity_selectors.yaml create mode 100644 backend/src/parameters/landmark_parameters.yaml delete mode 100644 backend/src/parameters/landmarks_manager.params delete mode 100644 backend/src/parameters/optimizer.params create mode 100644 backend/src/parameters/optimizer_parameters.yaml diff --git a/.gitea/workflows/backed_build-image.yaml b/.gitea/workflows/backend_build-image.yaml similarity index 95% rename from .gitea/workflows/backed_build-image.yaml rename to .gitea/workflows/backend_build-image.yaml index 5542a1f..1552c03 100644 --- a/.gitea/workflows/backed_build-image.yaml +++ b/.gitea/workflows/backend_build-image.yaml @@ -2,6 +2,8 @@ on: pull_request: branches: - main + paths: + - backend/** name: Build and push docker image diff --git a/backend/Dockerfile b/backend/Dockerfile index 79dbe76..01d58a2 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,6 +6,11 @@ COPY Pipfile Pipfile.lock . RUN pip install pipenv RUN pipenv install --deploy --system -COPY . /src +COPY src src -CMD ["pipenv", "run", "python", "/app/src/main.py"] +EXPOSE 8000 + +# Set environment variables used by the deployment. These can be overridden by the user using this image. +ENV NUM_WORKERS=1 + +CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS'] diff --git a/backend/src/constants.py b/backend/src/constants.py new file mode 100644 index 0000000..515a040 --- /dev/null +++ b/backend/src/constants.py @@ -0,0 +1,9 @@ +from pathlib import Path + + +PARAMETERS_DIR = Path('src/parameters') +AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml' +LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml' +OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' + +OSM_CACHE_DIR = Path('cache') diff --git a/backend/src/landmarks_manager.py b/backend/src/landmarks_manager.py index 618e169..39fa404 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -1,9 +1,11 @@ import math as m import json, os +import yaml from typing import List, Tuple, Optional from OSMPythonTools.overpass import Overpass, overpassQueryBuilder +import constants from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference @@ -13,34 +15,40 @@ NATURE = LandmarkType(landmark_type='nature') SHOPPING = LandmarkType(landmark_type='shopping') + # Include the json here # Create a list of all things to visit given some preferences and a city. Ready for the optimizer def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float]) : + with constants.AMENITY_SELECTORS_PATH.open('r') as f: + amenity_selectors = yaml.safe_load(f) + + with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: + # even though we don't use the parameters here, we already load them to avoid unnecessary io operations + parameters = yaml.safe_load(f) - l_sights, l_nature, l_shop = get_amenities() L = [] # List for sightseeing if preferences.sightseeing.score != 0 : - L1 = get_landmarks(l_sights, SIGHTSEEING, coordinates=coordinates) + L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, coordinates, parameters) correct_score(L1, preferences.sightseeing) L += L1 # List for nature if preferences.nature.score != 0 : - L2 = get_landmarks(l_nature, NATURE, coordinates=coordinates) + L2 = get_landmarks(amenity_selectors['nature'], NATURE, coordinates, parameters) correct_score(L2, preferences.nature) L += L2 # List for shopping if preferences.shopping.score != 0 : - L3 = get_landmarks(l_shop, SHOPPING, coordinates=coordinates) + L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, coordinates, parameters) correct_score(L3, preferences.shopping) L += L3 L = remove_duplicates(L) - return L, take_most_important(L) + return L, take_most_important(L, parameters) """def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] : @@ -69,37 +77,8 @@ def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float return remove_duplicates(L), take_most_important(L) """ # Helper function to gather the amenities list -def get_amenities() -> List[List[str]] : - - # Get the list of amenities from the files - sightseeing = get_list('/amenities/sightseeing.am') - nature = get_list('/amenities/nature.am') - shopping = get_list('/amenities/shopping.am') - - return sightseeing, nature, shopping - - -# Helper function to read a .am file and generate the corresponding list -def get_list(path: str) -> List[str] : - - with open(os.path.dirname(os.path.abspath(__file__)) + path) as f : - content = f.readlines() - - amenities = [] - for line in content : - amenities.append(line.strip('\n')) - - return amenities - - # Take the most important landmarks from the list -def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] : - - # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : - parameters = json.loads(f.read()) - N_important = parameters['N important'] - +def take_most_important(L: List[Landmark], parameters: dict, N: int = 0) -> List[Landmark]: L_copy = [] L_clean = [] scores = [0]*len(L) @@ -127,7 +106,8 @@ def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] : for i, elem in enumerate(L_copy) : scores[i] = elem.attractiveness - res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(N_important-N):] + + res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(parameters['N_important']-N):] for i, elem in enumerate(L_copy) : if i in res : @@ -220,19 +200,25 @@ def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[flo return min_lat, min_lon, max_lat, max_lon -def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: Tuple[float, float]) -> List[Landmark] : +def get_landmarks( + list_amenity: list, + landmarktype: LandmarkType, + coordinates: Tuple[float, float], + parameters: dict +) -> List[Landmark]: - # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : - parameters = json.loads(f.read()) - tag_coeff = parameters['tag coeff'] - park_coeff = parameters['park coeff'] - church_coeff = parameters['church coeff'] - radius = parameters['radius close to'] - bbox_side = parameters['city bbox side'] + + # # Read the parameters from the file + # with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : + # parameters = json.loads(f.read()) + # tag_coeff = parameters['tag coeff'] + # park_coeff = parameters['park coeff'] + # church_coeff = parameters['church coeff'] + # radius = parameters['radius close to'] + # bbox_side = parameters['city bbox side'] # Create bbox around start location - bbox = create_bbox(coordinates, bbox_side) + bbox = create_bbox(coordinates, parameters['city_bbox_side']) # Initialize some variables N = 0 @@ -269,11 +255,11 @@ def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: T # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT if amenity == "'amenity'='place_of_worship'" : - score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff) + score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff']) elif amenity == "'leisure'='park'" : - score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff) + score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff']) else : - score = count_elements_within_radius(location, radius) + n_tags*tag_coeff + score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] if score is not None : # Generate the landmark and append it to the list diff --git a/backend/src/main_example.py b/backend/src/main_example.py deleted file mode 100644 index 1a49305..0000000 --- a/backend/src/main_example.py +++ /dev/null @@ -1,23 +0,0 @@ -import fastapi -from dataclasses import dataclass - - -@dataclass -class Destination: - name: str - location: tuple - attractiveness: int - - - -d = Destination() - - - -def get_route() -> list[Destination]: - return {"route": "Hello World"} - -endpoint = ("/get_route", get_route) -end -if __name__ == "__main__": - fastapi.run() diff --git a/backend/src/optimizer.py b/backend/src/optimizer.py index b346a8a..68362c6 100644 --- a/backend/src/optimizer.py +++ b/backend/src/optimizer.py @@ -1,13 +1,13 @@ import numpy as np import json, os +import yaml from typing import List, Tuple from scipy.optimize import linprog from math import radians, sin, cos, acos -from shapely import Polygon from structs.landmarks import Landmark - +import constants # Function to print the result def print_res(L: List[Landmark], L_tot): @@ -161,10 +161,11 @@ def get_distance(p1: Tuple[float, float], p2: Tuple[float, float], detour: float # We want to maximize the sightseeing : max(c) st. A*x < b and A_eq*x = b_eq def init_ub_dist(landmarks: List[Landmark], max_steps: int): - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour = parameters['detour factor'] - speed = parameters['average walking speed'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] # Objective function coefficients. a*x1 + b*x2 + c*x3 + ... c = [] @@ -194,9 +195,9 @@ def respect_number(L:int, A_ub, b_ub): b_ub.append(1) # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - max_landmarks = parameters['max landmarks'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] A_ub = np.vstack((A_ub, ones*L)) b_ub.append(max_landmarks+1) @@ -300,13 +301,14 @@ def respect_order(N: int, A_eq, b_eq): # Computes the time to reach from each landmark to the next -def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] : +def link_list(order: List[int], landmarks: List[Landmark]) -> List[Landmark]: # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] j = 0 @@ -329,10 +331,11 @@ def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] : def link_list_simple(ordered_visit: List[Landmark])-> List[Landmark] : # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] j = 0 diff --git a/backend/src/parameters/amenity_selectors.yaml b/backend/src/parameters/amenity_selectors.yaml new file mode 100644 index 0000000..43e8220 --- /dev/null +++ b/backend/src/parameters/amenity_selectors.yaml @@ -0,0 +1,26 @@ +nature: + - "'leisure'='park'" + - "geological" + - "'natural'='geyser'" + - "'natural'='hot_spring'" + - "'natural'='arch'" + - "'natural'='volcano'" + - "'natural'='stone'" + - "'tourism'='alpine_hut'" + - "'tourism'='viewpoint'" + - "'tourism'='zoo'" + - "'waterway'='waterfall'" + +shopping: + - "'shop'='department_store'" + - "'shop'='mall'" + +sightseeing: + - "'tourism'='museum'" + - "'tourism'='attraction'" + - "'tourism'='gallery'" + - "historic" + - "'amenity'='planetarium'" + - "'amenity'='place_of_worship'" + - "'amenity'='fountain'" + - "'water'='reflecting_pool'" diff --git a/backend/src/parameters/landmark_parameters.yaml b/backend/src/parameters/landmark_parameters.yaml new file mode 100644 index 0000000..3cf1c76 --- /dev/null +++ b/backend/src/parameters/landmark_parameters.yaml @@ -0,0 +1,6 @@ +city_bbox_side: 10 +radius_close_to: 27.5 +church_coeff: 0.6 +park_coeff: 1.5 +tag_coeff: 100 +N_important: 40 diff --git a/backend/src/parameters/landmarks_manager.params b/backend/src/parameters/landmarks_manager.params deleted file mode 100644 index 942c214..0000000 --- a/backend/src/parameters/landmarks_manager.params +++ /dev/null @@ -1,8 +0,0 @@ -{ - "city bbox side" : 10, - "radius close to" : 27.5, - "church coeff" : 0.6, - "park coeff" : 1.5, - "tag coeff" : 100, - "N important" : 40 -} \ No newline at end of file diff --git a/backend/src/parameters/optimizer.params b/backend/src/parameters/optimizer.params deleted file mode 100644 index 18ca240..0000000 --- a/backend/src/parameters/optimizer.params +++ /dev/null @@ -1,5 +0,0 @@ -{ - "detour factor" : 1.4, - "average walking speed" : 4.8, - "max landmarks" : 10 -} \ No newline at end of file diff --git a/backend/src/parameters/optimizer_parameters.yaml b/backend/src/parameters/optimizer_parameters.yaml new file mode 100644 index 0000000..b6dca0e --- /dev/null +++ b/backend/src/parameters/optimizer_parameters.yaml @@ -0,0 +1,3 @@ +detour_factor: 1.4 +average_walking_speed: 4.8 +max_landmarks: 10 diff --git a/backend/src/refiner.py b/backend/src/refiner.py index 282f2f9..a292b73 100644 --- a/backend/src/refiner.py +++ b/backend/src/refiner.py @@ -1,7 +1,8 @@ from collections import defaultdict from heapq import heappop, heappush from itertools import permutations -import os, json +import os +import yaml from shapely import buffer, LineString, Point, Polygon, MultiPoint, convex_hull, concave_hull, LinearRing from typing import List, Tuple @@ -10,6 +11,7 @@ from math import pi from structs.landmarks import Landmark from landmarks_manager import take_most_important from optimizer import solve_optimization, link_list_simple, print_res, get_distance +import constants def create_corridor(landmarks: List[Landmark], width: float) : @@ -122,12 +124,12 @@ def total_path_distance(path: List[Landmark], detour, speed) -> float: def find_shortest_path_through_all_landmarks(landmarks: List[Landmark]) -> List[Landmark]: - - # Read from data - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour = parameters['detour factor'] - speed = parameters['average walking speed'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] # Step 1: Find 'start' and 'finish' landmarks start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start') @@ -174,8 +176,10 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L for landmark in all_landmarks : if is_in_area(area, landmark.location) and landmark.name not in visited_names: second_order_landmarks.append(landmark) - - return take_most_important(second_order_landmarks, len(visited_landmarks)) + + with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + return take_most_important(second_order_landmarks, parameters, len(visited_landmarks)) @@ -195,10 +199,10 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] : - # Read from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - max_landmarks = parameters['max landmarks'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] if len(base_tour)-2 >= max_landmarks : return base_tour From f9c86261cbf8935868a01751378a837aa896691a Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Sun, 7 Jul 2024 14:49:10 +0200 Subject: [PATCH 2/6] switch to osmnx --- backend/Dockerfile | 1 + backend/Pipfile | 3 +- backend/Pipfile.lock | 943 ++++++------------ backend/src/amenities/nature.am | 11 - backend/src/amenities/shopping.am | 2 - backend/src/amenities/sightseeing.am | 8 - backend/src/constants.py | 7 +- backend/src/landmarks_manager.py | 393 +++----- backend/src/optimizer.py | 1 - backend/src/parameters/amenity_selectors.yaml | 48 +- .../src/parameters/landmark_parameters.yaml | 4 +- backend/src/structs/landmarks.py | 4 +- backend/src/tester.py | 4 +- 13 files changed, 470 insertions(+), 959 deletions(-) delete mode 100644 backend/src/amenities/nature.am delete mode 100644 backend/src/amenities/shopping.am delete mode 100644 backend/src/amenities/sightseeing.am diff --git a/backend/Dockerfile b/backend/Dockerfile index 01d58a2..056c3a0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,5 +12,6 @@ EXPOSE 8000 # Set environment variables used by the deployment. These can be overridden by the user using this image. ENV NUM_WORKERS=1 +ENV OSM_CACHE_DIR=/cache CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS'] diff --git a/backend/Pipfile b/backend/Pipfile index 62e84e8..16c3768 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -7,8 +7,9 @@ name = "pypi" numpy = "*" scipy = "*" fastapi = "*" -osmpythontools = "*" pydantic = "*" shapely = "*" +osmnx = "*" +networkx = "*" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 2e5aac3..0532049 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0f88c01cde3be9a6332acec33fa0ccf13b6e122a6df8ee5cfefa52ba1e98034f" + "sha256": "937d6d3cf34dbe638ce1b4a8d8ed85b2365920264471798d81d5a49d4f6e5d2e" }, "pipfile-spec": 6, "requires": {}, @@ -30,14 +30,6 @@ "markers": "python_version >= '3.8'", "version": "==4.4.0" }, - "beautifulsoup4": { - "hashes": [ - "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", - "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" - ], - "markers": "python_full_version >= '3.6.0'", - "version": "==4.12.3" - }, "certifi": { "hashes": [ "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", @@ -46,6 +38,102 @@ "markers": "python_version >= '3.6'", "version": "==2024.6.2" }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -54,64 +142,6 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, - "contourpy": { - "hashes": [ - "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2", - "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9", - "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9", - "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4", - "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce", - "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7", - "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f", - "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922", - "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4", - "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e", - "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b", - "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619", - "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205", - "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480", - "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965", - "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c", - "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd", - "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5", - "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f", - "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc", - "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec", - "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd", - "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b", - "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9", - "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe", - "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce", - "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609", - "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8", - "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0", - "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f", - "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8", - "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b", - "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364", - "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040", - "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f", - "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083", - "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df", - "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba", - "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445", - "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da", - "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3", - "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72", - "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02", - "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985" - ], - "markers": "python_version >= '3.9'", - "version": "==1.2.1" - }, - "cycler": { - "hashes": [ - "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", - "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, "dnspython": { "hashes": [ "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", @@ -128,14 +158,6 @@ "markers": "python_version >= '3.8'", "version": "==2.2.0" }, - "exceptiongroup": { - "hashes": [ - "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", - "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" - ], - "markers": "python_version < '3.11'", - "version": "==1.2.1" - }, "fastapi": { "hashes": [ "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0", @@ -153,61 +175,13 @@ "markers": "python_version >= '3.8'", "version": "==0.0.4" }, - "fonttools": { + "geopandas": { "hashes": [ - "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d", - "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64", - "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2", - "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4", - "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6", - "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b", - "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f", - "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380", - "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e", - "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749", - "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20", - "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0", - "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4", - "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5", - "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206", - "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9", - "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac", - "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1", - "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce", - "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4", - "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12", - "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca", - "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d", - "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068", - "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796", - "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec", - "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea", - "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f", - "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005", - "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2", - "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06", - "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109", - "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002", - "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9", - "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a", - "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68", - "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6", - "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161", - "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd", - "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d", - "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee", - "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af" + "sha256:386d42c028047e2b0f09191d7859268304761c4711a247173a88891b6161f711", + "sha256:cdd3ddf3c9f978997c7f08d0c544b6f887c94af4bc2e01e3df3ae69a43510df3" ], - "markers": "python_version >= '3.8'", - "version": "==4.53.0" - }, - "geojson": { - "hashes": [ - "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac", - "sha256:68a9771827237adb8c0c71f8527509c8f5bef61733aa434cefc9c9d4f0ebe8f3" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.0" + "markers": "python_version >= '3.9'", + "version": "==1.0.0" }, "h11": { "hashes": [ @@ -290,264 +264,6 @@ "markers": "python_version >= '3.7'", "version": "==3.1.4" }, - "kiwisolver": { - "hashes": [ - "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf", - "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", - "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", - "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", - "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046", - "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", - "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", - "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71", - "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee", - "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", - "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9", - "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", - "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985", - "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea", - "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", - "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89", - "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", - "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", - "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712", - "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342", - "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", - "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958", - "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d", - "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", - "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130", - "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", - "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898", - "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b", - "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f", - "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265", - "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93", - "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929", - "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635", - "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709", - "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", - "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb", - "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a", - "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920", - "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e", - "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544", - "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", - "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390", - "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77", - "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", - "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff", - "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", - "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", - "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", - "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c", - "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", - "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", - "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", - "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc", - "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a", - "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901", - "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", - "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", - "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", - "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad", - "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", - "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29", - "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", - "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250", - "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d", - "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3", - "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54", - "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f", - "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", - "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da", - "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", - "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", - "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523", - "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", - "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205", - "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3", - "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4", - "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", - "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", - "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb", - "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced", - "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd", - "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0", - "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", - "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18", - "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", - "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", - "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333", - "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b", - "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", - "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126", - "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", - "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09", - "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", - "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", - "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7", - "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", - "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9", - "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", - "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", - "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", - "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6", - "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", - "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892", - "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f" - ], - "markers": "python_version >= '3.7'", - "version": "==1.4.5" - }, - "lxml": { - "hashes": [ - "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3", - "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a", - "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0", - "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b", - "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f", - "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6", - "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73", - "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d", - "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad", - "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b", - "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a", - "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5", - "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab", - "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316", - "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6", - "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df", - "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca", - "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264", - "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8", - "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f", - "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b", - "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3", - "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5", - "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed", - "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab", - "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5", - "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726", - "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d", - "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632", - "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706", - "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8", - "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472", - "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835", - "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf", - "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db", - "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d", - "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545", - "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9", - "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be", - "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe", - "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905", - "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438", - "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db", - "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776", - "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c", - "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed", - "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd", - "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484", - "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d", - "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6", - "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30", - "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182", - "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61", - "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425", - "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb", - "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1", - "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511", - "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e", - "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207", - "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b", - "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585", - "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56", - "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391", - "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85", - "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147", - "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18", - "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1", - "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa", - "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48", - "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3", - "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184", - "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67", - "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7", - "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34", - "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706", - "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8", - "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c", - "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115", - "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009", - "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466", - "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526", - "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d", - "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525", - "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14", - "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3", - "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0", - "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b", - "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1", - "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f", - "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf", - "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf", - "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0", - "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b", - "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff", - "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88", - "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2", - "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40", - "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716", - "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2", - "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2", - "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a", - "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734", - "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87", - "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48", - "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36", - "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b", - "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07", - "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c", - "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573", - "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001", - "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9", - "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3", - "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce", - "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3", - "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04", - "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927", - "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083", - "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d", - "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32", - "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9", - "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f", - "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2", - "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c", - "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d", - "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393", - "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8", - "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6", - "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66", - "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5", - "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97", - "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196", - "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836", - "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae", - "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297", - "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421", - "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6", - "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981", - "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30", - "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30", - "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f", - "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324", - "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b" - ], - "markers": "python_version >= '3.6'", - "version": "==5.2.2" - }, "markdown-it-py": { "hashes": [ "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", @@ -622,41 +338,6 @@ "markers": "python_version >= '3.7'", "version": "==2.1.5" }, - "matplotlib": { - "hashes": [ - "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38", - "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321", - "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db", - "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888", - "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463", - "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03", - "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56", - "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4", - "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b", - "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b", - "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85", - "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956", - "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb", - "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd", - "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7", - "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89", - "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152", - "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be", - "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e", - "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0", - "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84", - "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674", - "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382", - "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a", - "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5", - "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf", - "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a", - "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d", - "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241" - ], - "markers": "python_version >= '3.9'", - "version": "==3.9.0" - }, "mdurl": { "hashes": [ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", @@ -665,6 +346,15 @@ "markers": "python_version >= '3.7'", "version": "==0.1.2" }, + "networkx": { + "hashes": [ + "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", + "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==3.3" + }, "numpy": { "hashes": [ "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f", @@ -769,12 +459,14 @@ "markers": "python_version >= '3.8'", "version": "==3.10.5" }, - "osmpythontools": { + "osmnx": { "hashes": [ - "sha256:13ff721f760fdad5dd78b4d1461d286b78bba96ee151a7301ee8c11a0c258be9" + "sha256:22548d86d68d36edff3cf9ab76c45745cda86a4ea0b28442e107d6b42992a426", + "sha256:ac67bea77b521941af648ef641ae1d006101948d1112475c256ea23ef31b426a" ], "index": "pypi", - "version": "==0.3.5" + "markers": "python_version >= '3.8'", + "version": "==1.9.3" }, "packaging": { "hashes": [ @@ -819,174 +511,108 @@ "markers": "python_version >= '3.9'", "version": "==2.2.2" }, - "pillow": { - "hashes": [ - "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", - "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", - "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", - "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", - "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", - "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", - "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", - "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", - "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", - "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", - "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", - "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", - "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", - "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", - "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", - "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", - "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", - "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", - "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", - "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", - "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", - "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", - "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", - "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", - "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", - "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", - "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", - "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", - "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", - "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", - "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", - "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", - "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", - "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", - "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", - "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", - "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", - "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", - "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", - "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", - "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", - "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", - "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", - "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", - "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", - "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", - "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", - "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", - "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", - "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", - "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", - "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", - "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", - "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", - "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", - "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", - "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", - "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", - "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", - "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", - "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", - "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", - "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", - "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", - "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", - "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", - "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", - "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", - "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" - ], - "markers": "python_version >= '3.8'", - "version": "==10.3.0" - }, "pydantic": { "hashes": [ - "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52", - "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0" + "sha256:d970ffb9d030b710795878940bd0489842c638e7252fc4a19c3ae2f7da4d6141", + "sha256:ead4f3a1e92386a734ca1411cb25d94147cf8778ed5be6b56749047676d6364e" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.7.4" + "version": "==2.8.0" }, "pydantic-core": { "hashes": [ - "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3", - "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8", - "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8", - "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30", - "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a", - "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8", - "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d", - "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc", - "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2", - "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab", - "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077", - "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e", - "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9", - "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9", - "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef", - "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1", - "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507", - "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528", - "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558", - "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b", - "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154", - "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724", - "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695", - "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9", - "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851", - "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805", - "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a", - "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5", - "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94", - "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c", - "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d", - "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef", - "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26", - "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2", - "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c", - "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0", - "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2", - "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4", - "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d", - "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2", - "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce", - "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34", - "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f", - "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d", - "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b", - "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07", - "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312", - "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057", - "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d", - "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af", - "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb", - "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd", - "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78", - "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b", - "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223", - "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a", - "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4", - "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5", - "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23", - "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a", - "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4", - "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8", - "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d", - "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443", - "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e", - "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f", - "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e", - "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d", - "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc", - "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443", - "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be", - "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2", - "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee", - "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f", - "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae", - "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864", - "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4", - "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951", - "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc" + "sha256:03aceaf6a5adaad3bec2233edc5a7905026553916615888e53154807e404545c", + "sha256:05e83ce2f7eba29e627dd8066aa6c4c0269b2d4f889c0eba157233a353053cea", + "sha256:0b0eefc7633a04c0694340aad91fbfd1986fe1a1e0c63a22793ba40a18fcbdc8", + "sha256:0e75794883d635071cf6b4ed2a5d7a1e50672ab7a051454c76446ef1ebcdcc91", + "sha256:0f6dd3612a3b9f91f2e63924ea18a4476656c6d01843ca20a4c09e00422195af", + "sha256:116b326ac82c8b315e7348390f6d30bcfe6e688a7d3f1de50ff7bcc2042a23c2", + "sha256:16197e6f4fdecb9892ed2436e507e44f0a1aa2cff3b9306d1c879ea2f9200997", + "sha256:1c3c5b7f70dd19a6845292b0775295ea81c61540f68671ae06bfe4421b3222c2", + "sha256:1dacf660d6de692fe351e8c806e7efccf09ee5184865893afbe8e59be4920b4a", + "sha256:1def125d59a87fe451212a72ab9ed34c118ff771e5473fef4f2f95d8ede26d75", + "sha256:1e4f46189d8740561b43655263a41aac75ff0388febcb2c9ec4f1b60a0ec12f3", + "sha256:1f038156b696a1c39d763b2080aeefa87ddb4162c10aa9fabfefffc3dd8180fa", + "sha256:21d9f7e24f63fdc7118e6cc49defaab8c1d27570782f7e5256169d77498cf7c7", + "sha256:22b813baf0dbf612752d8143a2dbf8e33ccb850656b7850e009bad2e101fc377", + "sha256:22e3b1d4b1b3f6082849f9b28427ef147a5b46a6132a3dbaf9ca1baa40c88609", + "sha256:23425eccef8f2c342f78d3a238c824623836c6c874d93c726673dbf7e56c78c0", + "sha256:25c46bb2ff6084859bbcfdf4f1a63004b98e88b6d04053e8bf324e115398e9e7", + "sha256:2761f71faed820e25ec62eacba670d1b5c2709bb131a19fcdbfbb09884593e5a", + "sha256:2aec8eeea0b08fd6bc2213d8e86811a07491849fd3d79955b62d83e32fa2ad5f", + "sha256:2d06a7fa437f93782e3f32d739c3ec189f82fca74336c08255f9e20cea1ed378", + "sha256:316fe7c3fec017affd916a0c83d6f1ec697cbbbdf1124769fa73328e7907cc2e", + "sha256:344e352c96e53b4f56b53d24728217c69399b8129c16789f70236083c6ceb2ac", + "sha256:35681445dc85446fb105943d81ae7569aa7e89de80d1ca4ac3229e05c311bdb1", + "sha256:366be8e64e0cb63d87cf79b4e1765c0703dd6313c729b22e7b9e378db6b96877", + "sha256:3a7235b46c1bbe201f09b6f0f5e6c36b16bad3d0532a10493742f91fbdc8035f", + "sha256:3c05eaf6c863781eb834ab41f5963604ab92855822a2062897958089d1335dad", + "sha256:3e147fc6e27b9a487320d78515c5f29798b539179f7777018cedf51b7749e4f4", + "sha256:3f0f3a4a23717280a5ee3ac4fb1f81d6fde604c9ec5100f7f6f987716bb8c137", + "sha256:5084ec9721f82bef5ff7c4d1ee65e1626783abb585f8c0993833490b63fe1792", + "sha256:52527e8f223ba29608d999d65b204676398009725007c9336651c2ec2d93cffc", + "sha256:53b06aea7a48919a254b32107647be9128c066aaa6ee6d5d08222325f25ef175", + "sha256:58e251bb5a5998f7226dc90b0b753eeffa720bd66664eba51927c2a7a2d5f32c", + "sha256:603a843fea76a595c8f661cd4da4d2281dff1e38c4a836a928eac1a2f8fe88e4", + "sha256:616b9c2f882393d422ba11b40e72382fe975e806ad693095e9a3b67c59ea6150", + "sha256:649a764d9b0da29816889424697b2a3746963ad36d3e0968784ceed6e40c6355", + "sha256:658287a29351166510ebbe0a75c373600cc4367a3d9337b964dada8d38bcc0f4", + "sha256:6d0f52684868db7c218437d260e14d37948b094493f2646f22d3dda7229bbe3f", + "sha256:6dc85b9e10cc21d9c1055f15684f76fa4facadddcb6cd63abab702eb93c98943", + "sha256:72432fd6e868c8d0a6849869e004b8bcae233a3c56383954c228316694920b38", + "sha256:73deadd6fd8a23e2f40b412b3ac617a112143c8989a4fe265050fd91ba5c0608", + "sha256:763602504bf640b3ded3bba3f8ed8a1cc2fc6a87b8d55c1c5689f428c49c947e", + "sha256:7701df088d0b05f3460f7ba15aec81ac8b0fb5690367dfd072a6c38cf5b7fdb5", + "sha256:78d584caac52c24240ef9ecd75de64c760bbd0e20dbf6973631815e3ef16ef8b", + "sha256:7a3639011c2e8a9628466f616ed7fb413f30032b891898e10895a0a8b5857d6c", + "sha256:7b6a24d7b5893392f2b8e3b7a0031ae3b14c6c1942a4615f0d8794fdeeefb08b", + "sha256:7d4df13d1c55e84351fab51383520b84f490740a9f1fec905362aa64590b7a5d", + "sha256:7e37b6bb6e90c2b8412b06373c6978d9d81e7199a40e24a6ef480e8acdeaf918", + "sha256:8093473d7b9e908af1cef30025609afc8f5fd2a16ff07f97440fd911421e4432", + "sha256:840200827984f1c4e114008abc2f5ede362d6e11ed0b5931681884dd41852ff1", + "sha256:85770b4b37bb36ef93a6122601795231225641003e0318d23c6233c59b424279", + "sha256:879ae6bb08a063b3e1b7ac8c860096d8fd6b48dd9b2690b7f2738b8c835e744b", + "sha256:87d3df115f4a3c8c5e4d5acf067d399c6466d7e604fc9ee9acbe6f0c88a0c3cf", + "sha256:8b315685832ab9287e6124b5d74fc12dda31e6421d7f6b08525791452844bc2d", + "sha256:8e49524917b8d3c2f42cd0d2df61178e08e50f5f029f9af1f402b3ee64574392", + "sha256:978d4123ad1e605daf1ba5e01d4f235bcf7b6e340ef07e7122e8e9cfe3eb61ab", + "sha256:a0586cddbf4380e24569b8a05f234e7305717cc8323f50114dfb2051fcbce2a3", + "sha256:a272785a226869416c6b3c1b7e450506152d3844207331f02f27173562c917e0", + "sha256:a340d2bdebe819d08f605e9705ed551c3feb97e4fd71822d7147c1e4bdbb9508", + "sha256:a3f243f318bd9523277fa123b3163f4c005a3e8619d4b867064de02f287a564d", + "sha256:a4f0f71653b1c1bad0350bc0b4cc057ab87b438ff18fa6392533811ebd01439c", + "sha256:ab760f17c3e792225cdaef31ca23c0aea45c14ce80d8eff62503f86a5ab76bff", + "sha256:ac76f30d5d3454f4c28826d891fe74d25121a346c69523c9810ebba43f3b1cec", + "sha256:ad1bd2f377f56fec11d5cfd0977c30061cd19f4fa199bf138b200ec0d5e27eeb", + "sha256:b2ba34a099576234671f2e4274e5bc6813b22e28778c216d680eabd0db3f7dad", + "sha256:b2f13c3e955a087c3ec86f97661d9f72a76e221281b2262956af381224cfc243", + "sha256:b34480fd6778ab356abf1e9086a4ced95002a1e195e8d2fd182b0def9d944d11", + "sha256:b4a085bd04af7245e140d1b95619fe8abb445a3d7fdf219b3f80c940853268ef", + "sha256:b81ec2efc04fc1dbf400647d4357d64fb25543bae38d2d19787d69360aad21c9", + "sha256:b8c46a8cf53e849eea7090f331ae2202cd0f1ceb090b00f5902c423bd1e11805", + "sha256:bc7e43b4a528ffca8c9151b6a2ca34482c2fdc05e6aa24a84b7f475c896fc51d", + "sha256:c3dc8ec8b87c7ad534c75b8855168a08a7036fdb9deeeed5705ba9410721c84d", + "sha256:c4a9732a5cad764ba37f3aa873dccb41b584f69c347a57323eda0930deec8e10", + "sha256:c867230d715a3dd1d962c8d9bef0d3168994ed663e21bf748b6e3a529a129aab", + "sha256:cafde15a6f7feaec2f570646e2ffc5b73412295d29134a29067e70740ec6ee20", + "sha256:cb1ad5b4d73cde784cf64580166568074f5ccd2548d765e690546cff3d80937d", + "sha256:d08264b4460326cefacc179fc1411304d5af388a79910832835e6f641512358b", + "sha256:d42669d319db366cb567c3b444f43caa7ffb779bf9530692c6f244fc635a41eb", + "sha256:d43e7ab3b65e4dc35a7612cfff7b0fd62dce5bc11a7cd198310b57f39847fd6c", + "sha256:d5b8376a867047bf08910573deb95d3c8dfb976eb014ee24f3b5a61ccc5bee1b", + "sha256:d6f2d8b8da1f03f577243b07bbdd3412eee3d37d1f2fd71d1513cbc76a8c1239", + "sha256:d6f8c49657f3eb7720ed4c9b26624063da14937fc94d1812f1e04a2204db3e17", + "sha256:d70a8ff2d4953afb4cbe6211f17268ad29c0b47e73d3372f40e7775904bc28fc", + "sha256:d82e5ed3a05f2dcb89c6ead2fd0dbff7ac09bc02c1b4028ece2d3a3854d049ce", + "sha256:e9dcd7fb34f7bfb239b5fa420033642fff0ad676b765559c3737b91f664d4fa9", + "sha256:ed741183719a5271f97d93bbcc45ed64619fa38068aaa6e90027d1d17e30dc8d", + "sha256:ee7785938e407418795e4399b2bf5b5f3cf6cf728077a7f26973220d58d885cf", + "sha256:efbb412d55a4ffe73963fed95c09ccb83647ec63b711c4b3752be10a56f0090b", + "sha256:f8ea1d8b7df522e5ced34993c423c3bf3735c53df8b2a15688a2f03a7d678800" ], "markers": "python_version >= '3.8'", - "version": "==2.18.4" + "version": "==2.20.0" }, "pygments": { "hashes": [ @@ -996,13 +622,70 @@ "markers": "python_version >= '3.8'", "version": "==2.18.0" }, - "pyparsing": { + "pyogrio": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:019731a856a9abfe909e86f50eb13f8362f6742337caf757c54b7c8acfe75b89", + "sha256:083351b258b3e08b6c6085dac560bd321b68de5cb4a66229095da68d5f3d696b", + "sha256:13642608a1cd67797ae8b5d792b0518d8ef3eb76506c8232ab5eaa1ea1159dff", + "sha256:17420febc17651876d5140b54b24749aa751d482b5f9ef6267b8053e6e962876", + "sha256:1a495ca4fb77c69595747dd688f8f17bb7d2ea9cd86603aa71c7fc98cc8b4174", + "sha256:2829615cf58b1b24a9f96fea42abedaa1a800dd351c67374cc2f6341138608f3", + "sha256:2e98913fa183f7597c609e774820a149e9329fd2a0f8d33978252fbd00ae87e6", + "sha256:2f2ec57ab74785db9c2bf47c0a6731e5175595a13f8253f06fa84136adb310a9", + "sha256:30cbeeaedb9bced7012487e7438919aa0c7dfba18ac3d4315182b46eb3139b9d", + "sha256:3a2fcaa269031dbbc8ebd91243c6452c5d267d6df939c008ab7533413c9cf92d", + "sha256:3f964002d445521ad5b8e732a6b5ef0e2d2be7fe566768e5075c1d71398da64a", + "sha256:4a289584da6df7ca318947301fe0ba9177e7f863f63110e087c80ac5f3658de8", + "sha256:4da0b9deb380bd9a200fee13182c4f95b02b4c554c923e2e0032f32aaf1439ed", + "sha256:4e0f90a6c3771ee1f1fea857778b4b6a1b64000d851b819f435f9091b3c38c60", + "sha256:6a6fa2e8cf95b3d4a7c0fac48bce6e5037579e28d3eb33b53349d6e11f15e5a8", + "sha256:6dc94a67163218581c7df275223488ac9b31dc582ccd756da607c3338908566c", + "sha256:796e4f6a4e769b2eb6fea9a10546ea4bdee16182d1e29802b4d6349363c3c1d7", + "sha256:7fcafed24371fe6e23bcf5abebbb29269f8d79915f1dd818ac85453657ea714a", + "sha256:9440466c0211ac81f3417f274da5903f15546b486f76b2f290e74a56aaf0e737", + "sha256:959022f3ad04053f8072dc9a2ad110c46edd9e4f92352061ba835fc91df3ca96", + "sha256:d668cb10f2bf6ccd7c402f91e8b06290722dd09dbe265ae95b2c13db29ebeba0", + "sha256:e38c3c6d37cf2cc969407e4d051dcb507cfd948eb26c7b0840c4f7d7d4a71bd4", + "sha256:f47c9b6818cc0f420015b672d5dcc488530a5ee63e5ba35a184957b21ea3922a", + "sha256:f5d80eb846be4fc4e642cbedc1ed0c143e8d241653382ecc76a7620bbd2a5c3a", + "sha256:f8bf193269ea9d347ac3ddada960a59f1ab2e4a5c009be95dc70e6505346b2fc", + "sha256:fb04bd80964428491951766452f0071b0bc37c7d38c45ef02502dbd83e5d74a0" ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.1.2" + "markers": "python_version >= '3.8'", + "version": "==0.9.0" + }, + "pyproj": { + "hashes": [ + "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3", + "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746", + "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb", + "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f", + "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2", + "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a", + "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf", + "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b", + "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe", + "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae", + "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f", + "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110", + "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915", + "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8", + "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0", + "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35", + "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf", + "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132", + "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877", + "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4", + "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f", + "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e", + "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85", + "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882", + "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1", + "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f", + "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.6.1" }, "python-dateutil": { "hashes": [ @@ -1090,6 +773,14 @@ ], "version": "==6.0.1" }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, "rich": { "hashes": [ "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", @@ -1100,35 +791,35 @@ }, "scipy": { "hashes": [ - "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", - "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", - "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", - "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", - "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", - "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", - "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", - "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", - "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", - "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", - "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", - "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", - "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", - "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", - "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", - "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", - "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", - "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", - "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", - "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", - "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", - "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", - "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", - "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", - "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f" + "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0", + "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7", + "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d", + "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0", + "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20", + "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc", + "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0", + "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159", + "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6", + "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1", + "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e", + "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f", + "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484", + "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf", + "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74", + "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8", + "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14", + "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86", + "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359", + "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b", + "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4", + "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9", + "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb", + "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209", + "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.13.1" + "markers": "python_version >= '3.10'", + "version": "==1.14.0" }, "shapely": { "hashes": [ @@ -1202,14 +893,6 @@ "markers": "python_version >= '3.7'", "version": "==1.3.1" }, - "soupsieve": { - "hashes": [ - "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690", - "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7" - ], - "markers": "python_version >= '3.8'", - "version": "==2.5" - }, "starlette": { "hashes": [ "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", @@ -1326,6 +1009,14 @@ "markers": "python_version >= '3.8'", "version": "==5.10.0" }, + "urllib3": { + "hashes": [ + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.2" + }, "uvicorn": { "extras": [ "standard" @@ -1529,14 +1220,6 @@ "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" ], "version": "==12.0" - }, - "xarray": { - "hashes": [ - "sha256:0b91e0bc4dc0296947947640fe31ec6e867ce258d2f7cbc10bedf4a6d68340c7", - "sha256:721a7394e8ec3d592b2d8ebe21eed074ac077dc1bb1bd777ce00e41700b4866c" - ], - "markers": "python_version >= '3.9'", - "version": "==2024.6.0" } }, "develop": {} diff --git a/backend/src/amenities/nature.am b/backend/src/amenities/nature.am deleted file mode 100644 index dcc4061..0000000 --- a/backend/src/amenities/nature.am +++ /dev/null @@ -1,11 +0,0 @@ -'leisure'='park' -geological -'natural'='geyser' -'natural'='hot_spring' -'natural'='arch' -'natural'='volcano' -'natural'='stone' -'tourism'='alpine_hut' -'tourism'='viewpoint' -'tourism'='zoo' -'waterway'='waterfall' \ No newline at end of file diff --git a/backend/src/amenities/shopping.am b/backend/src/amenities/shopping.am deleted file mode 100644 index 14504a0..0000000 --- a/backend/src/amenities/shopping.am +++ /dev/null @@ -1,2 +0,0 @@ -'shop'='department_store' -'shop'='mall' \ No newline at end of file diff --git a/backend/src/amenities/sightseeing.am b/backend/src/amenities/sightseeing.am deleted file mode 100644 index 33bcb1a..0000000 --- a/backend/src/amenities/sightseeing.am +++ /dev/null @@ -1,8 +0,0 @@ -'tourism'='museum' -'tourism'='attraction' -'tourism'='gallery' -historic -'amenity'='planetarium' -'amenity'='place_of_worship' -'amenity'='fountain' -'water'='reflecting_pool' \ No newline at end of file diff --git a/backend/src/constants.py b/backend/src/constants.py index 515a040..c60bf84 100644 --- a/backend/src/constants.py +++ b/backend/src/constants.py @@ -1,9 +1,12 @@ from pathlib import Path - +import os PARAMETERS_DIR = Path('src/parameters') AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml' LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml' OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' -OSM_CACHE_DIR = Path('cache') + + +cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') +OSM_CACHE_DIR = Path(cache_dir_string) diff --git a/backend/src/landmarks_manager.py b/backend/src/landmarks_manager.py index 39fa404..8dcaa8c 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -1,150 +1,71 @@ -import math as m -import json, os import yaml +import os +import osmnx as ox +from shapely.geometry import Point, Polygon, LineString, MultiPolygon -from typing import List, Tuple, Optional -from OSMPythonTools.overpass import Overpass, overpassQueryBuilder - -import constants from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference +import constants SIGHTSEEING = LandmarkType(landmark_type='sightseeing') NATURE = LandmarkType(landmark_type='nature') SHOPPING = LandmarkType(landmark_type='shopping') - +ox.config(cache_folder=constants.OSM_CACHE_DIR) # Include the json here # Create a list of all things to visit given some preferences and a city. Ready for the optimizer -def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float]) : +def generate_landmarks(preferences: Preferences, center_coordinates: tuple[float, float]) : with constants.AMENITY_SELECTORS_PATH.open('r') as f: amenity_selectors = yaml.safe_load(f) with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: # even though we don't use the parameters here, we already load them to avoid unnecessary io operations parameters = yaml.safe_load(f) - + max_distance = parameters['city_bbox_side'] L = [] # List for sightseeing - if preferences.sightseeing.score != 0 : - L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, coordinates, parameters) + if preferences.sightseeing.score != 0: + score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['church_coeff']) + L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, max_distance, score_func) correct_score(L1, preferences.sightseeing) L += L1 # List for nature - if preferences.nature.score != 0 : - L2 = get_landmarks(amenity_selectors['nature'], NATURE, coordinates, parameters) + if preferences.nature.score != 0: + score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['park_coeff']) + L2 = get_landmarks(amenity_selectors['nature'], NATURE, center_coordinates, max_distance, score_func) correct_score(L2, preferences.nature) L += L2 # List for shopping - if preferences.shopping.score != 0 : - L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, coordinates, parameters) + if preferences.shopping.score != 0: + score_func = lambda loc, n_tags: count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff'] + L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, center_coordinates, max_distance, score_func) correct_score(L3, preferences.shopping) L += L3 - L = remove_duplicates(L) - - return L, take_most_important(L, parameters) + # remove duplicates + L = list(set(L)) + print(len(L)) + L_constrained = take_most_important(L, parameters['N_important']) + print(len(L_constrained)) + return L, L_constrained -"""def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] : - l_sights, l_nature, l_shop = get_amenities() - L = [] - - # List for sightseeing - if preferences.sightseeing.score != 0 : - L1 = get_landmarks(l_sights, SIGHTSEEING, city_country=city_country, coordinates=coordinates) - correct_score(L1, preferences.sightseeing) - L += L1 - - # List for nature - if preferences.nature.score != 0 : - L2 = get_landmarks(l_nature, NATURE, city_country=city_country, coordinates=coordinates) - correct_score(L2, preferences.nature) - L += L2 - - # List for shopping - if preferences.shopping.score != 0 : - L3 = get_landmarks(l_shop, SHOPPING, city_country=city_country, coordinates=coordinates) - correct_score(L3, preferences.shopping) - L += L3 - - return remove_duplicates(L), take_most_important(L) -""" -# Helper function to gather the amenities list # Take the most important landmarks from the list -def take_most_important(L: List[Landmark], parameters: dict, N: int = 0) -> List[Landmark]: - L_copy = [] - L_clean = [] - scores = [0]*len(L) - names = [] - name_id = {} +def take_most_important(landmarks: list[Landmark], n_max: int) -> list[Landmark]: - for i, elem in enumerate(L) : - if elem.name not in names : - names.append(elem.name) - name_id[elem.name] = [i] - L_copy.append(elem) - else : - name_id[elem.name] += [i] - scores = [] - for j in name_id[elem.name] : - scores.append(L[j].attractiveness) - best_id = max(range(len(scores)), key=scores.__getitem__) - t = name_id[elem.name][best_id] - if t == i : - for old in L_copy : - if old.name == elem.name : - old.attractiveness = L[t].attractiveness - - scores = [0]*len(L_copy) - for i, elem in enumerate(L_copy) : - scores[i] = elem.attractiveness - - - res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(parameters['N_important']-N):] - - for i, elem in enumerate(L_copy) : - if i in res : - L_clean.append(elem) - - return L_clean + landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True) + return landmarks_sorted[:n_max] -# Remove duplicate elements and elements with low score -def remove_duplicates(L: List[Landmark]) -> List[Landmark] : - """ - Removes duplicate landmarks based on their names from the given list. - - Parameters: - L (List[Landmark]): A list of Landmark objects. - - Returns: - List[Landmark]: A list of unique Landmark objects based on their names. - """ - - L_clean = [] - names = [] - - for landmark in L : - if landmark.name in names: - continue - - - else : - names.append(landmark.name) - L_clean.append(landmark) - - return L_clean - # Correct the score of a list of landmarks by taking into account preference settings -def correct_score(L: List[Landmark], preference: Preference) : +def correct_score(L: list[Landmark], preference: Preference) : if len(L) == 0 : return @@ -157,195 +78,111 @@ def correct_score(L: List[Landmark], preference: Preference) : # Function to count elements within a certain radius of a location -def count_elements_within_radius(coordinates: Tuple[float, float], radius: int) -> int: +def count_elements_within_radius(point: Point, radius: int) -> int: - lat = coordinates[0] - lon = coordinates[1] - - alpha = (180*radius)/(6371000*m.pi) - bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha} - - # Build the query to find elements within the radius - radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], - elementType=['node', 'way', 'relation']) - - try : - overpass = Overpass() - radius_result = overpass.query(radius_query) - return radius_result.countElements() - - except : - return None + center_coordinates = (point.x, point.y) + try: + landmarks = ox.features_from_point( + center_point = center_coordinates, + dist = radius, + tags = {'building': True} # this is a common tag to give an estimation of the number of elements in the area + ) + return len(landmarks) + except ox._errors.InsufficientResponseError: + return 0 -# Creates a bounding box around given coordinates -def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[float, float, float, float]: - - lat = coordinates[0] - lon = coordinates[1] - - # Half the side length in km (since it's a square bbox) - half_side_length_km = side_length / 2.0 - - # Convert distance to degrees - lat_diff = half_side_length_km / 111 # 1 degree latitude is approximately 111 km - lon_diff = half_side_length_km / (111 * m.cos(m.radians(lat))) # Adjust for longitude based on latitude - - # Calculate bbox - min_lat = lat - lat_diff - max_lat = lat + lat_diff - min_lon = lon - lon_diff - max_lon = lon + lon_diff - - return min_lat, min_lon, max_lat, max_lon def get_landmarks( - list_amenity: list, + amenity_selectors: list[dict], landmarktype: LandmarkType, - coordinates: Tuple[float, float], - parameters: dict -) -> List[Landmark]: + center_coordinates: tuple[float, float], + distance: int, + score_function: callable +) -> list[Landmark]: - - # # Read the parameters from the file - # with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : - # parameters = json.loads(f.read()) - # tag_coeff = parameters['tag coeff'] - # park_coeff = parameters['park coeff'] - # church_coeff = parameters['church coeff'] - # radius = parameters['radius close to'] - # bbox_side = parameters['city bbox side'] - - # Create bbox around start location - bbox = create_bbox(coordinates, parameters['city_bbox_side']) + landmarks = ox.features_from_point( + center_point = center_coordinates, + dist = distance, + tags = amenity_selectors + ) - # Initialize some variables - N = 0 - L = [] - overpass = Overpass() + # cleanup the list + # remove rows where name is None + landmarks = landmarks[landmarks['name'].notna()] + # TODO: remove rows that are part of another building - for amenity in list_amenity : - query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body') - result = overpass.query(query) - N += result.countElements() + ret_landmarks = [] + for element, description in landmarks.iterrows(): + osm_type = element[0] + osm_id = element[1] + location = description['geometry'] + n_tags = len(description['nodes']) if type(description['nodes']) == list else 1 - for elem in result.elements(): + # print(description['nodes']) + print(description['name']) + # print(location, type(location)) + if type(location) == Point: + location = location + elif type(location) == Polygon or type(location) == MultiPolygon: + location = location.centroid + elif type(location) == LineString: + location = location.interpolate(location.length/2) - name = elem.tag('name') # Add name - location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) + score = score_function(location, n_tags) + print(score) + landmark = Landmark( + name = description['name'], + type = landmarktype, + location = (location.x, location.y), + osm_type = osm_type, + osm_id = osm_id, + attractiveness = score, + must_do = False, + n_tags = n_tags + ) + ret_landmarks.append(landmark) - # skip if unprecise location - if name is None or location[0] is None: - continue + return ret_landmarks + # for elem in G.iterrows(): + # print(elem) + # print(elem.name) + # print(elem.address) + # name = elem.tag('name') # Add name + # location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) - # skip if unused - if 'disused:leisure' in elem.tags().keys(): - continue + # # skip if unprecise location + # if name is None or location[0] is None: + # continue + + # # skip if unused + # if 'disused:leisure' in elem.tags().keys(): + # continue + + # # skip if part of another building + # if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': + # continue + + # else : + # osm_type = elem.type() # Add type : 'way' or 'relation' + # osm_id = elem.id() # Add OSM id + # elem_type = landmarktype # Add the landmark type as 'sightseeing + # n_tags = len(elem.tags().keys()) # Add number of tags - # skip if part of another building - if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': - continue - - else : - osm_type = elem.type() # Add type : 'way' or 'relation' - osm_id = elem.id() # Add OSM id - elem_type = landmarktype # Add the landmark type as 'sightseeing - n_tags = len(elem.tags().keys()) # Add number of tags - - # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT - if amenity == "'amenity'='place_of_worship'" : - score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff']) - elif amenity == "'leisure'='park'" : - score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff']) - else : - score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] + # # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT + # if amenity == "'amenity'='place_of_worship'" : + # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff']) + # elif amenity == "'leisure'='park'" : + # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff']) + # else : + # score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] - if score is not None : - # Generate the landmark and append it to the list - landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags) - L.append(landmark) + # if score is not None : + # # Generate the landmark and append it to the list + # landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags) + # L.append(landmark) - return L + # return L - -"""def get_landmarks(list_amenity: list, landmarktype: LandmarkType, city_country: str = None, coordinates: Tuple[float, float] = None) -> List[Landmark] : - - if city_country is None and coordinates is None : - raise ValueError("Either one of 'city_country' and 'coordinates' arguments must be specified") - - if city_country is not None and coordinates is not None : - raise ValueError("Cannot specify both 'city_country' and 'coordinates' at the same time, please choose either one") - - # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : - parameters = json.loads(f.read()) - tag_coeff = parameters['tag coeff'] - park_coeff = parameters['park coeff'] - church_coeff = parameters['church coeff'] - radius = parameters['radius close to'] - bbox_side = parameters['city bbox side'] - - # If city_country is specified : - if city_country is not None : - nominatim = Nominatim() - areaId = nominatim.query(city_country).areaId() - bbox = None - - # If coordinates are specified : - elif coordinates is not None : - bbox = create_bbox(coordinates, bbox_side) - areaId = None - - else : - raise ValueError("Argument number is not corresponding.") - - # Initialize some variables - N = 0 - L = [] - overpass = Overpass() - - for amenity in list_amenity : - query = overpassQueryBuilder(area=areaId, bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body') - result = overpass.query(query) - N += result.countElements() - - for elem in result.elements(): - - name = elem.tag('name') # Add name - location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) - - # skip if unprecise location - if name is None or location[0] is None: - continue - - # skip if unused - if 'disused:leisure' in elem.tags().keys(): - continue - - # skip if part of another building - if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': - continue - - else : - osm_type = elem.type() # Add type : 'way' or 'relation' - osm_id = elem.id() # Add OSM id - elem_type = landmarktype # Add the landmark type as 'sightseeing - n_tags = len(elem.tags().keys()) # Add number of tags - - # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT - if amenity == "'amenity'='place_of_worship'" : - score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff) - elif amenity == "'leisure'='park'" : - score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff) - else : - score = count_elements_within_radius(location, radius) + n_tags*tag_coeff - - if score is not None : - # Generate the landmark and append it to the list - landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags) - L.append(landmark) - - return L -""" \ No newline at end of file diff --git a/backend/src/optimizer.py b/backend/src/optimizer.py index 68362c6..91251bc 100644 --- a/backend/src/optimizer.py +++ b/backend/src/optimizer.py @@ -1,5 +1,4 @@ import numpy as np -import json, os import yaml from typing import List, Tuple diff --git a/backend/src/parameters/amenity_selectors.yaml b/backend/src/parameters/amenity_selectors.yaml index 43e8220..4f6b223 100644 --- a/backend/src/parameters/amenity_selectors.yaml +++ b/backend/src/parameters/amenity_selectors.yaml @@ -1,26 +1,32 @@ nature: - - "'leisure'='park'" - - "geological" - - "'natural'='geyser'" - - "'natural'='hot_spring'" - - "'natural'='arch'" - - "'natural'='volcano'" - - "'natural'='stone'" - - "'tourism'='alpine_hut'" - - "'tourism'='viewpoint'" - - "'tourism'='zoo'" - - "'waterway'='waterfall'" + leisure: park + geological: '' + natural: + - geyser + - hot_spring + - arch + - volcano + - stone + tourism: + - alpine_hut + - viewpoint + - zoo + waterway: waterfall shopping: - - "'shop'='department_store'" - - "'shop'='mall'" + shop: + - department_store + - mall sightseeing: - - "'tourism'='museum'" - - "'tourism'='attraction'" - - "'tourism'='gallery'" - - "historic" - - "'amenity'='planetarium'" - - "'amenity'='place_of_worship'" - - "'amenity'='fountain'" - - "'water'='reflecting_pool'" + tourism: + - museum + - attraction + - gallery + historic: '' + amenity: + - planetarium + - place_of_worship + - fountain + water: + - reflecting_pool diff --git a/backend/src/parameters/landmark_parameters.yaml b/backend/src/parameters/landmark_parameters.yaml index 3cf1c76..524b7b6 100644 --- a/backend/src/parameters/landmark_parameters.yaml +++ b/backend/src/parameters/landmark_parameters.yaml @@ -1,5 +1,5 @@ -city_bbox_side: 10 -radius_close_to: 27.5 +city_bbox_side: 1500 #m +radius_close_to: 30 church_coeff: 0.6 park_coeff: 1.5 tag_coeff: 100 diff --git a/backend/src/structs/landmarks.py b/backend/src/structs/landmarks.py index d02dc5f..ae17850 100644 --- a/backend/src/structs/landmarks.py +++ b/backend/src/structs/landmarks.py @@ -1,6 +1,5 @@ from typing import Optional from pydantic import BaseModel, Field - from .landmarktype import LandmarkType from uuid import uuid4 @@ -28,3 +27,6 @@ class Landmark(BaseModel) : time_to_reach_next : Optional[int] = 0 # TODO fix this in existing code next_uuid : Optional[str] = None # TODO implement this ASAP + + def __hash__(self) -> int: + return self.uuid.int \ No newline at end of file diff --git a/backend/src/tester.py b/backend/src/tester.py index 91a12b2..fe7220d 100644 --- a/backend/src/tester.py +++ b/backend/src/tester.py @@ -90,7 +90,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]: #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) # Generate the landmarks from the start location - landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location) + landmarks, landmarks_short = generate_landmarks(preferences=preferences, center_coordinates=start.location) #write_data(landmarks, "landmarks.txt") # Insert start and finish to the landmarks list @@ -98,7 +98,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]: landmarks_short.append(finish) # TODO use these parameters in another way - max_walking_time = 2 # hours + max_walking_time = 3 # hours detour = 30 # minutes # First stage optimization From 8f23a4747dc2bb9ac9d8775e9d9ed025638fe08a Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Mon, 8 Jul 2024 11:55:00 +0200 Subject: [PATCH 3/6] further cleanup --- backend/src/constants.py | 7 + backend/src/landmarks_manager.py | 271 +++++++++++++------------------ backend/src/main.py | 85 ++++------ backend/src/tester.py | 96 ++++------- 4 files changed, 182 insertions(+), 277 deletions(-) diff --git a/backend/src/constants.py b/backend/src/constants.py index c60bf84..d5b7b5a 100644 --- a/backend/src/constants.py +++ b/backend/src/constants.py @@ -1,5 +1,6 @@ from pathlib import Path import os +import logging PARAMETERS_DIR = Path('src/parameters') AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml' @@ -10,3 +11,9 @@ OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') OSM_CACHE_DIR = Path(cache_dir_string) + +logger = logging.getLogger(__name__) +logging.basicConfig( + level = logging.INFO, + format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) diff --git a/backend/src/landmarks_manager.py b/backend/src/landmarks_manager.py index 8dcaa8c..690fae8 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -1,5 +1,5 @@ import yaml -import os +import logging import osmnx as ox from shapely.geometry import Point, Polygon, LineString, MultiPolygon @@ -7,182 +7,145 @@ from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference import constants - SIGHTSEEING = LandmarkType(landmark_type='sightseeing') NATURE = LandmarkType(landmark_type='nature') SHOPPING = LandmarkType(landmark_type='shopping') -ox.config(cache_folder=constants.OSM_CACHE_DIR) -# Include the json here -# Create a list of all things to visit given some preferences and a city. Ready for the optimizer -def generate_landmarks(preferences: Preferences, center_coordinates: tuple[float, float]) : - with constants.AMENITY_SELECTORS_PATH.open('r') as f: - amenity_selectors = yaml.safe_load(f) - - with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: - # even though we don't use the parameters here, we already load them to avoid unnecessary io operations - parameters = yaml.safe_load(f) - max_distance = parameters['city_bbox_side'] - L = [] +class LandmarkManager: + logger = logging.getLogger(__name__) - # List for sightseeing - if preferences.sightseeing.score != 0: - score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['church_coeff']) - L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, max_distance, score_func) - correct_score(L1, preferences.sightseeing) - L += L1 - - # List for nature - if preferences.nature.score != 0: - score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['park_coeff']) - L2 = get_landmarks(amenity_selectors['nature'], NATURE, center_coordinates, max_distance, score_func) - correct_score(L2, preferences.nature) - L += L2 - - # List for shopping - if preferences.shopping.score != 0: - score_func = lambda loc, n_tags: count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff'] - L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, center_coordinates, max_distance, score_func) - correct_score(L3, preferences.shopping) - L += L3 + def __init__(self) -> None: + ox.config(cache_folder=constants.OSM_CACHE_DIR) + with constants.AMENITY_SELECTORS_PATH.open('r') as f: + self.amenity_selectors = yaml.safe_load(f) + with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: + self.parameters = yaml.safe_load(f) + # max_distance = parameters['city_bbox_side'] - # remove duplicates - L = list(set(L)) - print(len(L)) - L_constrained = take_most_important(L, parameters['N_important']) - print(len(L_constrained)) - return L, L_constrained + + def get_landmark_lists(self, preferences: Preferences, center_coordinates: tuple[float, float]) -> tuple[list[Landmark], list[Landmark]]: + ''' + Generate a list of landmarks based on the preferences of the user and the center (ie. start) coordinates. + The list is then used by the pathfinding algorithm to generate a path that goes through the most interesting landmarks. + :param preferences: the preferences specified by the user + :param center_coordinates: the coordinates of the starting point + ''' + + L = [] + + # List for sightseeing + if preferences.sightseeing.score != 0: + score_func = lambda loc, n_tags: int((self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff']) * self.parameters['church_coeff']) + L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L1, preferences.sightseeing) + L += L1 + + # List for nature + if preferences.nature.score != 0: + score_func = lambda loc, n_tags: int((self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff']) * self.parameters['park_coeff']) + L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L2, preferences.nature) + L += L2 + + # List for shopping + if preferences.shopping.score != 0: + score_func = lambda loc, n_tags: self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff'] + L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L3, preferences.shopping) + L += L3 + + # remove duplicates + L = list(set(L)) + L_constrained = self.take_most_important(L, self.parameters['N_important']) + self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.') + return L, L_constrained -# Take the most important landmarks from the list -def take_most_important(landmarks: list[Landmark], n_max: int) -> list[Landmark]: + # Take the most important landmarks from the list + def take_most_important(self, landmarks: list[Landmark], n_max: int) -> list[Landmark]: - landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True) - return landmarks_sorted[:n_max] + landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True) + return landmarks_sorted[:n_max] -# Correct the score of a list of landmarks by taking into account preference settings -def correct_score(L: list[Landmark], preference: Preference) : + # Correct the score of a list of landmarks by taking into account preference settings + def correct_score(self, L: list[Landmark], preference: Preference): - if len(L) == 0 : - return - - if L[0].type != preference.type : - raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {L[0].name}") + if len(L) == 0 : + return - for elem in L : - elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation + if L[0].type != preference.type : + raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {L[0].name}") + + for elem in L : + elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation -# Function to count elements within a certain radius of a location -def count_elements_within_radius(point: Point, radius: int) -> int: - - center_coordinates = (point.x, point.y) - try: + # Function to count elements within a certain radius of a location + def count_elements_within_radius(self, point: Point, radius: int) -> int: + + center_coordinates = (point.x, point.y) + try: + landmarks = ox.features_from_point( + center_point = center_coordinates, + dist = radius, + tags = {'building': True} # this is a common tag to give an estimation of the number of elements in the area + ) + return len(landmarks) + except ox._errors.InsufficientResponseError: + return 0 + + + + + def fetch_landmarks( + self, + amenity_selectors: list[dict], + landmarktype: LandmarkType, + center_coordinates: tuple[float, float], + distance: int, + score_function: callable + ) -> list[Landmark]: + landmarks = ox.features_from_point( center_point = center_coordinates, - dist = radius, - tags = {'building': True} # this is a common tag to give an estimation of the number of elements in the area + dist = distance, + tags = amenity_selectors ) - return len(landmarks) - except ox._errors.InsufficientResponseError: - return 0 + # cleanup the list + # remove rows where name is None + landmarks = landmarks[landmarks['name'].notna()] + # TODO: remove rows that are part of another building + ret_landmarks = [] + for element, description in landmarks.iterrows(): + osm_type = element[0] + osm_id = element[1] + location = description['geometry'] + n_tags = len(description['nodes']) if type(description['nodes']) == list else 1 + if type(location) == Point: + location = location + elif type(location) == Polygon or type(location) == MultiPolygon: + location = location.centroid + elif type(location) == LineString: + location = location.interpolate(location.length/2) -def get_landmarks( - amenity_selectors: list[dict], - landmarktype: LandmarkType, - center_coordinates: tuple[float, float], - distance: int, - score_function: callable -) -> list[Landmark]: - - landmarks = ox.features_from_point( - center_point = center_coordinates, - dist = distance, - tags = amenity_selectors - ) - - # cleanup the list - # remove rows where name is None - landmarks = landmarks[landmarks['name'].notna()] - # TODO: remove rows that are part of another building - - ret_landmarks = [] - for element, description in landmarks.iterrows(): - osm_type = element[0] - osm_id = element[1] - location = description['geometry'] - n_tags = len(description['nodes']) if type(description['nodes']) == list else 1 - - # print(description['nodes']) - print(description['name']) - # print(location, type(location)) - if type(location) == Point: - location = location - elif type(location) == Polygon or type(location) == MultiPolygon: - location = location.centroid - elif type(location) == LineString: - location = location.interpolate(location.length/2) - - score = score_function(location, n_tags) - print(score) - landmark = Landmark( - name = description['name'], - type = landmarktype, - location = (location.x, location.y), - osm_type = osm_type, - osm_id = osm_id, - attractiveness = score, - must_do = False, - n_tags = n_tags - ) - ret_landmarks.append(landmark) - - return ret_landmarks - # for elem in G.iterrows(): - # print(elem) - # print(elem.name) - # print(elem.address) - # name = elem.tag('name') # Add name - # location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) - - # # skip if unprecise location - # if name is None or location[0] is None: - # continue - - # # skip if unused - # if 'disused:leisure' in elem.tags().keys(): - # continue - - # # skip if part of another building - # if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': - # continue - - # else : - # osm_type = elem.type() # Add type : 'way' or 'relation' - # osm_id = elem.id() # Add OSM id - # elem_type = landmarktype # Add the landmark type as 'sightseeing - # n_tags = len(elem.tags().keys()) # Add number of tags - - # # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT - # if amenity == "'amenity'='place_of_worship'" : - # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff']) - # elif amenity == "'leisure'='park'" : - # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff']) - # else : - # score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] - - # if score is not None : - # # Generate the landmark and append it to the list - # landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags) - # L.append(landmark) - - # return L - + score = score_function(location, n_tags) + landmark = Landmark( + name = description['name'], + type = landmarktype, + location = (location.x, location.y), + osm_type = osm_type, + osm_id = osm_id, + attractiveness = score, + must_do = False, + n_tags = n_tags + ) + ret_landmarks.append(landmark) + return ret_landmarks diff --git a/backend/src/main.py b/backend/src/main.py index 89b49fa..a1ff712 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,51 +1,45 @@ from optimizer import solve_optimization -from refiner import refine_optimization -from landmarks_manager import generate_landmarks +# from refiner import refine_optimization +from landmarks_manager import LandmarkManager from structs.landmarks import Landmark from structs.landmarktype import LandmarkType -from structs.preferences import Preferences, Preference +from structs.preferences import Preferences from fastapi import FastAPI, Query, Body -from typing import List app = FastAPI() - +manager = LandmarkManager() # TODO: needs a global variable to store the landmarks accross function calls # linked_tour = [] -# Assuming frontend is calling like this : -#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}" -@app.post("/optimizer_coords/{start_lat}/{start_lon}/{finish_lat}/{finish_lon}") -def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(...), finish_lat: float = None, finish_lon: float = None) -> List[Landmark]: - +@app.post("/route/new") +def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] = None) -> str: + ''' + Main function to call the optimizer. + :param preferences: the preferences specified by the user as the post body + :param start: the coordinates of the starting point as a tuple of floats (as url query parameters) + :param end: the coordinates of the finishing point as a tuple of floats (as url query parameters) + :return: the uuid of the first landmark in the optimized route + ''' if preferences is None : raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.") - if bool(start_lat) ^ bool(start_lon) : - raise ValueError("Please provide both latitude and longitude for the starting point") - if bool(finish_lat) ^ bool(finish_lon) : - raise ValueError("Please provide both latitude and longitude for the finish point") + if start is None: + raise ValueError("Please provide the starting coordinates as a tuple of floats.") + if end is None: + end = start - start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start_lat, start_lon), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + start_landmark = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start[0], start[1]), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + end_landmark = Landmark(name='end', type=LandmarkType(landmark_type='end'), location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - if bool(finish_lat) and bool(finish_lon) : - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(finish_lat, finish_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - else : - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(start_lat, start_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - - - start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.8375946, 2.2949904), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8375946, 2.2949904), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - # Generate the landmarks from the start location - landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location) - + landmarks, landmarks_short = LandmarkManager.get_landmark_lists(preferences=preferences, coordinates=start.location) + print([l.name for l in landmarks_short]) # insert start and finish to the landmarks list - landmarks_short.insert(0, start) - landmarks_short.append(finish) + landmarks_short.insert(0, start_landmark) + landmarks_short.append(end_landmark) - - # TODO use these parameters in another way + # TODO infer these parameters from the preferences max_walking_time = 4 # hours detour = 30 # minutes @@ -53,32 +47,11 @@ def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(.. base_tour = solve_optimization(landmarks_short, max_walking_time*60, True) # Second stage optimization - refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) + # refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) - - # TODO: should look something like this - # # set time to reach and transform into fully functional linked list - # linked_tour += link(refined_tour) - # return { - # 'city_name': 'Paris', - # 'n_stops': len(linked_tour), - # 'first_landmark_uuid': linked_tour[0].uuid, - # } - - return refined_tour - - - - -# input city, country in the form of 'Paris, France' -@app.post("/test2/{city_country}") -def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landmark]: - - landmarks = generate_landmarks(city_country, preferences) - - max_steps = 9000000 - - visiting_order = solve_optimization(landmarks, max_steps, True) + # linked_tour = ... + # return linked_tour[0].uuid + return base_tour[0].uuid @@ -86,5 +59,3 @@ def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landm def get_landmark(landmark_uuid: str) -> Landmark: #cherche dans linked_tour et retourne le landmark correspondant pass - - diff --git a/backend/src/tester.py b/backend/src/tester.py index fe7220d..57692d2 100644 --- a/backend/src/tester.py +++ b/backend/src/tester.py @@ -1,11 +1,11 @@ import pandas as pd from typing import List -from landmarks_manager import generate_landmarks +from landmarks_manager import LandmarkManager from fastapi.encoders import jsonable_encoder from optimizer import solve_optimization -from refiner import refine_optimization +# from refiner import refine_optimization from structs.landmarks import Landmark from structs.landmarktype import LandmarkType from structs.preferences import Preferences, Preference @@ -23,74 +23,37 @@ def write_data(L: List[Landmark], file_name: str): data.to_json(file_name, indent = 2, force_ascii=False) -def test3(city_country: str) -> List[Landmark]: +def main(coordinates: tuple[float, float]) -> List[Landmark]: + manager = LandmarkManager() preferences = Preferences( - sightseeing=Preference( - name='sightseeing', - type=LandmarkType(landmark_type='sightseeing'), - score = 5), - nature=Preference( - name='nature', - type=LandmarkType(landmark_type='nature'), - score = 0), - shopping=Preference( - name='shopping', - type=LandmarkType(landmark_type='shopping'), - score = 5)) - - coordinates = None - - landmarks, landmarks_short = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=coordinates) - - #write_data(landmarks) - - start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.2044576, 16.3870242), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.2044576, 16.3870242), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - - - test = landmarks_short - - test.insert(0, start) - test.append(finish) - - max_walking_time = 2 # hours - - visiting_list = solve_optimization(test, max_walking_time*60, True) - - - - - -def test4(coordinates: tuple[float, float]) -> List[Landmark]: - - - preferences = Preferences( - sightseeing=Preference( - name='sightseeing', - type=LandmarkType(landmark_type='sightseeing'), - score = 5), - nature=Preference( - name='nature', - type=LandmarkType(landmark_type='nature'), - score = 5), - shopping=Preference( - name='shopping', - type=LandmarkType(landmark_type='shopping'), - score = 5)) + sightseeing=Preference( + name='sightseeing', + type=LandmarkType(landmark_type='sightseeing'), + score = 5 + ), + nature=Preference( + name='nature', + type=LandmarkType(landmark_type='nature'), + score = 5 + ), + shopping=Preference( + name='shopping', + type=LandmarkType(landmark_type='shopping'), + score = 5 + ) + ) # Create start and finish start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=coordinates, osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=coordinates, osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8777055, 2.3640967), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - #start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.847132, 2.312359), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.843185, 2.344533), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) # Generate the landmarks from the start location - landmarks, landmarks_short = generate_landmarks(preferences=preferences, center_coordinates=start.location) + landmarks, landmarks_short = manager.get_landmark_lists(preferences=preferences, center_coordinates=start.location) + print([l.name for l in landmarks_short]) + #write_data(landmarks, "landmarks.txt") # Insert start and finish to the landmarks list @@ -105,12 +68,13 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]: base_tour = solve_optimization(landmarks_short, max_walking_time*60, True) # Second stage optimization - refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) + # refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) - return refined_tour + return base_tour -test4(tuple((48.8344400, 2.3220540))) # Café Chez César -#test4(tuple((48.8375946, 2.2949904))) # Point random -#test4(tuple((47.377859, 8.540585))) # Zurich HB -#test3('Vienna, Austria') \ No newline at end of file + +if __name__ == '__main__': + start = (48.847132, 2.312359) # Café Chez César + # start = (47.377859, 8.540585) # Zurich HB + main(start) From 25cc0fa300a8e0a4e39f19f6ecd22617c36612fa Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Mon, 8 Jul 2024 11:55:27 +0200 Subject: [PATCH 4/6] (theoretically) functional deployment --- deployment/deployment.yaml | 37 +++++++++++++++++++++++++++++++++++ deployment/ingress.yaml | 15 ++++++++++++++ deployment/kustomization.yaml | 15 ++++++++++++++ deployment/namespace.yaml | 4 ++++ deployment/pvc.yaml | 11 +++++++++++ deployment/service.yaml | 11 +++++++++++ 6 files changed, 93 insertions(+) create mode 100644 deployment/deployment.yaml create mode 100644 deployment/ingress.yaml create mode 100644 deployment/namespace.yaml create mode 100644 deployment/pvc.yaml create mode 100644 deployment/service.yaml diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml new file mode 100644 index 0000000..616b587 --- /dev/null +++ b/deployment/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nav-backend +spec: + replicas: 3 + selector: + matchLabels: + app: nav-backend + template: + metadata: + labels: + app: nav-backend + spec: + containers: + - name: worker + image: backend-image + ports: + - containerPort: 8000 + env: + - name: NUM_WORKERS + value: "3" + - name: OSM_CACHE_DIR + value: "/osm-cache" + volumeMounts: + - name: osm-cache + mountPath: /osm-cache + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 4 + memory: 8Gi + volumes: + - name: osm-cache + emptyDir: {} diff --git a/deployment/ingress.yaml b/deployment/ingress.yaml new file mode 100644 index 0000000..8fa3b41 --- /dev/null +++ b/deployment/ingress.yaml @@ -0,0 +1,15 @@ +kind: IngressRoute +apiVersion: traefik.io/v1alpha1 +metadata: + name: nav-ingress +spec: + entryPoints: + - websecure + routes: + - match: Host(`nav.kluster.moll.re`) + kind: Rule + services: + - name: nav-service + port: 8000 + tls: + certResolver: default-tls diff --git a/deployment/kustomization.yaml b/deployment/kustomization.yaml index e69de29..28040ec 100644 --- a/deployment/kustomization.yaml +++ b/deployment/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + + +namespace: nav +resources: + - namespace.yaml + - deployment.yaml + - service.yaml + - ingress.yaml + +images: + - name: backend-image + newName: git.kluster.moll.re/remoll/fast_network_navigation/backend + newTag: latest \ No newline at end of file diff --git a/deployment/namespace.yaml b/deployment/namespace.yaml new file mode 100644 index 0000000..0a074bd --- /dev/null +++ b/deployment/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: placeholder diff --git a/deployment/pvc.yaml b/deployment/pvc.yaml new file mode 100644 index 0000000..15897ec --- /dev/null +++ b/deployment/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cache +spec: + storageClassName: "nfs-client" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "5Gi" diff --git a/deployment/service.yaml b/deployment/service.yaml new file mode 100644 index 0000000..c4e5de3 --- /dev/null +++ b/deployment/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: nav-service +spec: + selector: + app: nav-backend + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 From 8d71cab8d5941cd70681524e8ddbcc4f1791fcf3 Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Wed, 17 Jul 2024 12:00:40 +0200 Subject: [PATCH 5/6] osmnx does not behave --- backend/src/landmarks_manager.py | 11 +++++++---- backend/src/parameters/optimizer_parameters.yaml | 2 +- deployment/deployment.yaml | 9 ++++++--- deployment/kustomization.yaml | 1 + deployment/pvc.yaml | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/src/landmarks_manager.py b/backend/src/landmarks_manager.py index 690fae8..0392245 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -16,7 +16,9 @@ class LandmarkManager: logger = logging.getLogger(__name__) def __init__(self) -> None: - ox.config(cache_folder=constants.OSM_CACHE_DIR) + ox.settings.cache_folder = constants.OSM_CACHE_DIR + ox.settings.use_cache = True + ox.config(use_cache=True, cache_folder=constants.OSM_CACHE_DIR) with constants.AMENITY_SELECTORS_PATH.open('r') as f: self.amenity_selectors = yaml.safe_load(f) with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: @@ -36,21 +38,21 @@ class LandmarkManager: # List for sightseeing if preferences.sightseeing.score != 0: - score_func = lambda loc, n_tags: int((self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff']) * self.parameters['church_coeff']) + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['church_coeff']) L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, self.parameters['city_bbox_side'], score_func) self.correct_score(L1, preferences.sightseeing) L += L1 # List for nature if preferences.nature.score != 0: - score_func = lambda loc, n_tags: int((self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff']) * self.parameters['park_coeff']) + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['park_coeff']) L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, center_coordinates, self.parameters['city_bbox_side'], score_func) self.correct_score(L2, preferences.nature) L += L2 # List for shopping if preferences.shopping.score != 0: - score_func = lambda loc, n_tags: self.count_elements_within_radius(loc, self.parameters['radius_close_to']) + n_tags * self.parameters['tag_coeff'] + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff'])) L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, center_coordinates, self.parameters['city_bbox_side'], score_func) self.correct_score(L3, preferences.shopping) L += L3 @@ -115,6 +117,7 @@ class LandmarkManager: dist = distance, tags = amenity_selectors ) + self.logger.info(f'Fetched {len(landmarks)} landmarks around {center_coordinates}.') # cleanup the list # remove rows where name is None diff --git a/backend/src/parameters/optimizer_parameters.yaml b/backend/src/parameters/optimizer_parameters.yaml index b6dca0e..515d028 100644 --- a/backend/src/parameters/optimizer_parameters.yaml +++ b/backend/src/parameters/optimizer_parameters.yaml @@ -1,3 +1,3 @@ detour_factor: 1.4 average_walking_speed: 4.8 -max_landmarks: 10 +max_landmarks: 40 diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml index 616b587..aabc6fa 100644 --- a/deployment/deployment.yaml +++ b/deployment/deployment.yaml @@ -3,7 +3,7 @@ kind: Deployment metadata: name: nav-backend spec: - replicas: 3 + replicas: 1 selector: matchLabels: app: nav-backend @@ -31,7 +31,10 @@ spec: memory: 100Mi limits: cpu: 4 - memory: 8Gi + memory: 10Gi volumes: - name: osm-cache - emptyDir: {} + persistentVolumeClaim: + claimName: osm-cache + + \ No newline at end of file diff --git a/deployment/kustomization.yaml b/deployment/kustomization.yaml index 28040ec..bbb5d0c 100644 --- a/deployment/kustomization.yaml +++ b/deployment/kustomization.yaml @@ -5,6 +5,7 @@ kind: Kustomization namespace: nav resources: - namespace.yaml + - pvc.yaml - deployment.yaml - service.yaml - ingress.yaml diff --git a/deployment/pvc.yaml b/deployment/pvc.yaml index 15897ec..a7e747f 100644 --- a/deployment/pvc.yaml +++ b/deployment/pvc.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: cache + name: osm-cache spec: storageClassName: "nfs-client" accessModes: From 7d7a25e2f3bb4a9ba854cd830f33f8e9f2948be7 Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Wed, 17 Jul 2024 12:33:53 +0200 Subject: [PATCH 6/6] stage changes as reference implementation --- ...landmarks_manager.py => example_landmarks_manager.py} | 9 +++------ backend/src/{optimizer.py => example_optimizer.py} | 0 backend/src/{refiner.py => example_refiner.py} | 2 +- backend/src/main.py | 2 +- backend/src/parameters/landmark_parameters.yaml | 2 +- backend/src/tester.py | 2 +- 6 files changed, 7 insertions(+), 10 deletions(-) rename backend/src/{landmarks_manager.py => example_landmarks_manager.py} (93%) rename backend/src/{optimizer.py => example_optimizer.py} (100%) rename backend/src/{refiner.py => example_refiner.py} (98%) diff --git a/backend/src/landmarks_manager.py b/backend/src/example_landmarks_manager.py similarity index 93% rename from backend/src/landmarks_manager.py rename to backend/src/example_landmarks_manager.py index 0392245..30df6dc 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/example_landmarks_manager.py @@ -1,8 +1,6 @@ import yaml import logging -import osmnx as ox -from shapely.geometry import Point, Polygon, LineString, MultiPolygon - +from OSMPythonTools import cachingStrategy, overpass from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference import constants @@ -16,9 +14,8 @@ class LandmarkManager: logger = logging.getLogger(__name__) def __init__(self) -> None: - ox.settings.cache_folder = constants.OSM_CACHE_DIR - ox.settings.use_cache = True - ox.config(use_cache=True, cache_folder=constants.OSM_CACHE_DIR) + strategy = cachingStrategy.JSON(cacheDir=constants.OSM_CACHE_DIR) + self.query_builder = overpass.Overpass() with constants.AMENITY_SELECTORS_PATH.open('r') as f: self.amenity_selectors = yaml.safe_load(f) with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: diff --git a/backend/src/optimizer.py b/backend/src/example_optimizer.py similarity index 100% rename from backend/src/optimizer.py rename to backend/src/example_optimizer.py diff --git a/backend/src/refiner.py b/backend/src/example_refiner.py similarity index 98% rename from backend/src/refiner.py rename to backend/src/example_refiner.py index a292b73..5eb4368 100644 --- a/backend/src/refiner.py +++ b/backend/src/example_refiner.py @@ -10,7 +10,7 @@ from math import pi from structs.landmarks import Landmark from landmarks_manager import take_most_important -from optimizer import solve_optimization, link_list_simple, print_res, get_distance +from backend.src.example_optimizer import solve_optimization, link_list_simple, print_res, get_distance import constants diff --git a/backend/src/main.py b/backend/src/main.py index a1ff712..8a76288 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,4 +1,4 @@ -from optimizer import solve_optimization +from backend.src.example_optimizer import solve_optimization # from refiner import refine_optimization from landmarks_manager import LandmarkManager from structs.landmarks import Landmark diff --git a/backend/src/parameters/landmark_parameters.yaml b/backend/src/parameters/landmark_parameters.yaml index 524b7b6..b85c89a 100644 --- a/backend/src/parameters/landmark_parameters.yaml +++ b/backend/src/parameters/landmark_parameters.yaml @@ -1,4 +1,4 @@ -city_bbox_side: 1500 #m +city_bbox_side: 5000 #m radius_close_to: 30 church_coeff: 0.6 park_coeff: 1.5 diff --git a/backend/src/tester.py b/backend/src/tester.py index 57692d2..71d82e7 100644 --- a/backend/src/tester.py +++ b/backend/src/tester.py @@ -4,7 +4,7 @@ from typing import List from landmarks_manager import LandmarkManager from fastapi.encoders import jsonable_encoder -from optimizer import solve_optimization +from backend.src.example_optimizer import solve_optimization # from refiner import refine_optimization from structs.landmarks import Landmark from structs.landmarktype import LandmarkType