diff --git a/.gitea/workflows/backed_build-image.yaml b/.gitea/workflows/backend_build-image.yaml similarity index 95% rename from .gitea/workflows/backed_build-image.yaml rename to .gitea/workflows/backend_build-image.yaml index 5542a1f..1552c03 100644 --- a/.gitea/workflows/backed_build-image.yaml +++ b/.gitea/workflows/backend_build-image.yaml @@ -2,6 +2,8 @@ on: pull_request: branches: - main + paths: + - backend/** name: Build and push docker image diff --git a/backend/Dockerfile b/backend/Dockerfile index 79dbe76..056c3a0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,6 +6,12 @@ COPY Pipfile Pipfile.lock . RUN pip install pipenv RUN pipenv install --deploy --system -COPY . /src +COPY src src -CMD ["pipenv", "run", "python", "/app/src/main.py"] +EXPOSE 8000 + +# Set environment variables used by the deployment. These can be overridden by the user using this image. +ENV NUM_WORKERS=1 +ENV OSM_CACHE_DIR=/cache + +CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS'] diff --git a/backend/Pipfile b/backend/Pipfile index 2757979..4197132 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -7,7 +7,6 @@ name = "pypi" numpy = "*" scipy = "*" fastapi = "*" -osmpythontools = "*" pydantic = "*" shapely = "*" networkx = "*" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index d3b6eef..d661287 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -30,14 +30,6 @@ "markers": "python_version >= '3.8'", "version": "==4.4.0" }, - "beautifulsoup4": { - "hashes": [ - "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", - "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" - ], - "markers": "python_full_version >= '3.6.0'", - "version": "==4.12.3" - }, "certifi": { "hashes": [ "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", @@ -142,6 +134,102 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.3.2" }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -150,64 +238,6 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, - "contourpy": { - "hashes": [ - "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2", - "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9", - "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9", - "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4", - "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce", - "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7", - "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f", - "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922", - "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4", - "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e", - "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b", - "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619", - "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205", - "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480", - "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965", - "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c", - "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd", - "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5", - "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f", - "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc", - "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec", - "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd", - "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b", - "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9", - "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe", - "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce", - "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609", - "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8", - "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0", - "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f", - "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8", - "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b", - "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364", - "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040", - "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f", - "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083", - "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df", - "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba", - "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445", - "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da", - "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3", - "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72", - "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02", - "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985" - ], - "markers": "python_version >= '3.9'", - "version": "==1.2.1" - }, - "cycler": { - "hashes": [ - "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", - "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, "dnspython": { "hashes": [ "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", @@ -249,7 +279,7 @@ "markers": "python_version >= '3.8'", "version": "==0.0.4" }, - "fonttools": { + "geopandas": { "hashes": [ "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122", "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397", @@ -403,264 +433,6 @@ "markers": "python_version >= '3.7'", "version": "==3.1.4" }, - "kiwisolver": { - "hashes": [ - "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf", - "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", - "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", - "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", - "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046", - "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", - "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", - "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71", - "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee", - "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", - "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9", - "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", - "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985", - "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea", - "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", - "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89", - "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", - "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", - "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712", - "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342", - "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", - "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958", - "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d", - "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", - "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130", - "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", - "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898", - "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b", - "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f", - "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265", - "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93", - "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929", - "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635", - "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709", - "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", - "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb", - "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a", - "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920", - "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e", - "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544", - "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", - "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390", - "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77", - "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", - "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff", - "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", - "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", - "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", - "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c", - "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", - "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", - "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", - "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc", - "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a", - "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901", - "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", - "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", - "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", - "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad", - "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", - "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29", - "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", - "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250", - "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d", - "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3", - "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54", - "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f", - "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", - "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da", - "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", - "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", - "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523", - "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", - "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205", - "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3", - "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4", - "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", - "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", - "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb", - "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced", - "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd", - "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0", - "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", - "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18", - "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", - "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", - "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333", - "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b", - "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", - "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126", - "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", - "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09", - "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", - "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", - "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7", - "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", - "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9", - "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", - "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", - "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", - "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6", - "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", - "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892", - "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f" - ], - "markers": "python_version >= '3.7'", - "version": "==1.4.5" - }, - "lxml": { - "hashes": [ - "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3", - "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a", - "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0", - "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b", - "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f", - "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6", - "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73", - "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d", - "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad", - "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b", - "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a", - "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5", - "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab", - "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316", - "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6", - "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df", - "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca", - "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264", - "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8", - "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f", - "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b", - "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3", - "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5", - "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed", - "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab", - "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5", - "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726", - "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d", - "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632", - "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706", - "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8", - "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472", - "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835", - "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf", - "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db", - "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d", - "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545", - "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9", - "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be", - "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe", - "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905", - "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438", - "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db", - "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776", - "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c", - "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed", - "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd", - "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484", - "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d", - "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6", - "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30", - "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182", - "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61", - "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425", - "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb", - "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1", - "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511", - "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e", - "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207", - "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b", - "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585", - "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56", - "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391", - "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85", - "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147", - "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18", - "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1", - "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa", - "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48", - "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3", - "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184", - "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67", - "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7", - "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34", - "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706", - "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8", - "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c", - "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115", - "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009", - "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466", - "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526", - "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d", - "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525", - "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14", - "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3", - "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0", - "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b", - "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1", - "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f", - "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf", - "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf", - "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0", - "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b", - "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff", - "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88", - "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2", - "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40", - "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716", - "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2", - "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2", - "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a", - "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734", - "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87", - "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48", - "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36", - "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b", - "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07", - "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c", - "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573", - "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001", - "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9", - "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3", - "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce", - "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3", - "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04", - "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927", - "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083", - "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d", - "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32", - "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9", - "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f", - "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2", - "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c", - "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d", - "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393", - "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8", - "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6", - "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66", - "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5", - "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97", - "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196", - "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836", - "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae", - "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297", - "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421", - "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6", - "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981", - "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30", - "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30", - "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f", - "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324", - "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b" - ], - "markers": "python_version >= '3.6'", - "version": "==5.2.2" - }, "markdown-it-py": { "hashes": [ "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", @@ -874,10 +646,12 @@ }, "osmpythontools": { "hashes": [ - "sha256:13ff721f760fdad5dd78b4d1461d286b78bba96ee151a7301ee8c11a0c258be9" + "sha256:22548d86d68d36edff3cf9ab76c45745cda86a4ea0b28442e107d6b42992a426", + "sha256:ac67bea77b521941af648ef641ae1d006101948d1112475c256ea23ef31b426a" ], "index": "pypi", - "version": "==0.3.5" + "markers": "python_version >= '3.8'", + "version": "==1.9.3" }, "packaging": { "hashes": [ @@ -1121,13 +895,70 @@ "markers": "python_version >= '3.8'", "version": "==2.18.0" }, - "pyparsing": { + "pyogrio": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:019731a856a9abfe909e86f50eb13f8362f6742337caf757c54b7c8acfe75b89", + "sha256:083351b258b3e08b6c6085dac560bd321b68de5cb4a66229095da68d5f3d696b", + "sha256:13642608a1cd67797ae8b5d792b0518d8ef3eb76506c8232ab5eaa1ea1159dff", + "sha256:17420febc17651876d5140b54b24749aa751d482b5f9ef6267b8053e6e962876", + "sha256:1a495ca4fb77c69595747dd688f8f17bb7d2ea9cd86603aa71c7fc98cc8b4174", + "sha256:2829615cf58b1b24a9f96fea42abedaa1a800dd351c67374cc2f6341138608f3", + "sha256:2e98913fa183f7597c609e774820a149e9329fd2a0f8d33978252fbd00ae87e6", + "sha256:2f2ec57ab74785db9c2bf47c0a6731e5175595a13f8253f06fa84136adb310a9", + "sha256:30cbeeaedb9bced7012487e7438919aa0c7dfba18ac3d4315182b46eb3139b9d", + "sha256:3a2fcaa269031dbbc8ebd91243c6452c5d267d6df939c008ab7533413c9cf92d", + "sha256:3f964002d445521ad5b8e732a6b5ef0e2d2be7fe566768e5075c1d71398da64a", + "sha256:4a289584da6df7ca318947301fe0ba9177e7f863f63110e087c80ac5f3658de8", + "sha256:4da0b9deb380bd9a200fee13182c4f95b02b4c554c923e2e0032f32aaf1439ed", + "sha256:4e0f90a6c3771ee1f1fea857778b4b6a1b64000d851b819f435f9091b3c38c60", + "sha256:6a6fa2e8cf95b3d4a7c0fac48bce6e5037579e28d3eb33b53349d6e11f15e5a8", + "sha256:6dc94a67163218581c7df275223488ac9b31dc582ccd756da607c3338908566c", + "sha256:796e4f6a4e769b2eb6fea9a10546ea4bdee16182d1e29802b4d6349363c3c1d7", + "sha256:7fcafed24371fe6e23bcf5abebbb29269f8d79915f1dd818ac85453657ea714a", + "sha256:9440466c0211ac81f3417f274da5903f15546b486f76b2f290e74a56aaf0e737", + "sha256:959022f3ad04053f8072dc9a2ad110c46edd9e4f92352061ba835fc91df3ca96", + "sha256:d668cb10f2bf6ccd7c402f91e8b06290722dd09dbe265ae95b2c13db29ebeba0", + "sha256:e38c3c6d37cf2cc969407e4d051dcb507cfd948eb26c7b0840c4f7d7d4a71bd4", + "sha256:f47c9b6818cc0f420015b672d5dcc488530a5ee63e5ba35a184957b21ea3922a", + "sha256:f5d80eb846be4fc4e642cbedc1ed0c143e8d241653382ecc76a7620bbd2a5c3a", + "sha256:f8bf193269ea9d347ac3ddada960a59f1ab2e4a5c009be95dc70e6505346b2fc", + "sha256:fb04bd80964428491951766452f0071b0bc37c7d38c45ef02502dbd83e5d74a0" ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.1.2" + "markers": "python_version >= '3.8'", + "version": "==0.9.0" + }, + "pyproj": { + "hashes": [ + "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3", + "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746", + "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb", + "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f", + "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2", + "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a", + "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf", + "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b", + "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe", + "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae", + "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f", + "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110", + "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915", + "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8", + "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0", + "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35", + "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf", + "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132", + "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877", + "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4", + "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f", + "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e", + "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85", + "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882", + "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1", + "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f", + "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.6.1" }, "python-dateutil": { "hashes": [ @@ -1340,14 +1171,6 @@ "markers": "python_version >= '3.7'", "version": "==1.3.1" }, - "soupsieve": { - "hashes": [ - "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690", - "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7" - ], - "markers": "python_version >= '3.8'", - "version": "==2.5" - }, "starlette": { "hashes": [ "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", @@ -1675,14 +1498,6 @@ "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" ], "version": "==12.0" - }, - "xarray": { - "hashes": [ - "sha256:0b91e0bc4dc0296947947640fe31ec6e867ce258d2f7cbc10bedf4a6d68340c7", - "sha256:721a7394e8ec3d592b2d8ebe21eed074ac077dc1bb1bd777ce00e41700b4866c" - ], - "markers": "python_version >= '3.9'", - "version": "==2024.6.0" } }, "develop": {} diff --git a/backend/src/amenities/shopping.am b/backend/src/amenities/shopping.am deleted file mode 100644 index 14504a0..0000000 --- a/backend/src/amenities/shopping.am +++ /dev/null @@ -1,2 +0,0 @@ -'shop'='department_store' -'shop'='mall' \ No newline at end of file diff --git a/backend/src/constants.py b/backend/src/constants.py new file mode 100644 index 0000000..d5b7b5a --- /dev/null +++ b/backend/src/constants.py @@ -0,0 +1,19 @@ +from pathlib import Path +import os +import logging + +PARAMETERS_DIR = Path('src/parameters') +AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml' +LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml' +OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' + + + +cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') +OSM_CACHE_DIR = Path(cache_dir_string) + +logger = logging.getLogger(__name__) +logging.basicConfig( + level = logging.INFO, + format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) diff --git a/backend/src/example_landmarks_manager.py b/backend/src/example_landmarks_manager.py new file mode 100644 index 0000000..30df6dc --- /dev/null +++ b/backend/src/example_landmarks_manager.py @@ -0,0 +1,151 @@ +import yaml +import logging +from OSMPythonTools import cachingStrategy, overpass +from structs.landmarks import Landmark, LandmarkType +from structs.preferences import Preferences, Preference +import constants + +SIGHTSEEING = LandmarkType(landmark_type='sightseeing') +NATURE = LandmarkType(landmark_type='nature') +SHOPPING = LandmarkType(landmark_type='shopping') + + +class LandmarkManager: + logger = logging.getLogger(__name__) + + def __init__(self) -> None: + strategy = cachingStrategy.JSON(cacheDir=constants.OSM_CACHE_DIR) + self.query_builder = overpass.Overpass() + with constants.AMENITY_SELECTORS_PATH.open('r') as f: + self.amenity_selectors = yaml.safe_load(f) + with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: + self.parameters = yaml.safe_load(f) + # max_distance = parameters['city_bbox_side'] + + + def get_landmark_lists(self, preferences: Preferences, center_coordinates: tuple[float, float]) -> tuple[list[Landmark], list[Landmark]]: + ''' + Generate a list of landmarks based on the preferences of the user and the center (ie. start) coordinates. + The list is then used by the pathfinding algorithm to generate a path that goes through the most interesting landmarks. + :param preferences: the preferences specified by the user + :param center_coordinates: the coordinates of the starting point + ''' + + L = [] + + # List for sightseeing + if preferences.sightseeing.score != 0: + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['church_coeff']) + L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L1, preferences.sightseeing) + L += L1 + + # List for nature + if preferences.nature.score != 0: + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['park_coeff']) + L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L2, preferences.nature) + L += L2 + + # List for shopping + if preferences.shopping.score != 0: + score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff'])) + L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, center_coordinates, self.parameters['city_bbox_side'], score_func) + self.correct_score(L3, preferences.shopping) + L += L3 + + # remove duplicates + L = list(set(L)) + L_constrained = self.take_most_important(L, self.parameters['N_important']) + self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.') + return L, L_constrained + + + + # Take the most important landmarks from the list + def take_most_important(self, landmarks: list[Landmark], n_max: int) -> list[Landmark]: + + landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True) + return landmarks_sorted[:n_max] + + + + # Correct the score of a list of landmarks by taking into account preference settings + def correct_score(self, L: list[Landmark], preference: Preference): + + if len(L) == 0 : + return + + if L[0].type != preference.type : + raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {L[0].name}") + + for elem in L : + elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation + + + # Function to count elements within a certain radius of a location + def count_elements_within_radius(self, point: Point, radius: int) -> int: + + center_coordinates = (point.x, point.y) + try: + landmarks = ox.features_from_point( + center_point = center_coordinates, + dist = radius, + tags = {'building': True} # this is a common tag to give an estimation of the number of elements in the area + ) + return len(landmarks) + except ox._errors.InsufficientResponseError: + return 0 + + + + + def fetch_landmarks( + self, + amenity_selectors: list[dict], + landmarktype: LandmarkType, + center_coordinates: tuple[float, float], + distance: int, + score_function: callable + ) -> list[Landmark]: + + landmarks = ox.features_from_point( + center_point = center_coordinates, + dist = distance, + tags = amenity_selectors + ) + self.logger.info(f'Fetched {len(landmarks)} landmarks around {center_coordinates}.') + + # cleanup the list + # remove rows where name is None + landmarks = landmarks[landmarks['name'].notna()] + # TODO: remove rows that are part of another building + + ret_landmarks = [] + for element, description in landmarks.iterrows(): + osm_type = element[0] + osm_id = element[1] + location = description['geometry'] + n_tags = len(description['nodes']) if type(description['nodes']) == list else 1 + + if type(location) == Point: + location = location + elif type(location) == Polygon or type(location) == MultiPolygon: + location = location.centroid + elif type(location) == LineString: + location = location.interpolate(location.length/2) + + score = score_function(location, n_tags) + landmark = Landmark( + name = description['name'], + type = landmarktype, + location = (location.x, location.y), + osm_type = osm_type, + osm_id = osm_id, + attractiveness = score, + must_do = False, + n_tags = n_tags + ) + ret_landmarks.append(landmark) + + return ret_landmarks diff --git a/backend/src/optimizer.py b/backend/src/example_optimizer.py similarity index 92% rename from backend/src/optimizer.py rename to backend/src/example_optimizer.py index 3d2ac29..922af99 100644 --- a/backend/src/optimizer.py +++ b/backend/src/example_optimizer.py @@ -1,13 +1,12 @@ import numpy as np -import json, os +import yaml from typing import List, Tuple from scipy.optimize import linprog from math import radians, sin, cos, acos -from shapely import Polygon from structs.landmarks import Landmark - +import constants # Function to print the result def print_res(L: List[Landmark], L_tot): @@ -161,10 +160,11 @@ def get_distance(p1: Tuple[float, float], p2: Tuple[float, float], detour: float # We want to maximize the sightseeing : max(c) st. A*x < b and A_eq*x = b_eq def init_ub_dist(landmarks: List[Landmark], max_steps: int): - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour = parameters['detour factor'] - speed = parameters['average walking speed'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] # Objective function coefficients. a*x1 + b*x2 + c*x3 + ... c = [] @@ -194,9 +194,9 @@ def respect_number(L:int, A_ub, b_ub): b_ub.append(1) # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - max_landmarks = parameters['max landmarks'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] A_ub = np.vstack((A_ub, ones*L)) b_ub.append(max_landmarks+1) @@ -300,13 +300,14 @@ def respect_order(N: int, A_eq, b_eq): # Computes the time to reach from each landmark to the next -def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] : +def link_list(order: List[int], landmarks: List[Landmark]) -> List[Landmark]: # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] j = 0 @@ -329,10 +330,11 @@ def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] : def link_list_simple(ordered_visit: List[Landmark])-> List[Landmark] : # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] j = 0 diff --git a/backend/src/example_refiner.py b/backend/src/example_refiner.py new file mode 100644 index 0000000..4b1c306 --- /dev/null +++ b/backend/src/example_refiner.py @@ -0,0 +1,303 @@ +from collections import defaultdict +from heapq import heappop, heappush +from itertools import permutations +import os +import yaml + +from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull +from typing import List, Tuple +from math import pi + +from structs.landmarks import Landmark +from landmarks_manager import take_most_important +from backend.src.example_optimizer import solve_optimization, link_list_simple, print_res, get_distance +import constants + + +# Create corridor from tour +def create_corridor(landmarks: List[Landmark], width: float) : + + corrected_width = (180*width)/(6371000*pi) + + path = create_linestring(landmarks) + obj = buffer(path, corrected_width, join_style="mitre", cap_style="square", mitre_limit=2) + + return obj + + +# Create linestring from tour +def create_linestring(landmarks: List[Landmark])->List[Point] : + + points = [] + + for landmark in landmarks : + points.append(Point(landmark.location)) + + return LineString(points) + + +# Check if some coordinates are in area. Used for the corridor +def is_in_area(area: Polygon, coordinates) -> bool : + point = Point(coordinates) + return point.within(area) + + +# Function to determine if two landmarks are close to each other +def is_close_to(location1: Tuple[float], location2: Tuple[float]): + """Determine if two locations are close by rounding their coordinates to 3 decimals.""" + absx = abs(location1[0] - location2[0]) + absy = abs(location1[1] - location2[1]) + + return absx < 0.001 and absy < 0.001 + #return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3)) + + +# Rearrange some landmarks in the order of visit to group visit +def rearrange(landmarks: List[Landmark]) -> List[Landmark]: + + i = 1 + while i < len(landmarks): + j = i+1 + while j < len(landmarks): + if is_close_to(landmarks[i].location, landmarks[j].location) and landmarks[i].name not in ['start', 'finish'] and landmarks[j].name not in ['start', 'finish']: + # If they are not adjacent, move the j-th element to be adjacent to the i-th element + if j != i + 1: + landmarks.insert(i + 1, landmarks.pop(j)) + break # Move to the next i-th element after rearrangement + j += 1 + i += 1 + + return landmarks + + + # Step 1: Build the graph + graph = defaultdict(list) + for i in range(len(landmarks)): + for j in range(len(landmarks)): + if i != j: + distance = get_distance(landmarks[i].location, landmarks[j].location, detour, speed)[1] + graph[i].append((distance, j)) + + # Step 2: Dijkstra's algorithm to find the shortest path from start to finish + start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start') + finish_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'finish') + + distances = {i: float('inf') for i in range(len(landmarks))} + previous_nodes = {i: None for i in range(len(landmarks))} + distances[start_idx] = 0 + priority_queue = [(0, start_idx)] + + while priority_queue: + current_distance, current_index = heappop(priority_queue) + + if current_distance > distances[current_index]: + continue + + for neighbor_distance, neighbor_index in graph[current_index]: + distance = current_distance + neighbor_distance + + if distance < distances[neighbor_index]: + distances[neighbor_index] = distance + previous_nodes[neighbor_index] = current_index + heappush(priority_queue, (distance, neighbor_index)) + + # Step 3: Backtrack from finish to start to find the path + path = [] + current_index = finish_idx + while current_index is not None: + path.append(landmarks[current_index]) + current_index = previous_nodes[current_index] + path.reverse() + + return path +""" +def total_path_distance(path: List[Landmark], detour, speed) -> float: + total_distance = 0 + for i in range(len(path) - 1): + total_distance += get_distance(path[i].location, path[i + 1].location, detour, speed)[1] + return total_distance +""" + + +def find_shortest_path_through_all_landmarks(landmarks: List[Landmark]) -> List[Landmark]: + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] + + # Step 1: Find 'start' and 'finish' landmarks + start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start') + finish_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'finish') + + start_landmark = landmarks[start_idx] + finish_landmark = landmarks[finish_idx] + + + # Step 2: Create a list of unvisited landmarks excluding 'start' and 'finish' + unvisited_landmarks = [lm for i, lm in enumerate(landmarks) if i not in [start_idx, finish_idx]] + + # Step 3: Initialize the path with the 'start' landmark + path = [start_landmark] + coordinates = [landmarks[start_idx].location] + + current_landmark = start_landmark + + # Step 4: Use nearest neighbor heuristic to visit all landmarks + while unvisited_landmarks: + nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time(current_landmark.location, lm.location, detour, speed)) + path.append(nearest_landmark) + coordinates.append(nearest_landmark.location) + current_landmark = nearest_landmark + unvisited_landmarks.remove(nearest_landmark) + + # Step 5: Finally add the 'finish' landmark to the path + path.append(finish_landmark) + coordinates.append(landmarks[finish_idx].location) + + path_poly = Polygon(coordinates) + + return path, path_poly + + +# Returns a list of minor landmarks around the planned path to enhance experience +def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[Landmark], width: float) -> List[Landmark] : + + second_order_landmarks = [] + visited_names = [] + area = create_corridor(visited_landmarks, width) + + for visited in visited_landmarks : + visited_names.append(visited.name) + + for landmark in all_landmarks : + if is_in_area(area, landmark.location) and landmark.name not in visited_names: + second_order_landmarks.append(landmark) + + with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + return take_most_important(second_order_landmarks, parameters, len(visited_landmarks)) + + +# Try fix the shortest path using shapely +def fix_using_polygon(tour: List[Landmark])-> List[Landmark] : + + coords = [] + coords_dict = {} + for landmark in tour : + coords.append(landmark.location) + if landmark.name != 'finish' : + coords_dict[landmark.location] = landmark + + tour_poly = Polygon(coords) + + better_tour_poly = tour_poly.buffer(0) + try : + xs, ys = better_tour_poly.exterior.xy + + if len(xs) != len(tour) : + better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish + xs, ys = better_tour_poly.exterior.xy + + except : + better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish + xs, ys = better_tour_poly.exterior.xy + + + # reverse the xs and ys + xs.reverse() + ys.reverse() + + better_tour = [] # List of ordered visit + 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 + for i,x in enumerate(xs[:-1]) : + y = ys[i] + better_tour.append(coords_dict[tuple((x,y))]) + name_index[coords_dict[tuple((x,y))].name] = i + + + # Scroll the list to have start in front again + start_index = name_index['start'] + better_tour = better_tour[start_index:] + better_tour[:start_index] + + # Append the finish back and correct the time to reach + better_tour.append(tour[-1]) + + # Rearrange only if polygon still not simple + if not better_tour_poly.is_simple : + better_tour = rearrange(better_tour) + + return better_tour + + +# Second stage of the optimization. Use linear programming again to refine the path +def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, detour: int, print_infos: bool) -> List[Landmark] : + + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] + + if len(base_tour)-2 >= max_landmarks : + return base_tour + + + # No need to refine if no detour is taken + # if detour == 0 : + if False : + new_tour = base_tour + + else : + minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200) + + if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path") + + # full set of visitable landmarks + full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish) + full_set.append(base_tour[-1]) # add finish back + + # get a new tour + new_tour = solve_optimization(full_set, max_time+detour, False, max_landmarks) + if new_tour is None : + 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 + better_tour, better_poly = find_shortest_path_through_all_landmarks(new_tour) + + # Fix the tour using Polygons if the path looks weird + if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid : + better_tour = fix_using_polygon(better_tour) + + # Link the tour again + 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 + + if print_infos : + print("\n\n\nRefined tour (result of second stage optimization): ") + print_res(final_tour) + total_score = 0 + for elem in final_tour : + total_score += elem.attractiveness + + print("\nTotal score : " + str(total_score)) + + + + return final_tour + + diff --git a/backend/src/example_tester.py b/backend/src/example_tester.py new file mode 100644 index 0000000..71d82e7 --- /dev/null +++ b/backend/src/example_tester.py @@ -0,0 +1,80 @@ +import pandas as pd + +from typing import List +from landmarks_manager import LandmarkManager +from fastapi.encoders import jsonable_encoder + +from backend.src.example_optimizer import solve_optimization +# from refiner import refine_optimization +from structs.landmarks import Landmark +from structs.landmarktype import LandmarkType +from structs.preferences import Preferences, Preference + + +# Helper function to create a .txt file with results +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 main(coordinates: tuple[float, float]) -> List[Landmark]: + + manager = LandmarkManager() + + preferences = Preferences( + sightseeing=Preference( + name='sightseeing', + type=LandmarkType(landmark_type='sightseeing'), + score = 5 + ), + nature=Preference( + name='nature', + type=LandmarkType(landmark_type='nature'), + score = 5 + ), + shopping=Preference( + name='shopping', + type=LandmarkType(landmark_type='shopping'), + score = 5 + ) + ) + + + # Create start and finish + start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=coordinates, osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=coordinates, osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + + # Generate the landmarks from the start location + landmarks, landmarks_short = manager.get_landmark_lists(preferences=preferences, center_coordinates=start.location) + print([l.name for l in landmarks_short]) + + #write_data(landmarks, "landmarks.txt") + + # Insert start and finish to the landmarks list + landmarks_short.insert(0, start) + landmarks_short.append(finish) + + # TODO use these parameters in another way + max_walking_time = 3 # hours + detour = 30 # minutes + + # First stage optimization + base_tour = solve_optimization(landmarks_short, max_walking_time*60, True) + + # Second stage optimization + # refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) + + return base_tour + + + +if __name__ == '__main__': + start = (48.847132, 2.312359) # Café Chez César + # start = (47.377859, 8.540585) # Zurich HB + main(start) diff --git a/backend/src/main.py b/backend/src/main.py index 89b49fa..8a76288 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,51 +1,45 @@ -from optimizer import solve_optimization -from refiner import refine_optimization -from landmarks_manager import generate_landmarks +from backend.src.example_optimizer import solve_optimization +# from refiner import refine_optimization +from landmarks_manager import LandmarkManager from structs.landmarks import Landmark from structs.landmarktype import LandmarkType -from structs.preferences import Preferences, Preference +from structs.preferences import Preferences from fastapi import FastAPI, Query, Body -from typing import List app = FastAPI() - +manager = LandmarkManager() # TODO: needs a global variable to store the landmarks accross function calls # linked_tour = [] -# Assuming frontend is calling like this : -#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}" -@app.post("/optimizer_coords/{start_lat}/{start_lon}/{finish_lat}/{finish_lon}") -def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(...), finish_lat: float = None, finish_lon: float = None) -> List[Landmark]: - +@app.post("/route/new") +def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] = None) -> str: + ''' + Main function to call the optimizer. + :param preferences: the preferences specified by the user as the post body + :param start: the coordinates of the starting point as a tuple of floats (as url query parameters) + :param end: the coordinates of the finishing point as a tuple of floats (as url query parameters) + :return: the uuid of the first landmark in the optimized route + ''' if preferences is None : raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.") - if bool(start_lat) ^ bool(start_lon) : - raise ValueError("Please provide both latitude and longitude for the starting point") - if bool(finish_lat) ^ bool(finish_lon) : - raise ValueError("Please provide both latitude and longitude for the finish point") + if start is None: + raise ValueError("Please provide the starting coordinates as a tuple of floats.") + if end is None: + end = start - start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start_lat, start_lon), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + start_landmark = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start[0], start[1]), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) + end_landmark = Landmark(name='end', type=LandmarkType(landmark_type='end'), location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - if bool(finish_lat) and bool(finish_lon) : - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(finish_lat, finish_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - else : - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(start_lat, start_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - - - start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.8375946, 2.2949904), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8375946, 2.2949904), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - # Generate the landmarks from the start location - landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location) - + landmarks, landmarks_short = LandmarkManager.get_landmark_lists(preferences=preferences, coordinates=start.location) + print([l.name for l in landmarks_short]) # insert start and finish to the landmarks list - landmarks_short.insert(0, start) - landmarks_short.append(finish) + landmarks_short.insert(0, start_landmark) + landmarks_short.append(end_landmark) - - # TODO use these parameters in another way + # TODO infer these parameters from the preferences max_walking_time = 4 # hours detour = 30 # minutes @@ -53,32 +47,11 @@ def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(.. base_tour = solve_optimization(landmarks_short, max_walking_time*60, True) # Second stage optimization - refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) + # refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True) - - # TODO: should look something like this - # # set time to reach and transform into fully functional linked list - # linked_tour += link(refined_tour) - # return { - # 'city_name': 'Paris', - # 'n_stops': len(linked_tour), - # 'first_landmark_uuid': linked_tour[0].uuid, - # } - - return refined_tour - - - - -# input city, country in the form of 'Paris, France' -@app.post("/test2/{city_country}") -def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landmark]: - - landmarks = generate_landmarks(city_country, preferences) - - max_steps = 9000000 - - visiting_order = solve_optimization(landmarks, max_steps, True) + # linked_tour = ... + # return linked_tour[0].uuid + return base_tour[0].uuid @@ -86,5 +59,3 @@ def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landm def get_landmark(landmark_uuid: str) -> Landmark: #cherche dans linked_tour et retourne le landmark correspondant pass - - diff --git a/backend/src/parameters/amenity_selectors.yaml b/backend/src/parameters/amenity_selectors.yaml new file mode 100644 index 0000000..4f6b223 --- /dev/null +++ b/backend/src/parameters/amenity_selectors.yaml @@ -0,0 +1,32 @@ +nature: + leisure: park + geological: '' + natural: + - geyser + - hot_spring + - arch + - volcano + - stone + tourism: + - alpine_hut + - viewpoint + - zoo + waterway: waterfall + +shopping: + shop: + - department_store + - mall + +sightseeing: + tourism: + - museum + - attraction + - gallery + historic: '' + amenity: + - planetarium + - place_of_worship + - fountain + water: + - reflecting_pool diff --git a/backend/src/parameters/landmark_parameters.yaml b/backend/src/parameters/landmark_parameters.yaml new file mode 100644 index 0000000..b85c89a --- /dev/null +++ b/backend/src/parameters/landmark_parameters.yaml @@ -0,0 +1,6 @@ +city_bbox_side: 5000 #m +radius_close_to: 30 +church_coeff: 0.6 +park_coeff: 1.5 +tag_coeff: 100 +N_important: 40 diff --git a/backend/src/parameters/optimizer_parameters.yaml b/backend/src/parameters/optimizer_parameters.yaml new file mode 100644 index 0000000..515d028 --- /dev/null +++ b/backend/src/parameters/optimizer_parameters.yaml @@ -0,0 +1,3 @@ +detour_factor: 1.4 +average_walking_speed: 4.8 +max_landmarks: 40 diff --git a/backend/src/structs/landmarks.py b/backend/src/structs/landmarks.py index 2448236..71111a1 100644 --- a/backend/src/structs/landmarks.py +++ b/backend/src/structs/landmarks.py @@ -1,6 +1,5 @@ from typing import Optional from pydantic import BaseModel, Field - from .landmarktype import LandmarkType from uuid import uuid4 @@ -29,3 +28,6 @@ class Landmark(BaseModel) : time_to_reach_next : Optional[int] = 0 # TODO fix this in existing code next_uuid : Optional[str] = None # TODO implement this ASAP + + def __hash__(self) -> int: + return self.uuid.int \ No newline at end of file diff --git a/backend/src/tester.py b/backend/src/tester.py index acc84d6..cea6527 100644 --- a/backend/src/tester.py +++ b/backend/src/tester.py @@ -1,7 +1,7 @@ import pandas as pd from typing import List -from landmarks_manager import generate_landmarks +from landmarks_manager import LandmarkManager from fastapi.encoders import jsonable_encoder from optimizer_v4 import solve_optimization @@ -26,6 +26,7 @@ def write_data(L: List[Landmark], file_name: str): def test4(coordinates: tuple[float, float]) -> List[Landmark]: + manager = LandmarkManager() preferences = Preferences( sightseeing=Preference( @@ -77,4 +78,4 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]: #test4(tuple((47.377859, 8.540585))) # Zurich HB #test4(tuple((45.7576485, 4.8330241))) # Lyon Bellecour test4(tuple((48.5848435, 7.7332974))) # Strasbourg Gare -#test4(tuple((48.2067858, 16.3692340))) # Vienne \ No newline at end of file +#test4(tuple((48.2067858, 16.3692340))) # Vienne diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml new file mode 100644 index 0000000..aabc6fa --- /dev/null +++ b/deployment/deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nav-backend +spec: + replicas: 1 + selector: + matchLabels: + app: nav-backend + template: + metadata: + labels: + app: nav-backend + spec: + containers: + - name: worker + image: backend-image + ports: + - containerPort: 8000 + env: + - name: NUM_WORKERS + value: "3" + - name: OSM_CACHE_DIR + value: "/osm-cache" + volumeMounts: + - name: osm-cache + mountPath: /osm-cache + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 4 + memory: 10Gi + volumes: + - name: osm-cache + persistentVolumeClaim: + claimName: osm-cache + + \ No newline at end of file diff --git a/deployment/ingress.yaml b/deployment/ingress.yaml new file mode 100644 index 0000000..8fa3b41 --- /dev/null +++ b/deployment/ingress.yaml @@ -0,0 +1,15 @@ +kind: IngressRoute +apiVersion: traefik.io/v1alpha1 +metadata: + name: nav-ingress +spec: + entryPoints: + - websecure + routes: + - match: Host(`nav.kluster.moll.re`) + kind: Rule + services: + - name: nav-service + port: 8000 + tls: + certResolver: default-tls diff --git a/deployment/kustomization.yaml b/deployment/kustomization.yaml index e69de29..bbb5d0c 100644 --- a/deployment/kustomization.yaml +++ b/deployment/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + + +namespace: nav +resources: + - namespace.yaml + - pvc.yaml + - deployment.yaml + - service.yaml + - ingress.yaml + +images: + - name: backend-image + newName: git.kluster.moll.re/remoll/fast_network_navigation/backend + newTag: latest \ No newline at end of file diff --git a/deployment/namespace.yaml b/deployment/namespace.yaml new file mode 100644 index 0000000..0a074bd --- /dev/null +++ b/deployment/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: placeholder diff --git a/deployment/pvc.yaml b/deployment/pvc.yaml new file mode 100644 index 0000000..a7e747f --- /dev/null +++ b/deployment/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: osm-cache +spec: + storageClassName: "nfs-client" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "5Gi" diff --git a/deployment/service.yaml b/deployment/service.yaml new file mode 100644 index 0000000..c4e5de3 --- /dev/null +++ b/deployment/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: nav-service +spec: + selector: + app: nav-backend + ports: + - protocol: TCP + port: 8000 + targetPort: 8000