diff --git a/backend/Dockerfile b/backend/Dockerfile index 056c3a0..9e15e47 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3 +FROM python:3.11-slim WORKDIR /app COPY Pipfile Pipfile.lock . diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index d661287..f6188cf 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b089081defe09c4ffad85fd22c5de336c76b31e7f6f67e5969b731bdaa58e528" + "sha256": "6f1907b01627126d80ae688e9f2e32fc696d85f0cca3913f14fd5d26cb3ac2db" }, "pipfile-spec": 6, "requires": {}, @@ -134,102 +134,6 @@ "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", @@ -254,14 +158,6 @@ "markers": "python_version >= '3.8'", "version": "==2.2.0" }, - "exceptiongroup": { - "hashes": [ - "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", - "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" - ], - "markers": "python_version < '3.11'", - "version": "==1.2.2" - }, "fastapi": { "hashes": [ "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf", @@ -279,54 +175,6 @@ "markers": "python_version >= '3.8'", "version": "==0.0.4" }, - "geopandas": { - "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": { "hashes": [ "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734", @@ -335,14 +183,6 @@ "markers": "python_version >= '3.7'", "version": "==2.0" }, - "geojson": { - "hashes": [ - "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac", - "sha256:68a9771827237adb8c0c71f8527509c8f5bef61733aa434cefc9c9d4f0ebe8f3" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.0" - }, "geopy": { "hashes": [ "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1", @@ -507,41 +347,6 @@ "markers": "python_version >= '3.7'", "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": { "hashes": [ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", @@ -594,64 +399,55 @@ }, "numpy": { "hashes": [ - "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f", - "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238", - "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f", - "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95", - "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a", - "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a", - "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2", - "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2", - "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f", - "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609", - "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f", - "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad", - "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86", - "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65", - "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb", - "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995", - "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a", - "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85", - "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4", - "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275", - "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1", - "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196", - "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d", - "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e", - "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514", - "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f", - "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6", - "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4", - "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44", - "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df", - "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581", - "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787", - "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5", - "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc", - "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871", - "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54", - "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2", - "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98", - "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9", - "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864", - "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de", - "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289", - "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b", - "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c", - "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9" + "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8", + "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134", + "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d", + "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67", + "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf", + "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02", + "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59", + "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55", + "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c", + "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f", + "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4", + "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018", + "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015", + "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9", + "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3", + "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8", + "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe", + "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1", + "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f", + "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a", + "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42", + "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac", + "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e", + "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06", + "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268", + "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1", + "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4", + "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868", + "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26", + "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef", + "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b", + "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82", + "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343", + "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b", + "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7", + "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171", + "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414", + "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7", + "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87", + "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368", + "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587", + "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990", + "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c", + "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101", + "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==2.0.0" - }, - "osmpythontools": { - "hashes": [ - "sha256:22548d86d68d36edff3cf9ab76c45745cda86a4ea0b28442e107d6b42992a426", - "sha256:ac67bea77b521941af648ef641ae1d006101948d1112475c256ea23ef31b426a" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.9.3" + "version": "==2.0.1" }, "packaging": { "hashes": [ @@ -662,127 +458,6 @@ "markers": "python_version >= '3.8'", "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": { "hashes": [ "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", @@ -895,79 +570,6 @@ "markers": "python_version >= '3.8'", "version": "==2.18.0" }, - "pyogrio": { - "hashes": [ - "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_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": [ - "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": { "hashes": [ "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", @@ -983,13 +585,6 @@ "markers": "python_version >= '3.8'", "version": "==0.0.9" }, - "pytz": { - "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" - ], - "version": "==2024.1" - }, "pywikibot": { "hashes": [ "sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2", @@ -1155,14 +750,6 @@ "markers": "python_version >= '3.7'", "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": { "hashes": [ "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", @@ -1195,98 +782,6 @@ "markers": "python_version >= '3.8'", "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": { "hashes": [ "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", @@ -1300,11 +795,11 @@ "standard" ], "hashes": [ - "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81", - "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8" + "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81", + "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503" ], "markers": "python_version >= '3.8'", - "version": "==0.30.1" + "version": "==0.30.3" }, "uvloop": { "hashes": [ diff --git a/backend/src/constants.py b/backend/src/constants.py index 2e66727..951a51d 100644 --- a/backend/src/constants.py +++ b/backend/src/constants.py @@ -14,6 +14,6 @@ OSM_CACHE_DIR = Path(cache_dir_string) logger = logging.getLogger(__name__) logging.basicConfig( - level = logging.INFO, + level = logging.DEBUG, format = '%(asctime)s - %(name)s\t- %(levelname)s\t- %(message)s' ) diff --git a/backend/src/example_landmarks_manager.py b/backend/src/example_landmarks_manager.py deleted file mode 100644 index 30df6dc..0000000 --- a/backend/src/example_landmarks_manager.py +++ /dev/null @@ -1,151 +0,0 @@ -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/example_optimizer.py b/backend/src/example_optimizer.py deleted file mode 100644 index 922af99..0000000 --- a/backend/src/example_optimizer.py +++ /dev/null @@ -1,419 +0,0 @@ -import numpy as np -import yaml - -from typing import List, Tuple -from scipy.optimize import linprog -from math import radians, sin, cos, acos - -from structs.landmarks import Landmark -import constants - -# Function to print the result -def print_res(L: List[Landmark], L_tot): - - if len(L) == L_tot: - print('\nAll landmarks can be visited within max_steps, the following order is suggested : ') - else : - print('Could not visit all the landmarks, the following order is suggested : ') - - dist = 0 - for elem in L : - if elem.time_to_reach_next is not None : - print('- ' + elem.name + ', time to reach next = ' + str(elem.time_to_reach_next)) - dist += elem.time_to_reach_next - else : - print('- ' + elem.name) - - print("\nMinutes walked : " + str(dist)) - print(f"Visited {len(L)-2} out of {L_tot-2} landmarks") - - -# Prevent the use of a particular solution -def prevent_config(resx, A_ub, b_ub) -> bool: - - for i, elem in enumerate(resx): - resx[i] = round(elem) - - N = len(resx) # Number of edges - L = int(np.sqrt(N)) # Number of landmarks - - nonzeroind = np.nonzero(resx)[0] # the return is a little funky so I use the [0] - nonzero_tup = np.unravel_index(nonzeroind, (L,L)) - - ind_a = nonzero_tup[0].tolist() - vertices_visited = ind_a - vertices_visited.remove(0) - - ones = [1]*L - h = [0]*N - for i in range(L) : - if i in vertices_visited : - h[i*L:i*L+L] = ones - - A_ub = np.vstack((A_ub, h)) - b_ub.append(len(vertices_visited)-1) - - return A_ub, b_ub - - -# Prevent the possibility of a given solution bit -def break_cricle(circle_vertices: list, L: int, A_ub: list, b_ub: list) -> bool: - - if L-1 in circle_vertices : - circle_vertices.remove(L-1) - - h = [0]*L*L - for i in range(L) : - if i in circle_vertices : - h[i*L:i*L+L] = [1]*L - - A_ub = np.vstack((A_ub, h)) - b_ub.append(len(circle_vertices)-1) - - return A_ub, b_ub - - -# Checks if the path is connected, returns a circle if it finds one and the RESULT -def is_connected(resx) -> bool: - - # first round the results to have only 0-1 values - for i, elem in enumerate(resx): - resx[i] = round(elem) - - N = len(resx) # length of res - L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def. - n_edges = resx.sum() # number of edges - - nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0] - - nonzero_tup = np.unravel_index(nonzeroind, (L,L)) - - ind_a = nonzero_tup[0].tolist() - ind_b = nonzero_tup[1].tolist() - - edges = [] - edges_visited = [] - vertices_visited = [] - - edge1 = (ind_a[0], ind_b[0]) - edges_visited.append(edge1) - vertices_visited.append(edge1[0]) - - for i, a in enumerate(ind_a) : - edges.append((a, ind_b[i])) # Create the list of edges - - remaining = edges - remaining.remove(edge1) - - break_flag = False - while len(remaining) > 0 and not break_flag: - for edge2 in remaining : - if edge2[0] == edge1[1] : - if edge1[1] in vertices_visited : - edges_visited.append(edge2) - break_flag = True - break - else : - vertices_visited.append(edge1[1]) - edges_visited.append(edge2) - remaining.remove(edge2) - edge1 = edge2 - - elif edge1[1] == L-1 or edge1[1] in vertices_visited: - break_flag = True - break - - vertices_visited.append(edge1[1]) - - - if len(vertices_visited) == n_edges +1 : - return vertices_visited, [] - else: - return vertices_visited, edges_visited - - -# Function that returns the distance in meters from one location to another -def get_distance(p1: Tuple[float, float], p2: Tuple[float, float], detour: float, speed: float) : - - # Compute the straight-line distance in km - if p1 == p2 : - return 0, 0 - else: - dist = 6371.01 * acos(sin(radians(p1[0]))*sin(radians(p2[0])) + cos(radians(p1[0]))*cos(radians(p2[0]))*cos(radians(p1[1]) - radians(p2[1]))) - - # Consider the detour factor for average cityto deterline walking distance (in km) - walk_dist = dist*detour - - # Time to walk this distance (in minutes) - walk_time = walk_dist/speed*60 - - if walk_time > 15 : - walk_time = 5*round(walk_time/5) - else : - walk_time = round(walk_time) - - - return round(walk_dist, 1), walk_time - - -# Initialize A and c. Compute the distances from all landmarks to each other and store attractiveness -# 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): - - # 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 = [] - # Coefficients of inequality constraints (left-hand side) - A_ub = [] - - for spot1 in landmarks : - dist_table = [0]*len(landmarks) - c.append(-spot1.attractiveness) - for j, spot2 in enumerate(landmarks) : - t = get_distance(spot1.location, spot2.location, detour, speed)[1] - dist_table[j] = t - A_ub += dist_table - c = c*len(landmarks) - - return c, A_ub, [max_steps] - - -# Constraint to respect only one travel per landmark. Also caps the total number of visited landmarks -def respect_number(L:int, A_ub, b_ub): - - ones = [1]*L - zeros = [0]*L - for i in range(L) : - h = zeros*i + ones + zeros*(L-1-i) - A_ub = np.vstack((A_ub, h)) - b_ub.append(1) - - # 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'] - - A_ub = np.vstack((A_ub, ones*L)) - b_ub.append(max_landmarks+1) - - return A_ub, b_ub - - -# Constraint to not have d14 and d41 simultaneously. Does not prevent circular symmetry with more elements -def break_sym(L, A_ub, b_ub): - upper_ind = np.triu_indices(L,0,L) - - up_ind_x = upper_ind[0] - up_ind_y = upper_ind[1] - - for i, _ in enumerate(up_ind_x) : - l = [0]*L*L - if up_ind_x[i] != up_ind_y[i] : - l[up_ind_x[i]*L + up_ind_y[i]] = 1 - l[up_ind_y[i]*L + up_ind_x[i]] = 1 - - A_ub = np.vstack((A_ub,l)) - b_ub.append(1) - - return A_ub, b_ub - - -# Constraint to not stay in position. Removes d11, d22, d33, etc. -def init_eq_not_stay(L: int): - l = [0]*L*L - - for i in range(L) : - for j in range(L) : - if j == i : - l[j + i*L] = 1 - - l = np.array(np.array(l)) - - return [l], [0] - - -# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1 -def respect_user_mustsee(landmarks: List[Landmark], A_eq: list, b_eq: list) : - L = len(landmarks) - - for i, elem in enumerate(landmarks) : - if elem.must_do is True and elem.name not in ['finish', 'start']: - l = [0]*L*L - for j in range(L) : # sets the horizontal ones (go from) - l[j +i*L] = 1 # sets the vertical ones (go to) double check if good - - for k in range(L-1) : - l[k*L+L-1] = 1 - - A_eq = np.vstack((A_eq,l)) - b_eq.append(2) - - return A_eq, b_eq - - -# Constraint to ensure start at start and finish at goal -def respect_start_finish(L: int, A_eq: list, b_eq: list): - ls = [1]*L + [0]*L*(L-1) # sets only horizontal ones for start (go from) - ljump = [0]*L*L - ljump[L-1] = 1 # Prevent start finish jump - lg = [0]*L*L - ll = [0]*L*(L-1) + [1]*L - for k in range(L-1) : # sets only vertical ones for goal (go to) - ll[k*L] = 1 - if k != 0 : # Prevent the shortcut start -> finish - lg[k*L+L-1] = 1 - - - A_eq = np.vstack((A_eq,ls)) - A_eq = np.vstack((A_eq,ljump)) - A_eq = np.vstack((A_eq,lg)) - A_eq = np.vstack((A_eq,ll)) - b_eq.append(1) - b_eq.append(0) - b_eq.append(1) - b_eq.append(0) - - return A_eq, b_eq - - -# Constraint to tie the problem together. Necessary but not sufficient to avoid circles -def respect_order(N: int, A_eq, b_eq): - for i in range(N-1) : # Prevent stacked ones - if i == 0 or i == N-1: # Don't touch start or finish - continue - else : - l = [0]*N - l[i] = -1 - l = l*N - for j in range(N) : - l[i*N + j] = 1 - - A_eq = np.vstack((A_eq,l)) - b_eq.append(0) - - return 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]: - - # Read the parameters from the file - 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 - total_dist = 0 - while j < len(order)-1 : - elem = landmarks[order[j]] - next = landmarks[order[j+1]] - - d = get_distance(elem.location, next.location, detour_factor, speed)[1] - elem.time_to_reach_next = d - L.append(elem) - j += 1 - total_dist += d - - L.append(next) - - return L, total_dist - - -def link_list_simple(ordered_visit: 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_factor = parameters['detour_factor'] - speed = parameters['average_walking_speed'] - - 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_distance(elem.location, next.location, detour_factor, speed)[1] - 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 - - -# Main optimization pipeline -def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_details: bool) : - - L = len(landmarks) - - # SET CONSTRAINTS FOR INEQUALITY - c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other - A_ub, b_ub = respect_number(L, A_ub, b_ub) # Respect max number of visits (no more possible stops than landmarks). - A_ub, b_ub = break_sym(L, A_ub, b_ub) # break the 'zig-zag' symmetry - - # SET CONSTRAINTS FOR EQUALITY - A_eq, b_eq = init_eq_not_stay(L) # Force solution not to stay in same place - A_eq, b_eq = respect_user_mustsee(landmarks, A_eq, b_eq) # Check if there are user_defined must_see. Also takes care of start/goal - A_eq, b_eq = respect_start_finish(L, A_eq, b_eq) # Force start and finish positions - A_eq, b_eq = respect_order(L, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor) - - # SET BOUNDS FOR DECISION VARIABLE (x can only be 0 or 1) - x_bounds = [(0, 1)]*L*L - - # Solve linear programming problem - res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3) - - # Raise error if no solution is found - if not res.success : - 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 - else : - order, circle = is_connected(res.x) - i = 0 - timeout = 80 - while len(circle) != 0 and i < timeout: - A_ub, b_ub = prevent_config(res.x, A_ub, b_ub) - A_ub, b_ub = break_cricle(order, len(landmarks), A_ub, b_ub) - res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3) - order, circle = is_connected(res.x) - if len(circle) == 0 : - break - print(i) - i += 1 - - if i == timeout : - raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.") - - # Add the times to reach and stop optimizing - L, total_dist = link_list(order, landmarks) - - if printing_details is True : - if i != 0 : - print(f"Neded to recompute paths {i} times because of unconnected loops...") - print_res(L, len(landmarks)) - print("\nTotal score : " + str(int(-res.fun))) - - return L - - - - - - diff --git a/backend/src/example_refiner.py b/backend/src/example_refiner.py deleted file mode 100644 index 4b1c306..0000000 --- a/backend/src/example_refiner.py +++ /dev/null @@ -1,303 +0,0 @@ -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 deleted file mode 100644 index 71d82e7..0000000 --- a/backend/src/example_tester.py +++ /dev/null @@ -1,80 +0,0 @@ -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/landmarks_manager.py b/backend/src/landmarks_manager.py index 53fa012..403648f 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -2,9 +2,8 @@ import math as m import yaml import logging -from typing import List, Tuple from OSMPythonTools.overpass import Overpass, overpassQueryBuilder -from OSMPythonTools import cachingStrategy +from OSMPythonTools.cachingStrategy import CachingStrategy, JSON from pywikibot import ItemPage, Site from pywikibot import config config.put_throttle = 0 @@ -33,10 +32,8 @@ class LandmarkManager: tag_coeff: float # coeff to adjust weight of tags N_important: int # number of important landmarks to consider - preferences: Preferences # preferences of visit - location: Tuple[float] # coordinates around which to find a path - def __init__(self, preferences: Preferences, coordinates: Tuple[float, float]) -> None: + def __init__(self) -> None: with constants.AMENITY_SELECTORS_PATH.open('r') as f: self.amenity_selectors = yaml.safe_load(f) @@ -49,12 +46,12 @@ class LandmarkManager: self.park_coeff = parameters['park_coeff'] self.tag_coeff = parameters['tag_coeff'] self.N_important = parameters['N_important'] - - self.preferences = preferences - self.location = coordinates + + self.overpass = Overpass() + CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR) - def generate_landmarks_list(self) -> Tuple[List[Landmark], List[Landmark]] : + def generate_landmarks_list(self, center_coordinates: tuple[float, float], preferences: Preferences) -> tuple[list[Landmark], list[Landmark]]: """ Generate and prioritize a list of landmarks based on user preferences. @@ -62,62 +59,71 @@ class LandmarkManager: and current location. It scores and corrects these landmarks, removes duplicates, and then selects the most important landmarks based on a predefined criterion. + Parameters: + center_coordinates (tuple[float, float]): The latitude and longitude of the center location around which to search. + preferences (Preferences): The user's preference settings that influence the landmark selection. + Returns: - Tuple[List[Landmark], List[Landmark]]: + tuple[list[Landmark], list[Landmark]]: - A list of all existing landmarks. - A list of the most important landmarks based on the user's preferences. """ L = [] - - # List for sightseeing - if self.preferences.sightseeing.score != 0 : - L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, coordinates=self.location) - self.correct_score(L1, self.preferences.sightseeing) + bbox = self.create_bbox(center_coordinates) + # list for sightseeing + if preferences.sightseeing.score != 0: + score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff) + L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], SIGHTSEEING, score_function) + self.correct_score(L1, preferences.sightseeing) L += L1 - - # List for nature - if self.preferences.nature.score != 0 : - L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, coordinates=self.location) - self.correct_score(L2, self.preferences.nature) + + # list for nature + if preferences.nature.score != 0: + score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff) + L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], NATURE, score_function) + self.correct_score(L2, preferences.nature) L += L2 - - # List for shopping - if self.preferences.shopping.score != 0 : - L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, coordinates=self.location) - self.correct_score(L3, self.preferences.shopping) + + # list for shopping + if preferences.shopping.score != 0: + score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff)) + L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], SHOPPING, score_function) + self.correct_score(L3, preferences.shopping) L += L3 L = self.remove_duplicates(L) + L_constrained = take_most_important(L, self.N_important) + self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.') - return L, take_most_important(L, self.N_important) + return L, L_constrained - def remove_duplicates(self, landmarks: List[Landmark]) -> List[Landmark] : + def remove_duplicates(self, landmarks: list[Landmark]) -> list[Landmark]: """ Removes duplicate landmarks based on their names from the given list. Only retains the landmark with highest score Parameters: - landmarks (List[Landmark]): A list of Landmark objects. + landmarks (list[Landmark]): A list of Landmark objects. Returns: - List[Landmark]: A list of unique Landmark objects based on their names. + list[Landmark]: A list of unique Landmark objects based on their names. """ L_clean = [] names = [] - for landmark in landmarks : + for landmark in landmarks: if landmark.name in names: continue - else : + else: names.append(landmark.name) L_clean.append(landmark) return L_clean - def correct_score(self, landmarks: List[Landmark], preference: Preference) : + def correct_score(self, landmarks: list[Landmark], preference: Preference): """ Adjust the attractiveness score of each landmark in the list based on user preferences. @@ -125,24 +131,24 @@ class LandmarkManager: The score adjustment is computed using a simple linear transformation based on the preference score. Args: - landmarks (List[Landmark]): A list of landmarks whose scores need to be corrected. + landmarks (list[Landmark]): A list of landmarks whose scores need to be corrected. preference (Preference): The user's preference settings that influence the attractiveness score adjustment. Raises: TypeError: If the type of any landmark in the list does not match the expected type in the preference. """ - if len(landmarks) == 0 : + if len(landmarks) == 0: return - if landmarks[0].type != preference.type : + if landmarks[0].type != preference.type: raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {landmarks[0].name}") - for elem in landmarks : + for elem in landmarks: elem.attractiveness = int(elem.attractiveness*preference.score/5) # arbitrary computation - def count_elements_close_to(self, coordinates: Tuple[float, float]) -> int: + def count_elements_close_to(self, coordinates: tuple[float, float]) -> int: """ Count the number of OpenStreetMap elements (nodes, ways, relations) within a specified radius of the given location. @@ -150,7 +156,7 @@ class LandmarkManager: OpenStreetMap data to count the number of elements within that bounding box. Args: - coordinates (Tuple[float, float]): The latitude and longitude of the location to search around. + coordinates (tuple[float, float]): The latitude and longitude of the location to search around. Returns: int: The number of elements (nodes, ways, relations) within the specified radius. Returns 0 if no elements @@ -164,32 +170,36 @@ class LandmarkManager: alpha = (180*radius) / (6371000*m.pi) bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha} - - # Build the query to find elements within the radius - radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], - elementType=['node', 'way', 'relation']) - try : - overpass = Overpass() - radius_result = overpass.query(radius_query) + # Build the query to find elements within the radius + radius_query = overpassQueryBuilder( + bbox=[bbox['latLower'], + bbox['lonLower'], + bbox['latHigher'], + bbox['lonHigher']], + elementType=['node', 'way', 'relation'] + ) + + try: + radius_result = self.overpass.query(radius_query) N_elem = radius_result.countWays() + radius_result.countRelations() - #print(f"There are {N_elem} ways/relations within 50m") - if N_elem is None : + self.logger.debug(f"There are {N_elem} ways/relations within 50m") + if N_elem is None: return 0 return N_elem - except : + except: return 0 - def create_bbox(self, coordinates: Tuple[float, float]) -> Tuple[float, float, float, float]: + def create_bbox(self, coordinates: tuple[float, float]) -> tuple[float, float, float, float]: """ Create a bounding box around the given coordinates. Args: - coordinates (Tuple[float, float]): The latitude and longitude of the center of the bounding box. + coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box. Returns: - Tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude + tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude defining the bounding box. """ @@ -212,44 +222,52 @@ class LandmarkManager: return min_lat, min_lon, max_lat, max_lon - def fetch_landmarks(self, list_amenity: list, landmarktype: str, coordinates: Tuple[float, float]) -> List[Landmark] : + def fetch_landmarks(self, bbox: tuple, amenity_selector: dict, landmarktype: str, score_function: callable) -> list[Landmark]: """ Fetches landmarks of a specified type from OpenStreetMap (OSM) within a bounding box centered on given coordinates. Args: - list_amenity (list): A list of OSM amenity queries to be used for fetching landmarks. - These queries are typically used to filter results (e.g., [''amenity'='place_of_worship']). + bbox (tuple[float, float, float, float]): The bounding box coordinates (min_lat, min_lon, max_lat, max_lon). + amenity_selector (dict): The Overpass API query selector for the desired landmark type. landmarktype (str): The type of the landmark (e.g., 'sightseeing', 'nature', 'shopping'). - coordinates (Tuple[float, float]): The central coordinates (latitude, longitude) for the bounding box. + score_function (callable): The function to compute the score of the landmark based on its attributes. Returns: - List[Landmark]: A list of Landmark objects that were fetched and filtered based on the provided criteria. + list[Landmark]: A list of Landmark objects that were fetched and filtered based on the provided criteria. Notes: - - The bounding box is created around the given coordinates with a side length defined by `self.city_bbox_side`. - Landmarks are fetched using Overpass API queries. + - Selectors are translated from the dictionary to the Overpass query format. (e.g., 'amenity'='place_of_worship') - Landmarks are filtered based on various conditions including tags and type. - Scores are assigned to landmarks based on their attributes and surrounding elements. """ - - # Create bbox around start location - bbox = self.create_bbox(coordinates) + return_list = [] - # Initialize some variables - N = 0 - L = [] - overpass = Overpass() - - for amenity in list_amenity : - query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body') - result = overpass.query(query) - N += result.countElements() + # caution, when applying a list of selectors, overpass will search for elements that match ALL selectors simultaneously + # we need to split the selectors into separate queries and merge the results + for sel in dict_to_selector_list(amenity_selector): + self.logger.debug(f"Current selector: {sel}") + query = overpassQueryBuilder( + bbox = bbox, + elementType = ['way', 'relation'], + selector = sel, + # conditions = [], + includeCenter = True, + out = 'body' + ) + try: + result = self.overpass.query(query) + except Exception as e: + self.logger.error(f"Error fetching landmarks: {e}") + return + for elem in result.elements(): name = elem.tag('name') # Add name location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) + # TODO: exclude these from the get go # skip if unprecise location if name is None or location[0] is None: continue @@ -262,65 +280,86 @@ class LandmarkManager: if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': continue - else : - osm_type = elem.type() # Add type : 'way' or 'relation' - osm_id = elem.id() # Add OSM id - elem_type = landmarktype # Add the landmark type as 'sightseeing, - n_tags = len(elem.tags().keys()) # Add number of tags + osm_type = elem.type() # Add type: 'way' or 'relation' + osm_id = elem.id() # Add OSM id + elem_type = landmarktype # Add the landmark type as 'sightseeing, + n_tags = len(elem.tags().keys()) # Add number of tags - # remove specific tags - skip = False - for tag in elem.tags().keys() : - if "pay" in tag : - n_tags -= 1 # discard payment options for tags + # remove specific tags + skip = False + for tag in elem.tags().keys(): + if "pay" in tag: + n_tags -= 1 # discard payment options for tags - if "disused" in tag : - skip = True # skip disused amenities + if "disused" in tag: + skip = True # skip disused amenities + break + + if "wikipedia" in tag: + n_tags += 3 # wikipedia entries count more + + if tag == "wikidata": + Q = elem.tag('wikidata') + site = Site("wikidata", "wikidata") + item = ItemPage(site, Q) + item.get() + n_languages = len(item.labels) + n_tags += n_languages/10 + + if elem_type != "nature": + if "leisure" in tag and elem.tag('leisure') == "park": + elem_type = "nature" + + if landmarktype != SHOPPING: + if "shop" in tag: + skip = True break - if "wikipedia" in tag : - n_tags += 3 # wikipedia entries count more + if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: + skip = True + break - if tag == "wikidata" : - Q = elem.tag('wikidata') - site = Site("wikidata", "wikidata") - item = ItemPage(site, Q) - item.get() - n_languages = len(item.labels) - n_tags += n_languages/10 + if skip: + continue - if elem_type != "nature" : - if "leisure" in tag and elem.tag('leisure') == "park": - elem_type = "nature" + score = score_function(location, n_tags) + if score != 0: + # Generate the landmark and append it to the list + landmark = Landmark( + name=name, + type=elem_type, + location=location, + osm_type=osm_type, + osm_id=osm_id, + attractiveness=score, + must_do=False, + n_tags=int(n_tags) + ) + return_list.append(landmark) + + self.logger.debug(f"Fetched {len(return_list)} landmarks of type {landmarktype} in {bbox}") - if amenity not in ["'shop'='department_store'", "'shop'='mall'"] : - if "shop" in tag : - skip = True - break - - if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: - skip = True - break - - if skip: - continue - - # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT - if amenity == "'amenity'='place_of_worship'" : - #score = int((count_elements_close_to(location, radius) + (n_tags*tag_coeff) )*church_coeff) - score = int((self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff) - elif amenity == "'leisure'='park'" : - score = int((self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff) - else : - score = int(self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff)) - - if score is not None : - - # Generate the landmark and append it to the list - #print(f"There are {n_tags} tags on this Landmark. Total score : {score}\n") - landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=int(n_tags)) - L.append(landmark) - - return L + return return_list + +def dict_to_selector_list(d: dict) -> list: + """ + Convert a dictionary of key-value pairs to a list of Overpass query strings. + + Args: + d (dict): A dictionary of key-value pairs representing the selector. + + Returns: + list: A list of strings representing the Overpass query selectors. + """ + return_list = [] + for key, value in d.items(): + if type(value) == list: + val = '|'.join(value) + return_list.append(f'{key}~"{val}"') + elif type(value) == str and len(value) == 0: + return_list.append(f'{key}') + else: + return_list.append(f'{key}={value}') + return return_list diff --git a/backend/src/tester.py b/backend/src/tester.py index cd75435..3c1cd94 100644 --- a/backend/src/tester.py +++ b/backend/src/tester.py @@ -24,9 +24,9 @@ def write_data(L: List[Landmark], file_name: str): def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> List[Landmark]: + manager = LandmarkManager() - preferences = Preferences( sightseeing=Preference( name='sightseeing', @@ -56,10 +56,12 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) - manager = LandmarkManager(preferences=preferences, coordinates=start_coords) # Generate the landmarks from the start location - landmarks, landmarks_short = manager.generate_landmarks_list() + landmarks, landmarks_short = manager.generate_landmarks_list( + center_coordinates = start_coords, + preferences = preferences + ) # Store data to file for debug purposes # write_data(landmarks, "landmarks_Strasbourg.txt") diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml deleted file mode 100644 index aabc6fa..0000000 --- a/deployment/deployment.yaml +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 8fa3b41..0000000 --- a/deployment/ingress.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index bbb5d0c..0000000 --- a/deployment/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 0a074bd..0000000 --- a/deployment/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: placeholder diff --git a/deployment/pvc.yaml b/deployment/pvc.yaml deleted file mode 100644 index a7e747f..0000000 --- a/deployment/pvc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index c4e5de3..0000000 --- a/deployment/service.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: nav-service -spec: - selector: - app: nav-backend - ports: - - protocol: TCP - port: 8000 - targetPort: 8000