style corrections, documentation, duplicate removal, flow improvement #11
@ -20,7 +20,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
registry: git.kluster.moll.re
|
registry: git.kluster.moll.re
|
||||||
username: ${{ gitea.repository_owner }}
|
username: ${{ gitea.repository_owner }}
|
||||||
password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
password: ${{ secrets.PACKAGE_REGISTRY_ACCESS }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@ -29,5 +29,5 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: backend
|
context: backend
|
||||||
tags: git.kluster.moll.re/remoll/fast_network_navigation/backend:latest
|
tags: git.kluster.moll.re/anydev/anyway-backend:latest
|
||||||
push: true
|
push: true
|
||||||
|
@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add required secrets
|
- name: Add required secrets
|
||||||
run: |
|
run: |
|
||||||
echo ${{ secrets.ANDROID_SECRETS_BASE64 }} | base64 -d > ./android/secrets.properties
|
echo ${{ secrets.ANDROID_SECRETS_PROPERTIES }} > ./android/secrets.properties
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|
||||||
- name: Sanity check
|
- name: Sanity check
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
on:
|
# on:
|
||||||
pull_request:
|
# pull_request:
|
||||||
branches:
|
# branches:
|
||||||
- main
|
# - main
|
||||||
paths:
|
# paths:
|
||||||
- frontend/**
|
# - frontend/**
|
||||||
|
|
||||||
|
|
||||||
name: Build web
|
# name: Build web
|
||||||
|
|
||||||
jobs:
|
# jobs:
|
||||||
build:
|
# build:
|
||||||
name: Build Web
|
# name: Build Web
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
|
|
||||||
- name: Install prerequisites
|
# - name: Install prerequisites
|
||||||
run: |
|
# run: |
|
||||||
sudo apt-get update
|
# sudo apt-get update
|
||||||
sudo apt-get install -y xz-utils
|
# sudo apt-get install -y xz-utils
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
# - uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: https://github.com/subosito/flutter-action@v2
|
# - uses: https://github.com/subosito/flutter-action@v2
|
||||||
with:
|
# with:
|
||||||
channel: stable
|
# channel: stable
|
||||||
flutter-version: 3.19.6
|
# flutter-version: 3.19.6
|
||||||
cache: true
|
# cache: true
|
||||||
|
|
||||||
- run: flutter pub get
|
# - run: flutter pub get
|
||||||
working-directory: ./frontend
|
# working-directory: ./frontend
|
||||||
|
|
||||||
- run: flutter build web
|
# - run: flutter build web
|
||||||
working-directory: ./frontend
|
# working-directory: ./frontend
|
||||||
|
52
.vscode/launch.json
vendored
Normal file
52
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
// backend - python using fastapi
|
||||||
|
{
|
||||||
|
"name": "Backend - debug",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "uvicorn",
|
||||||
|
"env": {
|
||||||
|
"DEBUG": "true"
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"--app-dir",
|
||||||
|
"src",
|
||||||
|
"main:app",
|
||||||
|
"--reload",
|
||||||
|
],
|
||||||
|
"jinja": true,
|
||||||
|
"cwd": "${workspaceFolder}/backend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Backend - tester",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "src/tester.py",
|
||||||
|
"env": {
|
||||||
|
"DEBUG": "true"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceFolder}/backend"
|
||||||
|
},
|
||||||
|
// frontend - flutter app
|
||||||
|
{
|
||||||
|
"name": "Frontend - debug",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"cwd": "${workspaceFolder}/frontend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frontend - profile",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"flutterMode": "profile",
|
||||||
|
"cwd": "${workspaceFolder}/frontend"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -14,4 +14,4 @@ EXPOSE 8000
|
|||||||
ENV NUM_WORKERS=1
|
ENV NUM_WORKERS=1
|
||||||
ENV OSM_CACHE_DIR=/cache
|
ENV OSM_CACHE_DIR=/cache
|
||||||
|
|
||||||
CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS']
|
CMD fastapi run src/main.py --port 8000 --workers $NUM_WORKERS
|
||||||
|
@ -3,17 +3,14 @@ url = "https://pypi.org/simple"
|
|||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
numpy = "*"
|
numpy = "*"
|
||||||
scipy = "*"
|
|
||||||
fastapi = "*"
|
fastapi = "*"
|
||||||
pydantic = "*"
|
pydantic = "*"
|
||||||
shapely = "*"
|
|
||||||
networkx = "*"
|
|
||||||
geopy = "*"
|
geopy = "*"
|
||||||
requests = ">=2.20.1"
|
shapely = "*"
|
||||||
mwparserfromhell = ">=0.5.2"
|
scipy = "*"
|
||||||
packaging = "*"
|
osmpythontools = "*"
|
||||||
pywikibot = "*"
|
pywikibot = "*"
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
|
696
backend/Pipfile.lock
generated
696
backend/Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "6f1907b01627126d80ae688e9f2e32fc696d85f0cca3913f14fd5d26cb3ac2db"
|
"sha256": "f0de801038593d42d8b780d14c2c72bb4f5f5e66df02f72244917ede5d5ebce6"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@ -30,6 +30,14 @@
|
|||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.4.0"
|
"version": "==4.4.0"
|
||||||
},
|
},
|
||||||
|
"beautifulsoup4": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051",
|
||||||
|
"sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
|
"version": "==4.12.3"
|
||||||
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b",
|
"sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b",
|
||||||
@ -142,6 +150,64 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==8.1.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": {
|
"dnspython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50",
|
"sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50",
|
||||||
@ -175,6 +241,54 @@
|
|||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==0.0.4"
|
"version": "==0.0.4"
|
||||||
},
|
},
|
||||||
|
"fonttools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122",
|
||||||
|
"sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397",
|
||||||
|
"sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f",
|
||||||
|
"sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d",
|
||||||
|
"sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60",
|
||||||
|
"sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169",
|
||||||
|
"sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8",
|
||||||
|
"sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31",
|
||||||
|
"sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923",
|
||||||
|
"sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2",
|
||||||
|
"sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb",
|
||||||
|
"sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab",
|
||||||
|
"sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb",
|
||||||
|
"sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a",
|
||||||
|
"sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670",
|
||||||
|
"sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8",
|
||||||
|
"sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407",
|
||||||
|
"sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671",
|
||||||
|
"sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88",
|
||||||
|
"sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f",
|
||||||
|
"sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f",
|
||||||
|
"sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0",
|
||||||
|
"sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb",
|
||||||
|
"sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2",
|
||||||
|
"sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d",
|
||||||
|
"sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c",
|
||||||
|
"sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3",
|
||||||
|
"sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719",
|
||||||
|
"sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749",
|
||||||
|
"sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4",
|
||||||
|
"sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f",
|
||||||
|
"sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02",
|
||||||
|
"sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58",
|
||||||
|
"sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1",
|
||||||
|
"sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41",
|
||||||
|
"sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4",
|
||||||
|
"sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb",
|
||||||
|
"sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb",
|
||||||
|
"sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3",
|
||||||
|
"sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d",
|
||||||
|
"sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d",
|
||||||
|
"sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==4.53.1"
|
||||||
|
},
|
||||||
"geographiclib": {
|
"geographiclib": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734",
|
"sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734",
|
||||||
@ -183,6 +297,14 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==2.0"
|
"version": "==2.0"
|
||||||
},
|
},
|
||||||
|
"geojson": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac",
|
||||||
|
"sha256:68a9771827237adb8c0c71f8527509c8f5bef61733aa434cefc9c9d4f0ebe8f3"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==3.1.0"
|
||||||
|
},
|
||||||
"geopy": {
|
"geopy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1",
|
"sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1",
|
||||||
@ -273,6 +395,264 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==3.1.4"
|
"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": {
|
"markdown-it-py": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
|
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
|
||||||
@ -347,6 +727,41 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==2.1.5"
|
"version": "==2.1.5"
|
||||||
},
|
},
|
||||||
|
"matplotlib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33",
|
||||||
|
"sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03",
|
||||||
|
"sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c",
|
||||||
|
"sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0",
|
||||||
|
"sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe",
|
||||||
|
"sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff",
|
||||||
|
"sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3",
|
||||||
|
"sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d",
|
||||||
|
"sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3",
|
||||||
|
"sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7",
|
||||||
|
"sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7",
|
||||||
|
"sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049",
|
||||||
|
"sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b",
|
||||||
|
"sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842",
|
||||||
|
"sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a",
|
||||||
|
"sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb",
|
||||||
|
"sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb",
|
||||||
|
"sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5",
|
||||||
|
"sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04",
|
||||||
|
"sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b",
|
||||||
|
"sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0",
|
||||||
|
"sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f",
|
||||||
|
"sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812",
|
||||||
|
"sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98",
|
||||||
|
"sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b",
|
||||||
|
"sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80",
|
||||||
|
"sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92",
|
||||||
|
"sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010",
|
||||||
|
"sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.9'",
|
||||||
|
"version": "==3.9.1"
|
||||||
|
},
|
||||||
"mdurl": {
|
"mdurl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
|
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
|
||||||
@ -384,19 +799,9 @@
|
|||||||
"sha256:fd05481adc0806f4b8f8f8cb309ec56924b17ce386cb1c2f73919d8a012e6b16",
|
"sha256:fd05481adc0806f4b8f8f8cb309ec56924b17ce386cb1c2f73919d8a012e6b16",
|
||||||
"sha256:fff66e97f7c02aa0fd57ff8f702977a9c5a1d72ef55b64ee9b146291e4c41057"
|
"sha256:fff66e97f7c02aa0fd57ff8f702977a9c5a1d72ef55b64ee9b146291e4c41057"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==0.6.6"
|
"version": "==0.6.6"
|
||||||
},
|
},
|
||||||
"networkx": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9",
|
|
||||||
"sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.10'",
|
|
||||||
"version": "==3.3"
|
|
||||||
},
|
|
||||||
"numpy": {
|
"numpy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8",
|
"sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8",
|
||||||
@ -449,15 +854,142 @@
|
|||||||
"markers": "python_version >= '3.9'",
|
"markers": "python_version >= '3.9'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
},
|
},
|
||||||
|
"osmpythontools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:13ff721f760fdad5dd78b4d1461d286b78bba96ee151a7301ee8c11a0c258be9"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.3.5"
|
||||||
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
|
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
|
||||||
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
|
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==24.1"
|
"version": "==24.1"
|
||||||
},
|
},
|
||||||
|
"pandas": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863",
|
||||||
|
"sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2",
|
||||||
|
"sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1",
|
||||||
|
"sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad",
|
||||||
|
"sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db",
|
||||||
|
"sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76",
|
||||||
|
"sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51",
|
||||||
|
"sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32",
|
||||||
|
"sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08",
|
||||||
|
"sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b",
|
||||||
|
"sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4",
|
||||||
|
"sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921",
|
||||||
|
"sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288",
|
||||||
|
"sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee",
|
||||||
|
"sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0",
|
||||||
|
"sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24",
|
||||||
|
"sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99",
|
||||||
|
"sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151",
|
||||||
|
"sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd",
|
||||||
|
"sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce",
|
||||||
|
"sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57",
|
||||||
|
"sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef",
|
||||||
|
"sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54",
|
||||||
|
"sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a",
|
||||||
|
"sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238",
|
||||||
|
"sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23",
|
||||||
|
"sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772",
|
||||||
|
"sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce",
|
||||||
|
"sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.9'",
|
||||||
|
"version": "==2.2.2"
|
||||||
|
},
|
||||||
|
"pillow": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885",
|
||||||
|
"sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea",
|
||||||
|
"sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df",
|
||||||
|
"sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5",
|
||||||
|
"sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c",
|
||||||
|
"sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d",
|
||||||
|
"sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd",
|
||||||
|
"sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06",
|
||||||
|
"sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908",
|
||||||
|
"sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a",
|
||||||
|
"sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be",
|
||||||
|
"sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0",
|
||||||
|
"sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b",
|
||||||
|
"sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80",
|
||||||
|
"sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a",
|
||||||
|
"sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e",
|
||||||
|
"sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9",
|
||||||
|
"sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696",
|
||||||
|
"sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b",
|
||||||
|
"sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309",
|
||||||
|
"sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e",
|
||||||
|
"sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab",
|
||||||
|
"sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d",
|
||||||
|
"sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060",
|
||||||
|
"sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d",
|
||||||
|
"sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d",
|
||||||
|
"sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4",
|
||||||
|
"sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3",
|
||||||
|
"sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6",
|
||||||
|
"sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb",
|
||||||
|
"sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94",
|
||||||
|
"sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b",
|
||||||
|
"sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496",
|
||||||
|
"sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0",
|
||||||
|
"sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319",
|
||||||
|
"sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b",
|
||||||
|
"sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856",
|
||||||
|
"sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef",
|
||||||
|
"sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680",
|
||||||
|
"sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b",
|
||||||
|
"sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42",
|
||||||
|
"sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e",
|
||||||
|
"sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597",
|
||||||
|
"sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a",
|
||||||
|
"sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8",
|
||||||
|
"sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3",
|
||||||
|
"sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736",
|
||||||
|
"sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da",
|
||||||
|
"sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126",
|
||||||
|
"sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd",
|
||||||
|
"sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5",
|
||||||
|
"sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b",
|
||||||
|
"sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026",
|
||||||
|
"sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b",
|
||||||
|
"sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc",
|
||||||
|
"sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46",
|
||||||
|
"sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2",
|
||||||
|
"sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c",
|
||||||
|
"sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe",
|
||||||
|
"sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984",
|
||||||
|
"sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a",
|
||||||
|
"sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70",
|
||||||
|
"sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca",
|
||||||
|
"sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b",
|
||||||
|
"sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91",
|
||||||
|
"sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3",
|
||||||
|
"sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84",
|
||||||
|
"sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1",
|
||||||
|
"sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5",
|
||||||
|
"sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be",
|
||||||
|
"sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f",
|
||||||
|
"sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc",
|
||||||
|
"sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9",
|
||||||
|
"sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e",
|
||||||
|
"sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141",
|
||||||
|
"sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef",
|
||||||
|
"sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22",
|
||||||
|
"sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27",
|
||||||
|
"sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e",
|
||||||
|
"sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==10.4.0"
|
||||||
|
},
|
||||||
"pydantic": {
|
"pydantic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a",
|
"sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a",
|
||||||
@ -570,6 +1102,22 @@
|
|||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.18.0"
|
"version": "==2.18.0"
|
||||||
},
|
},
|
||||||
|
"pyparsing": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad",
|
||||||
|
"sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.6.8'",
|
||||||
|
"version": "==3.1.2"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==2.9.0.post0"
|
||||||
|
},
|
||||||
"python-dotenv": {
|
"python-dotenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca",
|
"sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca",
|
||||||
@ -585,6 +1133,13 @@
|
|||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==0.0.9"
|
"version": "==0.0.9"
|
||||||
},
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
|
||||||
|
"sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
|
||||||
|
],
|
||||||
|
"version": "==2024.1"
|
||||||
|
},
|
||||||
"pywikibot": {
|
"pywikibot": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2",
|
"sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2",
|
||||||
@ -655,7 +1210,6 @@
|
|||||||
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
|
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
|
||||||
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
|
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.32.3"
|
"version": "==2.32.3"
|
||||||
},
|
},
|
||||||
@ -750,6 +1304,14 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.5.4"
|
"version": "==1.5.4"
|
||||||
},
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||||
|
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.16.0"
|
||||||
|
},
|
||||||
"sniffio": {
|
"sniffio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||||
@ -758,6 +1320,14 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.3.1"
|
"version": "==1.3.1"
|
||||||
},
|
},
|
||||||
|
"soupsieve": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690",
|
||||||
|
"sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==2.5"
|
||||||
|
},
|
||||||
"starlette": {
|
"starlette": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee",
|
"sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee",
|
||||||
@ -782,6 +1352,98 @@
|
|||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.12.2"
|
"version": "==4.12.2"
|
||||||
},
|
},
|
||||||
|
"tzdata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
|
||||||
|
"sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2'",
|
||||||
|
"version": "==2024.1"
|
||||||
|
},
|
||||||
|
"ujson": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e",
|
||||||
|
"sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b",
|
||||||
|
"sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6",
|
||||||
|
"sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7",
|
||||||
|
"sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9",
|
||||||
|
"sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd",
|
||||||
|
"sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569",
|
||||||
|
"sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f",
|
||||||
|
"sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51",
|
||||||
|
"sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20",
|
||||||
|
"sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1",
|
||||||
|
"sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf",
|
||||||
|
"sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc",
|
||||||
|
"sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e",
|
||||||
|
"sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a",
|
||||||
|
"sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539",
|
||||||
|
"sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27",
|
||||||
|
"sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165",
|
||||||
|
"sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126",
|
||||||
|
"sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1",
|
||||||
|
"sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816",
|
||||||
|
"sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64",
|
||||||
|
"sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8",
|
||||||
|
"sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e",
|
||||||
|
"sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287",
|
||||||
|
"sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3",
|
||||||
|
"sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb",
|
||||||
|
"sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0",
|
||||||
|
"sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043",
|
||||||
|
"sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557",
|
||||||
|
"sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e",
|
||||||
|
"sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21",
|
||||||
|
"sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d",
|
||||||
|
"sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd",
|
||||||
|
"sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0",
|
||||||
|
"sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337",
|
||||||
|
"sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753",
|
||||||
|
"sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804",
|
||||||
|
"sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f",
|
||||||
|
"sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f",
|
||||||
|
"sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5",
|
||||||
|
"sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5",
|
||||||
|
"sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1",
|
||||||
|
"sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00",
|
||||||
|
"sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2",
|
||||||
|
"sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050",
|
||||||
|
"sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e",
|
||||||
|
"sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4",
|
||||||
|
"sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8",
|
||||||
|
"sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996",
|
||||||
|
"sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6",
|
||||||
|
"sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1",
|
||||||
|
"sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f",
|
||||||
|
"sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1",
|
||||||
|
"sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4",
|
||||||
|
"sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b",
|
||||||
|
"sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88",
|
||||||
|
"sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518",
|
||||||
|
"sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5",
|
||||||
|
"sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770",
|
||||||
|
"sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4",
|
||||||
|
"sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a",
|
||||||
|
"sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76",
|
||||||
|
"sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe",
|
||||||
|
"sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988",
|
||||||
|
"sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1",
|
||||||
|
"sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5",
|
||||||
|
"sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b",
|
||||||
|
"sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7",
|
||||||
|
"sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8",
|
||||||
|
"sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc",
|
||||||
|
"sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a",
|
||||||
|
"sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720",
|
||||||
|
"sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3",
|
||||||
|
"sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b",
|
||||||
|
"sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9",
|
||||||
|
"sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1",
|
||||||
|
"sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==5.10.0"
|
||||||
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
|
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
|
||||||
@ -993,6 +1655,14 @@
|
|||||||
"sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"
|
"sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"
|
||||||
],
|
],
|
||||||
"version": "==12.0"
|
"version": "==12.0"
|
||||||
|
},
|
||||||
|
"xarray": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0b91e0bc4dc0296947947640fe31ec6e867ce258d2f7cbc10bedf4a6d68340c7",
|
||||||
|
"sha256:721a7394e8ec3d592b2d8ebe21eed074ac077dc1bb1bd777ce00e41700b4866c"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.9'",
|
||||||
|
"version": "==2024.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,9 @@
|
|||||||
|
import logging.config
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
|
|
||||||
PARAMETERS_DIR = Path('src/parameters')
|
LOCATION_PREFIX = Path('src')
|
||||||
|
PARAMETERS_DIR = LOCATION_PREFIX / 'parameters'
|
||||||
AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml'
|
AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml'
|
||||||
LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml'
|
LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml'
|
||||||
OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml'
|
OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml'
|
||||||
@ -12,8 +13,15 @@ OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml'
|
|||||||
cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')
|
cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')
|
||||||
OSM_CACHE_DIR = Path(cache_dir_string)
|
OSM_CACHE_DIR = Path(cache_dir_string)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(
|
import logging
|
||||||
level = logging.DEBUG,
|
import yaml
|
||||||
format = '%(asctime)s - %(name)s\t- %(levelname)s\t- %(message)s'
|
|
||||||
)
|
LOGGING_CONFIG = LOCATION_PREFIX / 'log_config.yaml'
|
||||||
|
config = yaml.safe_load(LOGGING_CONFIG.read_text())
|
||||||
|
|
||||||
|
logging.config.dictConfig(config)
|
||||||
|
|
||||||
|
# if we are in a debug session, set the log level to debug
|
||||||
|
if os.getenv('DEBUG', False):
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
34
backend/src/log_config.yaml
Normal file
34
backend/src/log_config.yaml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
version: 1
|
||||||
|
disable_existing_loggers: False
|
||||||
|
formatters:
|
||||||
|
simple:
|
||||||
|
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
handlers:
|
||||||
|
console:
|
||||||
|
class: rich.logging.RichHandler
|
||||||
|
formatter: simple
|
||||||
|
# access:
|
||||||
|
# class: logging.FileHandler
|
||||||
|
# filename: logs/access.log
|
||||||
|
# level: INFO
|
||||||
|
# formatter: simple
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
uvicorn.error:
|
||||||
|
level: INFO
|
||||||
|
handlers:
|
||||||
|
- console
|
||||||
|
propagate: no
|
||||||
|
# uvicorn.access:
|
||||||
|
# level: INFO
|
||||||
|
# handlers:
|
||||||
|
# - access
|
||||||
|
# propagate: no
|
||||||
|
root:
|
||||||
|
level: INFO
|
||||||
|
handlers:
|
||||||
|
- console
|
||||||
|
propagate: yes
|
@ -1,20 +1,24 @@
|
|||||||
from backend.src.example_optimizer import solve_optimization
|
import logging
|
||||||
# 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
|
|
||||||
from fastapi import FastAPI, Query, Body
|
from fastapi import FastAPI, Query, Body
|
||||||
|
|
||||||
|
from structs.landmark import Landmark
|
||||||
|
from structs.preferences import Preferences
|
||||||
|
from structs.linked_landmarks import LinkedLandmarks
|
||||||
|
from utils.landmarks_manager import LandmarkManager
|
||||||
|
from utils.optimizer import Optimizer
|
||||||
|
from utils.refiner import Refiner
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
manager = LandmarkManager()
|
manager = LandmarkManager()
|
||||||
|
optimizer = Optimizer()
|
||||||
# TODO: needs a global variable to store the landmarks accross function calls
|
refiner = Refiner(optimizer=optimizer)
|
||||||
# linked_tour = []
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/route/new")
|
@app.post("/route/new")
|
||||||
def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] = None) -> str:
|
def get_route(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] | None = None) -> str:
|
||||||
'''
|
'''
|
||||||
Main function to call the optimizer.
|
Main function to call the optimizer.
|
||||||
:param preferences: the preferences specified by the user as the post body
|
:param preferences: the preferences specified by the user as the post body
|
||||||
@ -28,13 +32,17 @@ def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float
|
|||||||
raise ValueError("Please provide the starting coordinates as a tuple of floats.")
|
raise ValueError("Please provide the starting coordinates as a tuple of floats.")
|
||||||
if end is None:
|
if end is None:
|
||||||
end = start
|
end = start
|
||||||
|
logger.info("No end coordinates provided. Using start=end.")
|
||||||
|
|
||||||
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)
|
start_landmark = Landmark(name='start', 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)
|
end_landmark = Landmark(name='end', type='finish', location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||||
|
|
||||||
# Generate the landmarks from the start location
|
# Generate the landmarks from the start location
|
||||||
landmarks, landmarks_short = LandmarkManager.get_landmark_lists(preferences=preferences, coordinates=start.location)
|
landmarks, landmarks_short = manager.generate_landmarks_list(
|
||||||
print([l.name for l in landmarks_short])
|
center_coordinates = start,
|
||||||
|
preferences = preferences
|
||||||
|
)
|
||||||
|
|
||||||
# insert start and finish to the landmarks list
|
# insert start and finish to the landmarks list
|
||||||
landmarks_short.insert(0, start_landmark)
|
landmarks_short.insert(0, start_landmark)
|
||||||
landmarks_short.append(end_landmark)
|
landmarks_short.append(end_landmark)
|
||||||
@ -44,14 +52,13 @@ def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float
|
|||||||
detour = 30 # minutes
|
detour = 30 # minutes
|
||||||
|
|
||||||
# First stage optimization
|
# First stage optimization
|
||||||
base_tour = solve_optimization(landmarks_short, max_walking_time*60, True)
|
base_tour = optimizer.solve_optimization(max_walking_time*60, landmarks_short)
|
||||||
|
|
||||||
# Second stage optimization
|
# Second stage optimization
|
||||||
# refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True)
|
refined_tour = refiner.refine_optimization(landmarks, base_tour, max_walking_time*60, detour)
|
||||||
|
|
||||||
# linked_tour = ...
|
linked_tour = LinkedLandmarks(refined_tour)
|
||||||
# return linked_tour[0].uuid
|
return linked_tour[0].uuid
|
||||||
return base_tour[0].uuid
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
detour_factor: 1.4
|
detour_factor: 1.4
|
||||||
|
detour_corridor_width: 200
|
||||||
average_walking_speed: 4.8
|
average_walking_speed: 4.8
|
||||||
max_landmarks: 7
|
max_landmarks: 7
|
||||||
|
@ -33,5 +33,6 @@ class Landmark(BaseModel) :
|
|||||||
return self.uuid.int
|
return self.uuid.int
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f'Landmark: [{self.name}, {self.location}, {self.attractiveness}]'
|
time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
|
||||||
|
return f'Landmark({self.type}): [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}]'
|
||||||
|
|
61
backend/src/structs/linked_landmarks.py
Normal file
61
backend/src/structs/linked_landmarks.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import uuid
|
||||||
|
from .landmark import Landmark
|
||||||
|
from utils.get_time_separation import get_time
|
||||||
|
|
||||||
|
class LinkedLandmarks:
|
||||||
|
"""
|
||||||
|
A list of landmarks that are linked together, e.g. in a route.
|
||||||
|
Each landmark serves as a node in the linked list, but since we expect these to be consumed through the rest API, a pythonic reference to the next landmark is not well suited. Instead we use the uuid of the next landmark to reference the next landmark in the list. This is not very efficient, but appropriate for the expected use case ("short" trips with onyl few landmarks).
|
||||||
|
"""
|
||||||
|
|
||||||
|
_landmarks = list[Landmark]
|
||||||
|
total_time = int
|
||||||
|
uuid = str
|
||||||
|
|
||||||
|
def __init__(self, data: list[Landmark] = None) -> None:
|
||||||
|
"""
|
||||||
|
Initialize a new LinkedLandmarks object. This expects an ORDERED list of landmarks, where the first landmark is the starting point and the last landmark is the end point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (list[Landmark], optional): The list of landmarks that are linked together. Defaults to None.
|
||||||
|
"""
|
||||||
|
self.uuid = uuid.uuid4()
|
||||||
|
self._landmarks = data if data else []
|
||||||
|
self._link_landmarks()
|
||||||
|
|
||||||
|
|
||||||
|
def _link_landmarks(self) -> None:
|
||||||
|
"""
|
||||||
|
Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes.
|
||||||
|
"""
|
||||||
|
self.total_time = 0
|
||||||
|
for i, landmark in enumerate(self._landmarks[:-1]):
|
||||||
|
landmark.next_uuid = self._landmarks[i + 1].uuid
|
||||||
|
time_to_next = get_time(landmark.location, self._landmarks[i + 1].location)
|
||||||
|
landmark.time_to_reach_next = time_to_next
|
||||||
|
self.total_time += time_to_next
|
||||||
|
|
||||||
|
self._landmarks[-1].next_uuid = None
|
||||||
|
self._landmarks[-1].time_to_reach_next = 0
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, index: int) -> Landmark:
|
||||||
|
return self._landmarks[index]
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"LinkedLandmarks, total time: {self.total_time} minutes, {len(self._landmarks)} stops: [{','.join([str(landmark) for landmark in self._landmarks])}]"
|
||||||
|
|
||||||
|
|
||||||
|
def asdict(self) -> dict:
|
||||||
|
"""
|
||||||
|
Convert the linked landmarks to a json serializable dictionary.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary representation of the linked landmarks.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'uuid': self.uuid,
|
||||||
|
'total_time': self.total_time,
|
||||||
|
'landmarks': [landmark.dict() for landmark in self._landmarks]
|
||||||
|
}
|
@ -1,30 +1,22 @@
|
|||||||
import pandas as pd
|
import logging
|
||||||
|
import yaml
|
||||||
|
|
||||||
from typing import List
|
from utils.landmarks_manager import LandmarkManager
|
||||||
from landmarks_manager import LandmarkManager
|
from utils.optimizer import Optimizer
|
||||||
from fastapi.encoders import jsonable_encoder
|
from utils.refiner import Refiner
|
||||||
|
from structs.landmark import Landmark
|
||||||
from optimizer import Optimizer
|
from structs.linked_landmarks import LinkedLandmarks
|
||||||
from refiner import Refiner
|
|
||||||
from structs.landmarks import Landmark
|
|
||||||
from structs.preferences import Preferences, Preference
|
from structs.preferences import Preferences, Preference
|
||||||
|
|
||||||
|
|
||||||
# Helper function to create a .txt file with results
|
logger = logging.getLogger(__name__)
|
||||||
def write_data(L: List[Landmark], file_name: str):
|
|
||||||
|
|
||||||
data = pd.DataFrame()
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
for landmark in L :
|
|
||||||
data[i] = jsonable_encoder(landmark)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
data.to_json(file_name, indent = 2, force_ascii=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> List[Landmark]:
|
|
||||||
|
def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> list[Landmark]:
|
||||||
manager = LandmarkManager()
|
manager = LandmarkManager()
|
||||||
|
optimizer = Optimizer()
|
||||||
|
refiner = Refiner(optimizer=optimizer)
|
||||||
|
|
||||||
|
|
||||||
preferences = Preferences(
|
preferences = Preferences(
|
||||||
@ -42,7 +34,7 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
|
|||||||
score = 5),
|
score = 5),
|
||||||
|
|
||||||
max_time_minute=180,
|
max_time_minute=180,
|
||||||
detour_tolerance_minute=0
|
detour_tolerance_minute=30
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create start and finish
|
# Create start and finish
|
||||||
@ -71,15 +63,18 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
|
|||||||
landmarks_short.append(finish)
|
landmarks_short.append(finish)
|
||||||
|
|
||||||
# First stage optimization
|
# First stage optimization
|
||||||
optimizer = Optimizer(max_time=preferences.max_time_minute, landmarks=landmarks_short)
|
base_tour = optimizer.solve_optimization(max_time=preferences.max_time_minute, landmarks=landmarks_short)
|
||||||
base_tour = optimizer.solve_optimization()
|
|
||||||
|
|
||||||
# Second stage using linear optimization
|
# Second stage using linear optimization
|
||||||
refiner = Refiner(max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute)
|
refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour, max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute)
|
||||||
refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour)
|
|
||||||
|
|
||||||
|
linked_tour = LinkedLandmarks(refined_tour)
|
||||||
|
logger.info(f"Optimized route: {linked_tour}")
|
||||||
|
|
||||||
return refined_tour
|
# with open('linked_tour.yaml', 'w') as f:
|
||||||
|
# yaml.dump(linked_tour.asdict(), f)
|
||||||
|
|
||||||
|
return linked_tour
|
||||||
|
|
||||||
|
|
||||||
#test(tuple((48.8344400, 2.3220540))) # Café Chez César
|
#test(tuple((48.8344400, 2.3220540))) # Café Chez César
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
import yaml
|
|
||||||
from typing import List, Tuple
|
|
||||||
from geopy.distance import geodesic
|
|
||||||
|
|
||||||
from structs.landmarks import Landmark
|
|
||||||
import constants
|
|
||||||
|
|
||||||
def get_time(p1: Tuple[float, float], p2: Tuple[float, float]) -> int :
|
|
||||||
"""
|
|
||||||
Calculate the time in minutes to travel from one location to another.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
p1 (Tuple[float, float]): Coordinates of the starting location.
|
|
||||||
p2 (Tuple[float, float]): Coordinates of the destination.
|
|
||||||
detour (float): Detour factor affecting the distance.
|
|
||||||
speed (float): Walking speed in kilometers per hour.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Time to travel from p1 to p2 in minutes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
|
||||||
parameters = yaml.safe_load(f)
|
|
||||||
detour_factor = parameters['detour_factor']
|
|
||||||
average_walking_speed = parameters['average_walking_speed']
|
|
||||||
|
|
||||||
# Compute the straight-line distance in km
|
|
||||||
if p1 == p2 :
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
dist = geodesic(p1, p2).kilometers
|
|
||||||
|
|
||||||
# Consider the detour factor for average cityto deterline walking distance (in km)
|
|
||||||
walk_dist = dist*detour_factor
|
|
||||||
|
|
||||||
# Time to walk this distance (in minutes)
|
|
||||||
walk_time = walk_dist/average_walking_speed*60
|
|
||||||
|
|
||||||
return round(walk_time)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Same as link_list but does it on a already ordered list
|
|
||||||
def link_list_simple(ordered_visit: List[Landmark])-> List[Landmark] :
|
|
||||||
|
|
||||||
L = []
|
|
||||||
j = 0
|
|
||||||
total_dist = 0
|
|
||||||
while j < len(ordered_visit)-1 :
|
|
||||||
elem = ordered_visit[j]
|
|
||||||
next = ordered_visit[j+1]
|
|
||||||
|
|
||||||
elem.next_uuid = next.uuid
|
|
||||||
d = get_time(elem.location, next.location)
|
|
||||||
elem.time_to_reach_next = d
|
|
||||||
if elem.name not in ['start', 'finish'] :
|
|
||||||
elem.must_do = True
|
|
||||||
L.append(elem)
|
|
||||||
j += 1
|
|
||||||
total_dist += d
|
|
||||||
|
|
||||||
L.append(next)
|
|
||||||
|
|
||||||
return L, total_dist
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Take the most important landmarks from the list
|
|
||||||
def take_most_important(landmarks: List[Landmark], N_important) -> List[Landmark] :
|
|
||||||
|
|
||||||
L = len(landmarks)
|
|
||||||
L_copy = []
|
|
||||||
L_clean = []
|
|
||||||
scores = [0]*len(landmarks)
|
|
||||||
names = []
|
|
||||||
name_id = {}
|
|
||||||
|
|
||||||
for i, elem in enumerate(landmarks) :
|
|
||||||
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])[-(N_important-L):]
|
|
||||||
|
|
||||||
for i, elem in enumerate(L_copy) :
|
|
||||||
if i in res :
|
|
||||||
L_clean.append(elem)
|
|
||||||
|
|
||||||
return L_clean
|
|
39
backend/src/utils/get_time_separation.py
Normal file
39
backend/src/utils/get_time_separation.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import yaml
|
||||||
|
from geopy.distance import geodesic
|
||||||
|
|
||||||
|
import constants
|
||||||
|
|
||||||
|
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
||||||
|
parameters = yaml.safe_load(f)
|
||||||
|
DETOUR_FACTOR = parameters['detour_factor']
|
||||||
|
AVERAGE_WALKING_SPEED = parameters['average_walking_speed']
|
||||||
|
|
||||||
|
|
||||||
|
def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int:
|
||||||
|
"""
|
||||||
|
Calculate the time in minutes to travel from one location to another.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p1 (Tuple[float, float]): Coordinates of the starting location.
|
||||||
|
p2 (Tuple[float, float]): Coordinates of the destination.
|
||||||
|
detour (float): Detour factor affecting the distance.
|
||||||
|
speed (float): Walking speed in kilometers per hour.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Time to travel from p1 to p2 in minutes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Compute the straight-line distance in km
|
||||||
|
if p1 == p2 :
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
dist = geodesic(p1, p2).kilometers
|
||||||
|
|
||||||
|
# Consider the detour factor for average cityto deterline walking distance (in km)
|
||||||
|
walk_dist = dist*DETOUR_FACTOR
|
||||||
|
|
||||||
|
# Time to walk this distance (in minutes)
|
||||||
|
walk_time = walk_dist/AVERAGE_WALKING_SPEED*60
|
||||||
|
|
||||||
|
return round(walk_time)
|
@ -10,8 +10,8 @@ config.put_throttle = 0
|
|||||||
config.maxlag = 0
|
config.maxlag = 0
|
||||||
|
|
||||||
from structs.preferences import Preferences, Preference
|
from structs.preferences import Preferences, Preference
|
||||||
from structs.landmarks import Landmark
|
from structs.landmark import Landmark
|
||||||
from utils import take_most_important
|
from .take_most_important import take_most_important
|
||||||
import constants
|
import constants
|
||||||
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
import yaml, logging
|
import yaml, logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from typing import List, Tuple
|
|
||||||
from scipy.optimize import linprog
|
from scipy.optimize import linprog
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from geopy.distance import geodesic
|
from geopy.distance import geodesic
|
||||||
|
|
||||||
from structs.landmarks import Landmark
|
from structs.landmark import Landmark
|
||||||
|
from .get_time_separation import get_time
|
||||||
import constants
|
import constants
|
||||||
|
|
||||||
|
|
||||||
@ -17,17 +17,13 @@ class Optimizer:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
landmarks: List[Landmark] # list of landmarks
|
|
||||||
max_time: int = None # max visit time (in minutes)
|
|
||||||
detour: int = None # accepted max detour time (in minutes)
|
detour: int = None # accepted max detour time (in minutes)
|
||||||
detour_factor: float # detour factor of straight line vs real distance in cities
|
detour_factor: float # detour factor of straight line vs real distance in cities
|
||||||
average_walking_speed: float # average walking speed of adult
|
average_walking_speed: float # average walking speed of adult
|
||||||
max_landmarks: int # max number of landmarks to visit
|
max_landmarks: int # max number of landmarks to visit
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, max_time: int, landmarks: List[Landmark]) :
|
def __init__(self) :
|
||||||
self.max_time = max_time
|
|
||||||
self.landmarks = landmarks
|
|
||||||
|
|
||||||
# load parameters from file
|
# load parameters from file
|
||||||
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
||||||
@ -37,25 +33,6 @@ class Optimizer:
|
|||||||
self.max_landmarks = parameters['max_landmarks']
|
self.max_landmarks = parameters['max_landmarks']
|
||||||
|
|
||||||
|
|
||||||
def print_res(self, L: List[Landmark]):
|
|
||||||
"""
|
|
||||||
Print the suggested order of landmarks to visit and log the total time walked.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
L (List[Landmark]): List of landmarks in the suggested visit order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.logger.info(f'The following order is suggested : ')
|
|
||||||
dist = 0
|
|
||||||
for elem in L :
|
|
||||||
if elem.time_to_reach_next is not None :
|
|
||||||
self.logger.info(f" {elem.name}, time to next = {elem.time_to_reach_next}")
|
|
||||||
dist += elem.time_to_reach_next
|
|
||||||
else :
|
|
||||||
self.logger.info(f" {elem.name}")
|
|
||||||
self.logger.info(f'Minutes walked : {dist}')
|
|
||||||
self.logger.info(f'Visited {len(L)-2} landmarks')
|
|
||||||
|
|
||||||
|
|
||||||
# Prevent the use of a particular solution
|
# Prevent the use of a particular solution
|
||||||
def prevent_config(self, resx):
|
def prevent_config(self, resx):
|
||||||
@ -63,10 +40,10 @@ class Optimizer:
|
|||||||
Prevent the use of a particular solution by adding constraints to the optimization.
|
Prevent the use of a particular solution by adding constraints to the optimization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
resx (List[float]): List of edge weights.
|
resx (list[float]): List of edge weights.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[int], List[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector.
|
Tuple[list[int], list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for i, elem in enumerate(resx):
|
for i, elem in enumerate(resx):
|
||||||
@ -101,7 +78,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector.
|
Tuple[np.ndarray, list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
l1 = [0]*L*L
|
l1 = [0]*L*L
|
||||||
@ -129,7 +106,7 @@ class Optimizer:
|
|||||||
resx (list): List of edge weights.
|
resx (list): List of edge weights.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[int], Optional[List[List[int]]]]: A tuple containing the visit order and a list of any detected circles.
|
Tuple[list[int], Optional[list[list[int]]]]: A tuple containing the visit order and a list of any detected circles.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# first round the results to have only 0-1 values
|
# first round the results to have only 0-1 values
|
||||||
@ -189,34 +166,8 @@ class Optimizer:
|
|||||||
return order, all_journeys_nodes
|
return order, all_journeys_nodes
|
||||||
|
|
||||||
|
|
||||||
def get_time(self, p1: Tuple[float, float], p2: Tuple[float, float]) -> int :
|
|
||||||
"""
|
|
||||||
Calculate the time in minutes to travel from one location to another.
|
|
||||||
|
|
||||||
Args:
|
def init_ub_dist(self, landmarks: list[Landmark], max_steps: int):
|
||||||
p1 (Tuple[float, float]): Coordinates of the starting location.
|
|
||||||
p2 (Tuple[float, float]): Coordinates of the destination.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Time to travel from p1 to p2 in minutes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Compute the straight-line distance in km
|
|
||||||
if p1 == p2 :
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
dist = geodesic(p1, p2).kilometers
|
|
||||||
|
|
||||||
# Consider the detour factor for average cityto deterline walking distance (in km)
|
|
||||||
walk_dist = dist*self.detour_factor
|
|
||||||
|
|
||||||
# Time to walk this distance (in minutes)
|
|
||||||
walk_time = walk_dist/self.average_walking_speed*60
|
|
||||||
|
|
||||||
return round(walk_time)
|
|
||||||
|
|
||||||
|
|
||||||
def init_ub_dist(self, landmarks: List[Landmark], max_steps: int):
|
|
||||||
"""
|
"""
|
||||||
Initialize the objective function coefficients and inequality constraints for the optimization problem.
|
Initialize the objective function coefficients and inequality constraints for the optimization problem.
|
||||||
|
|
||||||
@ -224,11 +175,11 @@ class Optimizer:
|
|||||||
The goal is to maximize the objective function subject to the constraints A*x < b and A_eq*x = b_eq.
|
The goal is to maximize the objective function subject to the constraints A*x < b and A_eq*x = b_eq.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (List[Landmark]): List of landmarks.
|
landmarks (list[Landmark]): List of landmarks.
|
||||||
max_steps (int): Maximum number of steps allowed.
|
max_steps (int): Maximum number of steps allowed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[float], List[float], List[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint.
|
Tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
|
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
|
||||||
@ -240,7 +191,7 @@ class Optimizer:
|
|||||||
dist_table = [0]*len(landmarks)
|
dist_table = [0]*len(landmarks)
|
||||||
c.append(-spot1.attractiveness)
|
c.append(-spot1.attractiveness)
|
||||||
for j, spot2 in enumerate(landmarks) :
|
for j, spot2 in enumerate(landmarks) :
|
||||||
t = self.get_time(spot1.location, spot2.location)
|
t = get_time(spot1.location, spot2.location)
|
||||||
dist_table[j] = t
|
dist_table[j] = t
|
||||||
closest = sorted(dist_table)[:22]
|
closest = sorted(dist_table)[:22]
|
||||||
for i, dist in enumerate(dist_table) :
|
for i, dist in enumerate(dist_table) :
|
||||||
@ -260,7 +211,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ones = [1]*L
|
ones = [1]*L
|
||||||
@ -287,7 +238,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
upper_ind = np.triu_indices(L,0,L)
|
upper_ind = np.triu_indices(L,0,L)
|
||||||
@ -318,7 +269,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[np.ndarray], List[int]]: Equality constraint coefficients and the right-hand side of the equality constraints.
|
Tuple[list[np.ndarray], list[int]]: Equality constraint coefficients and the right-hand side of the equality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
l = [0]*L*L
|
l = [0]*L*L
|
||||||
@ -333,15 +284,15 @@ class Optimizer:
|
|||||||
return [l], [0]
|
return [l], [0]
|
||||||
|
|
||||||
|
|
||||||
def respect_user_must_do(self, landmarks: List[Landmark]) :
|
def respect_user_must_do(self, landmarks: list[Landmark]) :
|
||||||
"""
|
"""
|
||||||
Generate constraints to ensure that landmarks marked as 'must_do' are included in the optimization.
|
Generate constraints to ensure that landmarks marked as 'must_do' are included in the optimization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (List[Landmark]): List of landmarks, where some are marked as 'must_do'.
|
landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_do'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
L = len(landmarks)
|
L = len(landmarks)
|
||||||
@ -359,15 +310,15 @@ class Optimizer:
|
|||||||
return A, b
|
return A, b
|
||||||
|
|
||||||
|
|
||||||
def respect_user_must_avoid(self, landmarks: List[Landmark]) :
|
def respect_user_must_avoid(self, landmarks: list[Landmark]) :
|
||||||
"""
|
"""
|
||||||
Generate constraints to ensure that landmarks marked as 'must_avoid' are skipped in the optimization.
|
Generate constraints to ensure that landmarks marked as 'must_avoid' are skipped in the optimization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (List[Landmark]): List of landmarks, where some are marked as 'must_avoid'.
|
landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_avoid'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
L = len(landmarks)
|
L = len(landmarks)
|
||||||
@ -394,7 +345,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
l_start = [1]*L + [0]*L*(L-1) # sets departures only for start (horizontal ones)
|
l_start = [1]*L + [0]*L*(L-1) # sets departures only for start (horizontal ones)
|
||||||
@ -422,7 +373,7 @@ class Optimizer:
|
|||||||
L (int): Number of landmarks.
|
L (int): Number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[np.ndarray, List[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
A = [0]*L*L
|
A = [0]*L*L
|
||||||
@ -443,16 +394,16 @@ class Optimizer:
|
|||||||
return A, b
|
return A, b
|
||||||
|
|
||||||
|
|
||||||
def link_list(self, order: List[int], landmarks: List[Landmark])->List[Landmark] :
|
def link_list(self, order: list[int], landmarks: list[Landmark])->list[Landmark] :
|
||||||
"""
|
"""
|
||||||
Compute the time to reach from each landmark to the next and create a list of landmarks with updated travel times.
|
Compute the time to reach from each landmark to the next and create a list of landmarks with updated travel times.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
order (List[int]): List of indices representing the order of landmarks to visit.
|
order (list[int]): List of indices representing the order of landmarks to visit.
|
||||||
landmarks (List[Landmark]): List of all landmarks.
|
landmarks (list[Landmark]): List of all landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]]: The updated linked list of landmarks with travel times
|
list[Landmark]]: The updated linked list of landmarks with travel times
|
||||||
"""
|
"""
|
||||||
|
|
||||||
L = []
|
L = []
|
||||||
@ -463,7 +414,7 @@ class Optimizer:
|
|||||||
next = landmarks[order[j+1]]
|
next = landmarks[order[j+1]]
|
||||||
|
|
||||||
# get attributes
|
# get attributes
|
||||||
elem.time_to_reach_next = self.get_time(elem.location, next.location)
|
elem.time_to_reach_next = get_time(elem.location, next.location)
|
||||||
elem.must_do = True
|
elem.must_do = True
|
||||||
elem.location = (round(elem.location[0], 5), round(elem.location[1], 5))
|
elem.location = (round(elem.location[0], 5), round(elem.location[1], 5))
|
||||||
elem.next_uuid = next.uuid
|
elem.next_uuid = next.uuid
|
||||||
@ -478,21 +429,28 @@ class Optimizer:
|
|||||||
|
|
||||||
|
|
||||||
# Main optimization pipeline
|
# Main optimization pipeline
|
||||||
def solve_optimization (self, hide_log=False) :
|
def solve_optimization(
|
||||||
|
self,
|
||||||
|
max_time: int,
|
||||||
|
landmarks: list[Landmark],
|
||||||
|
) -> list[Landmark]:
|
||||||
"""
|
"""
|
||||||
Main optimization pipeline to solve the landmark visiting problem.
|
Main optimization pipeline to solve the landmark visiting problem.
|
||||||
|
|
||||||
This method sets up and solves a linear programming problem with constraints to find an optimal tour of landmarks,
|
This method sets up and solves a linear programming problem with constraints to find an optimal tour of landmarks,
|
||||||
considering user-defined must-visit landmarks, start and finish points, and ensuring no cycles are present.
|
considering user-defined must-visit landmarks, start and finish points, and ensuring no cycles are present.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_time (int): Maximum time allowed for the tour in minutes.
|
||||||
|
landmarks (list[Landmark]): List of landmarks to visit.
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found.
|
list[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
L = len(self.landmarks)
|
L = len(landmarks)
|
||||||
|
|
||||||
# SET CONSTRAINTS FOR INEQUALITY
|
# SET CONSTRAINTS FOR INEQUALITY
|
||||||
c, A_ub, b_ub = self.init_ub_dist(self.landmarks, self.max_time) # Add the distances from each landmark to the other
|
c, A_ub, b_ub = self.init_ub_dist(landmarks, max_time) # Add the distances from each landmark to the other
|
||||||
A, b = self.respect_number(L) # Respect max number of visits (no more possible stops than landmarks).
|
A, b = self.respect_number(L) # Respect max number of visits (no more possible stops than landmarks).
|
||||||
A_ub = np.vstack((A_ub, A), dtype=np.int16)
|
A_ub = np.vstack((A_ub, A), dtype=np.int16)
|
||||||
b_ub += b
|
b_ub += b
|
||||||
@ -503,10 +461,10 @@ class Optimizer:
|
|||||||
|
|
||||||
# SET CONSTRAINTS FOR EQUALITY
|
# SET CONSTRAINTS FOR EQUALITY
|
||||||
A_eq, b_eq = self.init_eq_not_stay(L) # Force solution not to stay in same place
|
A_eq, b_eq = self.init_eq_not_stay(L) # Force solution not to stay in same place
|
||||||
A, b = self.respect_user_must_do(self.landmarks) # Check if there are user_defined must_see. Also takes care of start/goal
|
A, b = self.respect_user_must_do(landmarks) # Check if there are user_defined must_see. Also takes care of start/goal
|
||||||
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
||||||
b_eq += b
|
b_eq += b
|
||||||
A, b = self.respect_user_must_avoid(self.landmarks) # Check if there are user_defined must_see. Also takes care of start/goal
|
A, b = self.respect_user_must_avoid(landmarks) # Check if there are user_defined must_see. Also takes care of start/goal
|
||||||
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
||||||
b_eq += b
|
b_eq += b
|
||||||
A, b = self.respect_start_finish(L) # Force start and finish positions
|
A, b = self.respect_start_finish(L) # Force start and finish positions
|
||||||
@ -527,7 +485,6 @@ class Optimizer:
|
|||||||
raise ArithmeticError("No solution could be found, the problem is overconstrained. Please adapt your must_dos")
|
raise ArithmeticError("No solution could be found, the problem is overconstrained. Please adapt your must_dos")
|
||||||
|
|
||||||
# If there is a solution, we're good to go, just check for connectiveness
|
# If there is a solution, we're good to go, just check for connectiveness
|
||||||
else :
|
|
||||||
order, circles = self.is_connected(res.x)
|
order, circles = self.is_connected(res.x)
|
||||||
#nodes, edges = is_connected(res.x)
|
#nodes, edges = is_connected(res.x)
|
||||||
i = 0
|
i = 0
|
||||||
@ -555,20 +512,8 @@ class Optimizer:
|
|||||||
if i == timeout :
|
if i == timeout :
|
||||||
raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
|
raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
|
||||||
|
|
||||||
# Add the times to reach and stop optimizing
|
#sort the landmarks in the order of the solution
|
||||||
tour = self.link_list(order, self.landmarks)
|
tour = [landmarks[i] for i in order]
|
||||||
|
|
||||||
# logging
|
|
||||||
if not hide_log :
|
|
||||||
if i != 0 :
|
|
||||||
self.logger.info(f"Neded to recompute paths {i} times")
|
|
||||||
self.print_res(tour) # how to do better ?
|
|
||||||
self.logger.info(f"Total score : {int(-res.fun)}")
|
|
||||||
|
|
||||||
|
self.logger.debug(f"Re-optimized {i} times, score: {int(-res.fun)}")
|
||||||
return tour
|
return tour
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
|||||||
import yaml, logging
|
import yaml, logging
|
||||||
|
|
||||||
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
|
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
|
||||||
from typing import List, Tuple
|
|
||||||
from math import pi
|
from math import pi
|
||||||
|
|
||||||
from structs.landmarks import Landmark
|
from structs.landmark import Landmark
|
||||||
from optimizer import Optimizer
|
from . import take_most_important, get_time_separation
|
||||||
from utils import get_time, link_list_simple, take_most_important
|
from .optimizer import Optimizer
|
||||||
import constants
|
import constants
|
||||||
|
|
||||||
|
|
||||||
@ -15,31 +14,30 @@ class Refiner :
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
max_time: int = None # max visit time (in minutes)
|
|
||||||
detour: int = None # accepted max detour time (in minutes)
|
|
||||||
detour_factor: float # detour factor of straight line vs real distance in cities
|
detour_factor: float # detour factor of straight line vs real distance in cities
|
||||||
|
detour_corridor_width: float # width of the corridor around the path
|
||||||
average_walking_speed: float # average walking speed of adult
|
average_walking_speed: float # average walking speed of adult
|
||||||
max_landmarks: int # max number of landmarks to visit
|
max_landmarks: int # max number of landmarks to visit
|
||||||
|
optimizer: Optimizer # optimizer object
|
||||||
|
|
||||||
|
def __init__(self, optimizer: Optimizer) :
|
||||||
def __init__(self, max_time: int, detour: int) :
|
self.optimizer = optimizer
|
||||||
self.max_time = max_time
|
|
||||||
self.detour = detour
|
|
||||||
|
|
||||||
# load parameters from file
|
# load parameters from file
|
||||||
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
||||||
parameters = yaml.safe_load(f)
|
parameters = yaml.safe_load(f)
|
||||||
self.detour_factor = parameters['detour_factor']
|
self.detour_factor = parameters['detour_factor']
|
||||||
|
self.detour_corridor_width = parameters['detour_corridor_width']
|
||||||
self.average_walking_speed = parameters['average_walking_speed']
|
self.average_walking_speed = parameters['average_walking_speed']
|
||||||
self.max_landmarks = parameters['max_landmarks'] + 4
|
self.max_landmarks = parameters['max_landmarks'] + 4
|
||||||
|
|
||||||
|
|
||||||
def create_corridor(self, landmarks: List[Landmark], width: float) :
|
def create_corridor(self, landmarks: list[Landmark], width: float) :
|
||||||
"""
|
"""
|
||||||
Create a corridor around the path connecting the landmarks.
|
Create a corridor around the path connecting the landmarks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (List[Landmark]): the landmark path around which to create the corridor
|
landmarks (list[Landmark]): the landmark path around which to create the corridor
|
||||||
width (float): Width of the corridor in meters.
|
width (float): Width of the corridor in meters.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -54,12 +52,12 @@ class Refiner :
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def create_linestring(self, tour: List[Landmark])->LineString :
|
def create_linestring(self, tour: list[Landmark]) -> LineString :
|
||||||
"""
|
"""
|
||||||
Create a `LineString` object from a tour.
|
Create a `LineString` object from a tour.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tour (List[Landmark]): An ordered sequence of landmarks that represents the visiting order.
|
tour (list[Landmark]): An ordered sequence of landmarks that represents the visiting order.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
LineString: A `LineString` object representing the path through the landmarks.
|
LineString: A `LineString` object representing the path through the landmarks.
|
||||||
@ -79,7 +77,7 @@ class Refiner :
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
area (Polygon): The polygon defining the area.
|
area (Polygon): The polygon defining the area.
|
||||||
coordinates (Tuple[float, float]): The coordinates of the point to check.
|
coordinates (tuple[float, float]): The coordinates of the point to check.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the point is within the area, otherwise False.
|
bool: True if the point is within the area, otherwise False.
|
||||||
@ -89,13 +87,13 @@ class Refiner :
|
|||||||
|
|
||||||
|
|
||||||
# Function to determine if two landmarks are close to each other
|
# Function to determine if two landmarks are close to each other
|
||||||
def is_close_to(self, location1: Tuple[float], location2: Tuple[float]):
|
def is_close_to(self, location1: tuple[float], location2: tuple[float]):
|
||||||
"""
|
"""
|
||||||
Determine if two locations are close to each other by rounding their coordinates to 3 decimal places.
|
Determine if two locations are close to each other by rounding their coordinates to 3 decimal places.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
location1 (Tuple[float, float]): The coordinates of the first location.
|
location1 (tuple[float, float]): The coordinates of the first location.
|
||||||
location2 (Tuple[float, float]): The coordinates of the second location.
|
location2 (tuple[float, float]): The coordinates of the second location.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the locations are within 0.001 degrees of each other, otherwise False.
|
bool: True if the locations are within 0.001 degrees of each other, otherwise False.
|
||||||
@ -108,7 +106,7 @@ class Refiner :
|
|||||||
#return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3))
|
#return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3))
|
||||||
|
|
||||||
|
|
||||||
def rearrange(self, tour: List[Landmark]) -> List[Landmark]:
|
def rearrange(self, tour: list[Landmark]) -> list[Landmark]:
|
||||||
"""
|
"""
|
||||||
Rearrange landmarks to group nearby visits together.
|
Rearrange landmarks to group nearby visits together.
|
||||||
|
|
||||||
@ -116,10 +114,10 @@ class Refiner :
|
|||||||
while keeping 'start' and 'finish' landmarks in their original positions.
|
while keeping 'start' and 'finish' landmarks in their original positions.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tour (List[Landmark]): Ordered list of landmarks to be rearranged.
|
tour (list[Landmark]): Ordered list of landmarks to be rearranged.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]: The rearranged list of landmarks with grouped nearby visits.
|
list[Landmark]: The rearranged list of landmarks with grouped nearby visits.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
@ -137,7 +135,7 @@ class Refiner :
|
|||||||
return tour
|
return tour
|
||||||
|
|
||||||
|
|
||||||
def find_shortest_path_through_all_landmarks(self, landmarks: List[Landmark]) -> Tuple[List[Landmark], Polygon]:
|
def find_shortest_path_through_all_landmarks(self, landmarks: list[Landmark]) -> tuple[list[Landmark], Polygon]:
|
||||||
"""
|
"""
|
||||||
Find the shortest path through all landmarks using a nearest neighbor heuristic.
|
Find the shortest path through all landmarks using a nearest neighbor heuristic.
|
||||||
|
|
||||||
@ -146,10 +144,10 @@ class Refiner :
|
|||||||
polygon representing the path.
|
polygon representing the path.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (List[Landmark]): List of all landmarks including 'start' and 'finish'.
|
landmarks (list[Landmark]): list of all landmarks including 'start' and 'finish'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[Landmark], Polygon]: A tuple where the first element is the list of landmarks in the order they
|
tuple[list[Landmark], Polygon]: A tuple where the first element is the list of landmarks in the order they
|
||||||
should be visited, and the second element is a `Polygon` representing
|
should be visited, and the second element is a `Polygon` representing
|
||||||
the path connecting all landmarks.
|
the path connecting all landmarks.
|
||||||
"""
|
"""
|
||||||
@ -173,7 +171,7 @@ class Refiner :
|
|||||||
|
|
||||||
# Step 4: Use nearest neighbor heuristic to visit all landmarks
|
# Step 4: Use nearest neighbor heuristic to visit all landmarks
|
||||||
while unvisited_landmarks:
|
while unvisited_landmarks:
|
||||||
nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time(current_landmark.location, lm.location))
|
nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time_separation.get_time(current_landmark.location, lm.location))
|
||||||
path.append(nearest_landmark)
|
path.append(nearest_landmark)
|
||||||
coordinates.append(nearest_landmark.location)
|
coordinates.append(nearest_landmark.location)
|
||||||
current_landmark = nearest_landmark
|
current_landmark = nearest_landmark
|
||||||
@ -189,7 +187,7 @@ class Refiner :
|
|||||||
|
|
||||||
|
|
||||||
# Returns a list of minor landmarks around the planned path to enhance experience
|
# Returns a list of minor landmarks around the planned path to enhance experience
|
||||||
def get_minor_landmarks(self, all_landmarks: List[Landmark], visited_landmarks: List[Landmark], width: float) -> List[Landmark] :
|
def get_minor_landmarks(self, all_landmarks: list[Landmark], visited_landmarks: list[Landmark], width: float) -> list[Landmark] :
|
||||||
"""
|
"""
|
||||||
Identify landmarks within a specified corridor that have not been visited yet.
|
Identify landmarks within a specified corridor that have not been visited yet.
|
||||||
|
|
||||||
@ -197,12 +195,12 @@ class Refiner :
|
|||||||
within this corridor. It returns a list of these landmarks, excluding those already visited, sorted by their importance.
|
within this corridor. It returns a list of these landmarks, excluding those already visited, sorted by their importance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
all_landmarks (List[Landmark]): List of all available landmarks.
|
all_landmarks (list[Landmark]): list of all available landmarks.
|
||||||
visited_landmarks (List[Landmark]): List of landmarks that have already been visited.
|
visited_landmarks (list[Landmark]): list of landmarks that have already been visited.
|
||||||
width (float): Width of the corridor around the visited landmarks.
|
width (float): Width of the corridor around the visited landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]: List of important landmarks within the corridor that have not been visited yet.
|
list[Landmark]: list of important landmarks within the corridor that have not been visited yet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
second_order_landmarks = []
|
second_order_landmarks = []
|
||||||
@ -216,11 +214,11 @@ class Refiner :
|
|||||||
if self.is_in_area(area, landmark.location) and landmark.name not in visited_names:
|
if self.is_in_area(area, landmark.location) and landmark.name not in visited_names:
|
||||||
second_order_landmarks.append(landmark)
|
second_order_landmarks.append(landmark)
|
||||||
|
|
||||||
return take_most_important(second_order_landmarks, len(visited_landmarks))
|
return take_most_important.take_most_important(second_order_landmarks, len(visited_landmarks))
|
||||||
|
|
||||||
|
|
||||||
# Try fix the shortest path using shapely
|
# Try fix the shortest path using shapely
|
||||||
def fix_using_polygon(self, tour: List[Landmark])-> List[Landmark] :
|
def fix_using_polygon(self, tour: list[Landmark])-> list[Landmark] :
|
||||||
"""
|
"""
|
||||||
Improve the tour path using geometric methods to ensure it follows a more optimal shape.
|
Improve the tour path using geometric methods to ensure it follows a more optimal shape.
|
||||||
|
|
||||||
@ -229,10 +227,10 @@ class Refiner :
|
|||||||
beginning. It also checks if the final polygon is simple and rearranges the tour if necessary.
|
beginning. It also checks if the final polygon is simple and rearranges the tour if necessary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tour (List[Landmark]): List of landmarks representing the current tour path.
|
tour (list[Landmark]): list of landmarks representing the current tour path.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]: Refined list of landmarks in the order of visit to produce a better tour path.
|
list[Landmark]: Refined list of landmarks in the order of visit to produce a better tour path.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
coords = []
|
coords = []
|
||||||
@ -261,7 +259,7 @@ class Refiner :
|
|||||||
xs.reverse()
|
xs.reverse()
|
||||||
ys.reverse()
|
ys.reverse()
|
||||||
|
|
||||||
better_tour = [] # List of ordered visit
|
better_tour = [] # list of ordered visit
|
||||||
name_index = {} # Maps the name of a landmark to its index in the concave polygon
|
name_index = {} # Maps the name of a landmark to its index in the concave polygon
|
||||||
|
|
||||||
# Loop through the polygon and generate the better (ordered) tour
|
# Loop through the polygon and generate the better (ordered) tour
|
||||||
@ -285,27 +283,35 @@ class Refiner :
|
|||||||
return better_tour
|
return better_tour
|
||||||
|
|
||||||
|
|
||||||
# Second stage of the optimization. Use linear programming again to refine the path
|
def refine_optimization(
|
||||||
def refine_optimization(self, all_landmarks: List[Landmark], base_tour: List[Landmark]) -> List[Landmark] :
|
self,
|
||||||
|
all_landmarks: list[Landmark],
|
||||||
|
base_tour: list[Landmark],
|
||||||
|
max_time: int,
|
||||||
|
detour: int
|
||||||
|
) -> list[Landmark]:
|
||||||
"""
|
"""
|
||||||
Refine the initial tour path by considering additional minor landmarks and optimizing the path.
|
This is the second stage of the optimization. It refines the initial tour path by considering additional minor landmarks and optimizes the path.
|
||||||
|
|
||||||
This method evaluates the need for further optimization based on the initial tour. If a detour is required, it adds
|
This method evaluates the need for further optimization based on the initial tour. If a detour is required
|
||||||
minor landmarks around the initial predicted path and solves a new optimization problem to find a potentially better
|
it adds minor landmarks around the initial predicted path and solves a new optimization problem to find a potentially better
|
||||||
tour. It then links the new tour and adjusts it using a nearest neighbor heuristic and polygon-based methods to
|
tour. It then links the new tour and adjusts it using a nearest neighbor heuristic and polygon-based methods to
|
||||||
ensure a valid path. The final tour is chosen based on the shortest distance.
|
ensure a valid path. The final tour is chosen based on the shortest distance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
all_landmarks (list[Landmark]): The full list of landmarks available for the optimization.
|
||||||
|
base_tour (list[Landmark]): The initial tour path to be refined.
|
||||||
|
max_time (int): The maximum time available for the tour in minutes.
|
||||||
|
detour (int): The maximum detour time allowed for the tour in minutes.
|
||||||
Returns:
|
Returns:
|
||||||
List[Landmark]: The refined list of landmarks representing the optimized tour path.
|
list[Landmark]: The refined list of landmarks representing the optimized tour path.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# No need to refine if no detour is taken
|
# No need to refine if no detour is taken
|
||||||
# if detour == 0 :
|
if detour == 0:
|
||||||
if False :
|
return base_tour
|
||||||
new_tour = base_tour
|
|
||||||
|
|
||||||
else :
|
minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width)
|
||||||
minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, 200)
|
|
||||||
|
|
||||||
self.logger.info(f"Using {len(minor_landmarks)} minor landmarks around the predicted path")
|
self.logger.info(f"Using {len(minor_landmarks)} minor landmarks around the predicted path")
|
||||||
|
|
||||||
@ -314,18 +320,15 @@ class Refiner :
|
|||||||
full_set.append(base_tour[-1]) # add finish back
|
full_set.append(base_tour[-1]) # add finish back
|
||||||
|
|
||||||
# get a new tour
|
# get a new tour
|
||||||
optimizer = Optimizer(self.max_time + self.detour, full_set)
|
new_tour = self.optimizer.solve_optimization(
|
||||||
new_tour = optimizer.solve_optimization(hide_log=True)
|
max_time = max_time + detour,
|
||||||
|
landmarks = full_set
|
||||||
|
)
|
||||||
|
|
||||||
if new_tour is None:
|
if new_tour is None:
|
||||||
|
self.logger.warning("No solution found for the refined tour. Returning the initial tour.")
|
||||||
new_tour = base_tour
|
new_tour = base_tour
|
||||||
|
|
||||||
# Link the new tour
|
|
||||||
new_tour, new_dist = link_list_simple(new_tour)
|
|
||||||
|
|
||||||
# If the tour contains only one landmark, return
|
|
||||||
if len(new_tour) < 4 :
|
|
||||||
return new_tour
|
|
||||||
|
|
||||||
# Find shortest path using the nearest neighbor heuristic
|
# Find shortest path using the nearest neighbor heuristic
|
||||||
better_tour, better_poly = self.find_shortest_path_through_all_landmarks(new_tour)
|
better_tour, better_poly = self.find_shortest_path_through_all_landmarks(new_tour)
|
||||||
@ -334,18 +337,4 @@ class Refiner :
|
|||||||
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
|
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
|
||||||
better_tour = self.fix_using_polygon(better_tour)
|
better_tour = self.fix_using_polygon(better_tour)
|
||||||
|
|
||||||
# Link the tour again
|
return better_tour
|
||||||
better_tour, better_dist = link_list_simple(better_tour)
|
|
||||||
|
|
||||||
# Choose the better tour depending on walked distance
|
|
||||||
if new_dist < better_dist :
|
|
||||||
final_tour = new_tour
|
|
||||||
else :
|
|
||||||
final_tour = better_tour
|
|
||||||
|
|
||||||
self.logger.info("Refined tour (result of second stage optimization): ")
|
|
||||||
optimizer.print_res(final_tour)
|
|
||||||
|
|
||||||
return final_tour
|
|
||||||
|
|
||||||
|
|
38
backend/src/utils/take_most_important.py
Normal file
38
backend/src/utils/take_most_important.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from structs.landmark import Landmark
|
||||||
|
|
||||||
|
def take_most_important(landmarks: list[Landmark], N_important) -> list[Landmark] :
|
||||||
|
L = len(landmarks)
|
||||||
|
L_copy = []
|
||||||
|
L_clean = []
|
||||||
|
scores = [0]*len(landmarks)
|
||||||
|
names = []
|
||||||
|
name_id = {}
|
||||||
|
|
||||||
|
for i, elem in enumerate(landmarks) :
|
||||||
|
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])[-(N_important-L):]
|
||||||
|
|
||||||
|
for i, elem in enumerate(L_copy) :
|
||||||
|
if i in res :
|
||||||
|
L_clean.append(elem)
|
||||||
|
|
||||||
|
return L_clean
|
Loading…
x
Reference in New Issue
Block a user