refactored landmark manager and clean up
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| FROM python:3 | FROM python:3.11-slim | ||||||
|  |  | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
| COPY Pipfile Pipfile.lock . | COPY Pipfile Pipfile.lock . | ||||||
|   | |||||||
							
								
								
									
										605
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										605
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|     "_meta": { |     "_meta": { | ||||||
|         "hash": { |         "hash": { | ||||||
|             "sha256": "b089081defe09c4ffad85fd22c5de336c76b31e7f6f67e5969b731bdaa58e528" |             "sha256": "6f1907b01627126d80ae688e9f2e32fc696d85f0cca3913f14fd5d26cb3ac2db" | ||||||
|         }, |         }, | ||||||
|         "pipfile-spec": 6, |         "pipfile-spec": 6, | ||||||
|         "requires": {}, |         "requires": {}, | ||||||
| @@ -134,102 +134,6 @@ | |||||||
|             "markers": "python_full_version >= '3.7.0'", |             "markers": "python_full_version >= '3.7.0'", | ||||||
|             "version": "==3.3.2" |             "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": { |         "click": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", |                 "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", | ||||||
| @@ -254,14 +158,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==2.2.0" |             "version": "==2.2.0" | ||||||
|         }, |         }, | ||||||
|         "exceptiongroup": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", |  | ||||||
|                 "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version < '3.11'", |  | ||||||
|             "version": "==1.2.2" |  | ||||||
|         }, |  | ||||||
|         "fastapi": { |         "fastapi": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf", |                 "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf", | ||||||
| @@ -279,54 +175,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==0.0.4" |             "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": { |         "geographiclib": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734", |                 "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734", | ||||||
| @@ -335,14 +183,6 @@ | |||||||
|             "markers": "python_version >= '3.7'", |             "markers": "python_version >= '3.7'", | ||||||
|             "version": "==2.0" |             "version": "==2.0" | ||||||
|         }, |         }, | ||||||
|         "geojson": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac", |  | ||||||
|                 "sha256:68a9771827237adb8c0c71f8527509c8f5bef61733aa434cefc9c9d4f0ebe8f3" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '3.7'", |  | ||||||
|             "version": "==3.1.0" |  | ||||||
|         }, |  | ||||||
|         "geopy": { |         "geopy": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1", |                 "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1", | ||||||
| @@ -507,41 +347,6 @@ | |||||||
|             "markers": "python_version >= '3.7'", |             "markers": "python_version >= '3.7'", | ||||||
|             "version": "==2.1.5" |             "version": "==2.1.5" | ||||||
|         }, |         }, | ||||||
|         "matplotlib": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33", |  | ||||||
|                 "sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03", |  | ||||||
|                 "sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c", |  | ||||||
|                 "sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0", |  | ||||||
|                 "sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe", |  | ||||||
|                 "sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff", |  | ||||||
|                 "sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3", |  | ||||||
|                 "sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d", |  | ||||||
|                 "sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3", |  | ||||||
|                 "sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7", |  | ||||||
|                 "sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7", |  | ||||||
|                 "sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049", |  | ||||||
|                 "sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b", |  | ||||||
|                 "sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842", |  | ||||||
|                 "sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a", |  | ||||||
|                 "sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb", |  | ||||||
|                 "sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb", |  | ||||||
|                 "sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5", |  | ||||||
|                 "sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04", |  | ||||||
|                 "sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b", |  | ||||||
|                 "sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0", |  | ||||||
|                 "sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f", |  | ||||||
|                 "sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812", |  | ||||||
|                 "sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98", |  | ||||||
|                 "sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b", |  | ||||||
|                 "sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80", |  | ||||||
|                 "sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92", |  | ||||||
|                 "sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010", |  | ||||||
|                 "sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '3.9'", |  | ||||||
|             "version": "==3.9.1" |  | ||||||
|         }, |  | ||||||
|         "mdurl": { |         "mdurl": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", |                 "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", | ||||||
| @@ -594,64 +399,55 @@ | |||||||
|         }, |         }, | ||||||
|         "numpy": { |         "numpy": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f", |                 "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8", | ||||||
|                 "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238", |                 "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134", | ||||||
|                 "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f", |                 "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d", | ||||||
|                 "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95", |                 "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67", | ||||||
|                 "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a", |                 "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf", | ||||||
|                 "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a", |                 "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02", | ||||||
|                 "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2", |                 "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59", | ||||||
|                 "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2", |                 "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55", | ||||||
|                 "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f", |                 "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c", | ||||||
|                 "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609", |                 "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f", | ||||||
|                 "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f", |                 "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4", | ||||||
|                 "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad", |                 "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018", | ||||||
|                 "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86", |                 "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015", | ||||||
|                 "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65", |                 "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9", | ||||||
|                 "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb", |                 "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3", | ||||||
|                 "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995", |                 "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8", | ||||||
|                 "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a", |                 "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe", | ||||||
|                 "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85", |                 "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1", | ||||||
|                 "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4", |                 "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f", | ||||||
|                 "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275", |                 "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a", | ||||||
|                 "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1", |                 "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42", | ||||||
|                 "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196", |                 "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac", | ||||||
|                 "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d", |                 "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e", | ||||||
|                 "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e", |                 "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06", | ||||||
|                 "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514", |                 "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268", | ||||||
|                 "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f", |                 "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1", | ||||||
|                 "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6", |                 "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4", | ||||||
|                 "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4", |                 "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868", | ||||||
|                 "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44", |                 "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26", | ||||||
|                 "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df", |                 "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef", | ||||||
|                 "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581", |                 "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b", | ||||||
|                 "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787", |                 "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82", | ||||||
|                 "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5", |                 "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343", | ||||||
|                 "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc", |                 "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b", | ||||||
|                 "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871", |                 "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7", | ||||||
|                 "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54", |                 "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171", | ||||||
|                 "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2", |                 "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414", | ||||||
|                 "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98", |                 "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7", | ||||||
|                 "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9", |                 "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87", | ||||||
|                 "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864", |                 "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368", | ||||||
|                 "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de", |                 "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587", | ||||||
|                 "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289", |                 "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990", | ||||||
|                 "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b", |                 "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c", | ||||||
|                 "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c", |                 "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101", | ||||||
|                 "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9" |                 "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "markers": "python_version >= '3.9'", |             "markers": "python_version >= '3.9'", | ||||||
|             "version": "==2.0.0" |             "version": "==2.0.1" | ||||||
|         }, |  | ||||||
|         "osmpythontools": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:22548d86d68d36edff3cf9ab76c45745cda86a4ea0b28442e107d6b42992a426", |  | ||||||
|                 "sha256:ac67bea77b521941af648ef641ae1d006101948d1112475c256ea23ef31b426a" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "markers": "python_version >= '3.8'", |  | ||||||
|             "version": "==1.9.3" |  | ||||||
|         }, |         }, | ||||||
|         "packaging": { |         "packaging": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @@ -662,127 +458,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==24.1" |             "version": "==24.1" | ||||||
|         }, |         }, | ||||||
|         "pandas": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863", |  | ||||||
|                 "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2", |  | ||||||
|                 "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", |  | ||||||
|                 "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", |  | ||||||
|                 "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", |  | ||||||
|                 "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", |  | ||||||
|                 "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", |  | ||||||
|                 "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", |  | ||||||
|                 "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", |  | ||||||
|                 "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", |  | ||||||
|                 "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4", |  | ||||||
|                 "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921", |  | ||||||
|                 "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", |  | ||||||
|                 "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", |  | ||||||
|                 "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", |  | ||||||
|                 "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", |  | ||||||
|                 "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", |  | ||||||
|                 "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", |  | ||||||
|                 "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd", |  | ||||||
|                 "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", |  | ||||||
|                 "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57", |  | ||||||
|                 "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", |  | ||||||
|                 "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", |  | ||||||
|                 "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a", |  | ||||||
|                 "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238", |  | ||||||
|                 "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", |  | ||||||
|                 "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", |  | ||||||
|                 "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", |  | ||||||
|                 "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '3.9'", |  | ||||||
|             "version": "==2.2.2" |  | ||||||
|         }, |  | ||||||
|         "pillow": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", |  | ||||||
|                 "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", |  | ||||||
|                 "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", |  | ||||||
|                 "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", |  | ||||||
|                 "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", |  | ||||||
|                 "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", |  | ||||||
|                 "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", |  | ||||||
|                 "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", |  | ||||||
|                 "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", |  | ||||||
|                 "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", |  | ||||||
|                 "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", |  | ||||||
|                 "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", |  | ||||||
|                 "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", |  | ||||||
|                 "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", |  | ||||||
|                 "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", |  | ||||||
|                 "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", |  | ||||||
|                 "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", |  | ||||||
|                 "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", |  | ||||||
|                 "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", |  | ||||||
|                 "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", |  | ||||||
|                 "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", |  | ||||||
|                 "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", |  | ||||||
|                 "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", |  | ||||||
|                 "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", |  | ||||||
|                 "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", |  | ||||||
|                 "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", |  | ||||||
|                 "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", |  | ||||||
|                 "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", |  | ||||||
|                 "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", |  | ||||||
|                 "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", |  | ||||||
|                 "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", |  | ||||||
|                 "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", |  | ||||||
|                 "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", |  | ||||||
|                 "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", |  | ||||||
|                 "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", |  | ||||||
|                 "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", |  | ||||||
|                 "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", |  | ||||||
|                 "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", |  | ||||||
|                 "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", |  | ||||||
|                 "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", |  | ||||||
|                 "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", |  | ||||||
|                 "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", |  | ||||||
|                 "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", |  | ||||||
|                 "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", |  | ||||||
|                 "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", |  | ||||||
|                 "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", |  | ||||||
|                 "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", |  | ||||||
|                 "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", |  | ||||||
|                 "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", |  | ||||||
|                 "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", |  | ||||||
|                 "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", |  | ||||||
|                 "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", |  | ||||||
|                 "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", |  | ||||||
|                 "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", |  | ||||||
|                 "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", |  | ||||||
|                 "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", |  | ||||||
|                 "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", |  | ||||||
|                 "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", |  | ||||||
|                 "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", |  | ||||||
|                 "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", |  | ||||||
|                 "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", |  | ||||||
|                 "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", |  | ||||||
|                 "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", |  | ||||||
|                 "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", |  | ||||||
|                 "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", |  | ||||||
|                 "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", |  | ||||||
|                 "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", |  | ||||||
|                 "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", |  | ||||||
|                 "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", |  | ||||||
|                 "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", |  | ||||||
|                 "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", |  | ||||||
|                 "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", |  | ||||||
|                 "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", |  | ||||||
|                 "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", |  | ||||||
|                 "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", |  | ||||||
|                 "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", |  | ||||||
|                 "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", |  | ||||||
|                 "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", |  | ||||||
|                 "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", |  | ||||||
|                 "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '3.8'", |  | ||||||
|             "version": "==10.4.0" |  | ||||||
|         }, |  | ||||||
|         "pydantic": { |         "pydantic": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", |                 "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", | ||||||
| @@ -895,79 +570,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==2.18.0" |             "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": { |         "python-dotenv": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", |                 "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", | ||||||
| @@ -983,13 +585,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==0.0.9" |             "version": "==0.0.9" | ||||||
|         }, |         }, | ||||||
|         "pytz": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", |  | ||||||
|                 "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" |  | ||||||
|             ], |  | ||||||
|             "version": "==2024.1" |  | ||||||
|         }, |  | ||||||
|         "pywikibot": { |         "pywikibot": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2", |                 "sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2", | ||||||
| @@ -1155,14 +750,6 @@ | |||||||
|             "markers": "python_version >= '3.7'", |             "markers": "python_version >= '3.7'", | ||||||
|             "version": "==1.5.4" |             "version": "==1.5.4" | ||||||
|         }, |         }, | ||||||
|         "six": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", |  | ||||||
|                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", |  | ||||||
|             "version": "==1.16.0" |  | ||||||
|         }, |  | ||||||
|         "sniffio": { |         "sniffio": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", |                 "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", | ||||||
| @@ -1195,98 +782,6 @@ | |||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==4.12.2" |             "version": "==4.12.2" | ||||||
|         }, |         }, | ||||||
|         "tzdata": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", |  | ||||||
|                 "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '2'", |  | ||||||
|             "version": "==2024.1" |  | ||||||
|         }, |  | ||||||
|         "ujson": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e", |  | ||||||
|                 "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b", |  | ||||||
|                 "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6", |  | ||||||
|                 "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7", |  | ||||||
|                 "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9", |  | ||||||
|                 "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd", |  | ||||||
|                 "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569", |  | ||||||
|                 "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f", |  | ||||||
|                 "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51", |  | ||||||
|                 "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", |  | ||||||
|                 "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1", |  | ||||||
|                 "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf", |  | ||||||
|                 "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc", |  | ||||||
|                 "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", |  | ||||||
|                 "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a", |  | ||||||
|                 "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", |  | ||||||
|                 "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27", |  | ||||||
|                 "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", |  | ||||||
|                 "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126", |  | ||||||
|                 "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1", |  | ||||||
|                 "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", |  | ||||||
|                 "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64", |  | ||||||
|                 "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8", |  | ||||||
|                 "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e", |  | ||||||
|                 "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", |  | ||||||
|                 "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3", |  | ||||||
|                 "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb", |  | ||||||
|                 "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", |  | ||||||
|                 "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043", |  | ||||||
|                 "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", |  | ||||||
|                 "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e", |  | ||||||
|                 "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21", |  | ||||||
|                 "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d", |  | ||||||
|                 "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd", |  | ||||||
|                 "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0", |  | ||||||
|                 "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337", |  | ||||||
|                 "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753", |  | ||||||
|                 "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804", |  | ||||||
|                 "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f", |  | ||||||
|                 "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f", |  | ||||||
|                 "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5", |  | ||||||
|                 "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5", |  | ||||||
|                 "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1", |  | ||||||
|                 "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00", |  | ||||||
|                 "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2", |  | ||||||
|                 "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050", |  | ||||||
|                 "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e", |  | ||||||
|                 "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4", |  | ||||||
|                 "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8", |  | ||||||
|                 "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996", |  | ||||||
|                 "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6", |  | ||||||
|                 "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", |  | ||||||
|                 "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", |  | ||||||
|                 "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1", |  | ||||||
|                 "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4", |  | ||||||
|                 "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b", |  | ||||||
|                 "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88", |  | ||||||
|                 "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518", |  | ||||||
|                 "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5", |  | ||||||
|                 "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770", |  | ||||||
|                 "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4", |  | ||||||
|                 "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a", |  | ||||||
|                 "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76", |  | ||||||
|                 "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe", |  | ||||||
|                 "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", |  | ||||||
|                 "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1", |  | ||||||
|                 "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5", |  | ||||||
|                 "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b", |  | ||||||
|                 "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7", |  | ||||||
|                 "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8", |  | ||||||
|                 "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc", |  | ||||||
|                 "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a", |  | ||||||
|                 "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720", |  | ||||||
|                 "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3", |  | ||||||
|                 "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b", |  | ||||||
|                 "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9", |  | ||||||
|                 "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1", |  | ||||||
|                 "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746" |  | ||||||
|             ], |  | ||||||
|             "markers": "python_version >= '3.8'", |  | ||||||
|             "version": "==5.10.0" |  | ||||||
|         }, |  | ||||||
|         "urllib3": { |         "urllib3": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", |                 "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", | ||||||
| @@ -1300,11 +795,11 @@ | |||||||
|                 "standard" |                 "standard" | ||||||
|             ], |             ], | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81", |                 "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81", | ||||||
|                 "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8" |                 "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503" | ||||||
|             ], |             ], | ||||||
|             "markers": "python_version >= '3.8'", |             "markers": "python_version >= '3.8'", | ||||||
|             "version": "==0.30.1" |             "version": "==0.30.3" | ||||||
|         }, |         }, | ||||||
|         "uvloop": { |         "uvloop": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|   | |||||||
| @@ -14,6 +14,6 @@ OSM_CACHE_DIR = Path(cache_dir_string) | |||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
|     level = logging.INFO, |     level = logging.DEBUG, | ||||||
|     format = '%(asctime)s - %(name)s\t- %(levelname)s\t- %(message)s' |     format = '%(asctime)s - %(name)s\t- %(levelname)s\t- %(message)s' | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| @@ -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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -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) |  | ||||||
| @@ -2,9 +2,8 @@ import math as m | |||||||
| import yaml | import yaml | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from typing import List, Tuple |  | ||||||
| from OSMPythonTools.overpass import Overpass, overpassQueryBuilder | from OSMPythonTools.overpass import Overpass, overpassQueryBuilder | ||||||
| from OSMPythonTools import cachingStrategy | from OSMPythonTools.cachingStrategy import CachingStrategy, JSON | ||||||
| from pywikibot import ItemPage, Site | from pywikibot import ItemPage, Site | ||||||
| from pywikibot import config | from pywikibot import config | ||||||
| config.put_throttle = 0 | config.put_throttle = 0 | ||||||
| @@ -33,10 +32,8 @@ class LandmarkManager: | |||||||
|     tag_coeff: float        # coeff to adjust weight of tags |     tag_coeff: float        # coeff to adjust weight of tags | ||||||
|     N_important: int        # number of important landmarks to consider |     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: |         with constants.AMENITY_SELECTORS_PATH.open('r') as f: | ||||||
|             self.amenity_selectors = yaml.safe_load(f) |             self.amenity_selectors = yaml.safe_load(f) | ||||||
| @@ -50,11 +47,11 @@ class LandmarkManager: | |||||||
|             self.tag_coeff = parameters['tag_coeff'] |             self.tag_coeff = parameters['tag_coeff'] | ||||||
|             self.N_important = parameters['N_important'] |             self.N_important = parameters['N_important'] | ||||||
|  |  | ||||||
|         self.preferences = preferences |         self.overpass = Overpass() | ||||||
|         self.location = coordinates |         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. |         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 |         and current location. It scores and corrects these landmarks, removes duplicates, and then selects the most important | ||||||
|         landmarks based on a predefined criterion. |         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: |         Returns: | ||||||
|             Tuple[List[Landmark], List[Landmark]]: |             tuple[list[Landmark], list[Landmark]]: | ||||||
|                 - A list of all existing landmarks. |                 - A list of all existing landmarks. | ||||||
|                 - A list of the most important landmarks based on the user's preferences. |                 - A list of the most important landmarks based on the user's preferences. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         L = [] |         L = [] | ||||||
|  |         bbox = self.create_bbox(center_coordinates) | ||||||
|         # List for sightseeing |         # list for sightseeing | ||||||
|         if self.preferences.sightseeing.score != 0 : |         if preferences.sightseeing.score != 0: | ||||||
|             L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, coordinates=self.location) |             score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)   | ||||||
|             self.correct_score(L1, self.preferences.sightseeing) |             L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], SIGHTSEEING, score_function) | ||||||
|  |             self.correct_score(L1, preferences.sightseeing) | ||||||
|             L += L1 |             L += L1 | ||||||
|  |  | ||||||
|         # List for nature |         # list for nature | ||||||
|         if self.preferences.nature.score != 0 : |         if preferences.nature.score != 0: | ||||||
|             L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, coordinates=self.location) |             score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)   | ||||||
|             self.correct_score(L2, self.preferences.nature) |             L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], NATURE, score_function) | ||||||
|  |             self.correct_score(L2, preferences.nature) | ||||||
|             L += L2 |             L += L2 | ||||||
|  |  | ||||||
|         # List for shopping |         # list for shopping | ||||||
|         if self.preferences.shopping.score != 0 : |         if preferences.shopping.score != 0: | ||||||
|             L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, coordinates=self.location) |             score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff)) | ||||||
|             self.correct_score(L3, self.preferences.shopping) |             L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], SHOPPING, score_function) | ||||||
|  |             self.correct_score(L3, preferences.shopping) | ||||||
|             L += L3 |             L += L3 | ||||||
|  |  | ||||||
|         L = self.remove_duplicates(L) |         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 |         Removes duplicate landmarks based on their names from the given list. Only retains the landmark with highest score | ||||||
|  |  | ||||||
|         Parameters: |         Parameters: | ||||||
|         landmarks (List[Landmark]): A list of Landmark objects. |         landmarks (list[Landmark]): A list of Landmark objects. | ||||||
|  |  | ||||||
|         Returns: |         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 = [] |         L_clean = [] | ||||||
|         names = [] |         names = [] | ||||||
|  |  | ||||||
|         for landmark in landmarks : |         for landmark in landmarks: | ||||||
|             if landmark.name in names:  |             if landmark.name in names:  | ||||||
|                 continue   |                 continue   | ||||||
|             else : |             else: | ||||||
|                 names.append(landmark.name) |                 names.append(landmark.name) | ||||||
|                 L_clean.append(landmark) |                 L_clean.append(landmark) | ||||||
|          |          | ||||||
|         return L_clean |         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. |         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. |         The score adjustment is computed using a simple linear transformation based on the preference score. | ||||||
|  |  | ||||||
|         Args: |         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. |             preference (Preference): The user's preference settings that influence the attractiveness score adjustment. | ||||||
|  |  | ||||||
|         Raises: |         Raises: | ||||||
|             TypeError: If the type of any landmark in the list does not match the expected type in the preference. |             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 |             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}") |             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 |             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. |         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. |         OpenStreetMap data to count the number of elements within that bounding box. | ||||||
|  |  | ||||||
|         Args: |         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: |         Returns: | ||||||
|             int: The number of elements (nodes, ways, relations) within the specified radius. Returns 0 if no elements |             int: The number of elements (nodes, ways, relations) within the specified radius. Returns 0 if no elements | ||||||
| @@ -166,30 +172,34 @@ class LandmarkManager: | |||||||
|         bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha} |         bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha} | ||||||
|  |  | ||||||
|         # Build the query to find elements within the radius |         # Build the query to find elements within the radius | ||||||
|         radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], |         radius_query = overpassQueryBuilder( | ||||||
|                                 elementType=['node', 'way', 'relation']) |             bbox=[bbox['latLower'], | ||||||
|  |             bbox['lonLower'], | ||||||
|  |             bbox['latHigher'], | ||||||
|  |             bbox['lonHigher']], | ||||||
|  |             elementType=['node', 'way', 'relation'] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         try :  |         try:  | ||||||
|             overpass = Overpass() |             radius_result = self.overpass.query(radius_query) | ||||||
|             radius_result = overpass.query(radius_query) |  | ||||||
|             N_elem = radius_result.countWays() + radius_result.countRelations() |             N_elem = radius_result.countWays() + radius_result.countRelations() | ||||||
|             #print(f"There are {N_elem} ways/relations within 50m") |             self.logger.debug(f"There are {N_elem} ways/relations within 50m") | ||||||
|             if N_elem is None : |             if N_elem is None: | ||||||
|                 return 0 |                 return 0 | ||||||
|             return N_elem |             return N_elem | ||||||
|         except : |         except: | ||||||
|             return 0 |             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. |         Create a bounding box around the given coordinates. | ||||||
|  |  | ||||||
|         Args: |         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: |         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. |                                                 defining the bounding box. | ||||||
|         """ |         """ | ||||||
|          |          | ||||||
| @@ -212,44 +222,52 @@ class LandmarkManager: | |||||||
|         return min_lat, min_lon, max_lat, max_lon |         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. |         Fetches landmarks of a specified type from OpenStreetMap (OSM) within a bounding box centered on given coordinates. | ||||||
|  |  | ||||||
|         Args: |         Args: | ||||||
|             list_amenity (list): A list of OSM amenity queries to be used for fetching landmarks.  |             bbox (tuple[float, float, float, float]): The bounding box coordinates (min_lat, min_lon, max_lat, max_lon). | ||||||
|                                 These queries are typically used to filter results (e.g., [''amenity'='place_of_worship']). |             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'). |             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: |         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: |         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. |             - 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. |             - Landmarks are filtered based on various conditions including tags and type. | ||||||
|             - Scores are assigned to landmarks based on their attributes and surrounding elements. |             - Scores are assigned to landmarks based on their attributes and surrounding elements. | ||||||
|         """ |         """ | ||||||
|  |         return_list = [] | ||||||
|  |  | ||||||
|         # Create bbox around start location |         # caution, when applying a list of selectors, overpass will search for elements that match ALL selectors simultaneously | ||||||
|         bbox = self.create_bbox(coordinates) |         # 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' | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|         # Initialize some variables |             try: | ||||||
|         N = 0 |                 result = self.overpass.query(query) | ||||||
|         L = [] |             except Exception as e: | ||||||
|         overpass = Overpass() |                 self.logger.error(f"Error fetching landmarks: {e}") | ||||||
|  |                 return | ||||||
|         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() |  | ||||||
|              |              | ||||||
|             for elem in result.elements(): |             for elem in result.elements(): | ||||||
|  |  | ||||||
|                 name = elem.tag('name')                             # Add name |                 name = elem.tag('name')                             # Add name | ||||||
|                 location = (elem.centerLat(), elem.centerLon())     # Add coordinates (lat, lon) |                 location = (elem.centerLat(), elem.centerLon())     # Add coordinates (lat, lon) | ||||||
|  |  | ||||||
|  |                 # TODO: exclude these from the get go | ||||||
|                 # skip if unprecise location |                 # skip if unprecise location | ||||||
|                 if name is None or location[0] is None: |                 if name is None or location[0] is None: | ||||||
|                     continue |                     continue | ||||||
| @@ -262,65 +280,86 @@ class LandmarkManager: | |||||||
|                 if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': |                 if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': | ||||||
|                     continue |                     continue | ||||||
|              |              | ||||||
|                 else : |                 osm_type = elem.type()              # Add type: 'way' or 'relation' | ||||||
|                     osm_type = elem.type()              # Add type : 'way' or 'relation' |                 osm_id = elem.id()                  # Add OSM id  | ||||||
|                     osm_id = elem.id()                  # Add OSM id  |                 elem_type = landmarktype            # Add the landmark type as 'sightseeing,  | ||||||
|                     elem_type = landmarktype            # Add the landmark type as 'sightseeing,  |                 n_tags = len(elem.tags().keys())    # Add number of tags | ||||||
|                     n_tags = len(elem.tags().keys())    # Add number of tags |  | ||||||
|  |  | ||||||
|                     # remove specific tags |                 # remove specific tags | ||||||
|                     skip = False |                 skip = False | ||||||
|                     for tag in elem.tags().keys() : |                 for tag in elem.tags().keys(): | ||||||
|                         if "pay" in tag : |                     if "pay" in tag: | ||||||
|                             n_tags -= 1             # discard payment options for tags |                         n_tags -= 1             # discard payment options for tags | ||||||
|  |  | ||||||
|                         if "disused" in tag : |                     if "disused" in tag: | ||||||
|                             skip = True             # skip disused amenities |                         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 |                             break | ||||||
|  |  | ||||||
|                         if "wikipedia" in tag : |                         if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: | ||||||
|                             n_tags += 3             # wikipedia entries count more |                             skip = True | ||||||
|  |                             break | ||||||
|  |  | ||||||
|                         if tag == "wikidata" : |                 if skip: | ||||||
|                             Q = elem.tag('wikidata') |                     continue | ||||||
|                             site = Site("wikidata", "wikidata") |  | ||||||
|                             item = ItemPage(site, Q) |  | ||||||
|                             item.get() |  | ||||||
|                             n_languages = len(item.labels) |  | ||||||
|                             n_tags += n_languages/10 |  | ||||||
|  |  | ||||||
|                         if elem_type != "nature" : |                 score = score_function(location, n_tags) | ||||||
|                             if "leisure" in tag and elem.tag('leisure') == "park": |                 if score != 0: | ||||||
|                                 elem_type = "nature" |                     # 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) | ||||||
|          |          | ||||||
|                         if amenity not in ["'shop'='department_store'", "'shop'='mall'"] : |         self.logger.debug(f"Fetched {len(return_list)} landmarks of type {landmarktype} in {bbox}") | ||||||
|                             if "shop" in tag : |  | ||||||
|                                 skip = True |  | ||||||
|                                 break |  | ||||||
|  |  | ||||||
|                             if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: |         return return_list | ||||||
|                                 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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ def write_data(L: List[Landmark], file_name: str): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> List[Landmark]: | def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> List[Landmark]: | ||||||
|  |     manager = LandmarkManager() | ||||||
|  |  | ||||||
|      |      | ||||||
|     preferences = Preferences( |     preferences = Preferences( | ||||||
| @@ -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) |     #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 |     # 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 |     # Store data to file for debug purposes | ||||||
|     # write_data(landmarks, "landmarks_Strasbourg.txt") |     # write_data(landmarks, "landmarks_Strasbourg.txt") | ||||||
|   | |||||||
| @@ -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 |  | ||||||
|  |  | ||||||
|      |  | ||||||
| @@ -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 |  | ||||||
| @@ -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 |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| apiVersion: v1 |  | ||||||
| kind: Namespace |  | ||||||
| metadata: |  | ||||||
|   name: placeholder |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| apiVersion: v1 |  | ||||||
| kind: PersistentVolumeClaim |  | ||||||
| metadata: |  | ||||||
|   name: osm-cache |  | ||||||
| spec: |  | ||||||
|   storageClassName: "nfs-client" |  | ||||||
|   accessModes: |  | ||||||
|     - ReadWriteOnce |  | ||||||
|   resources: |  | ||||||
|     requests: |  | ||||||
|       storage: "5Gi" |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| apiVersion: v1 |  | ||||||
| kind: Service |  | ||||||
| metadata: |  | ||||||
|   name: nav-service |  | ||||||
| spec: |  | ||||||
|   selector: |  | ||||||
|     app: nav-backend |  | ||||||
|   ports: |  | ||||||
|     - protocol: TCP |  | ||||||
|       port: 8000 |  | ||||||
|       targetPort: 8000 |  | ||||||
		Reference in New Issue
	
	Block a user