refactored landmark manager and clean up

This commit is contained in:
Remy Moll 2024-07-25 09:37:37 +02:00
parent d23050b811
commit 80b3d5b012
15 changed files with 218 additions and 1732 deletions

View File

@ -1,4 +1,4 @@
FROM python:3
FROM python:3.11-slim
WORKDIR /app
COPY Pipfile Pipfile.lock .

605
backend/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "b089081defe09c4ffad85fd22c5de336c76b31e7f6f67e5969b731bdaa58e528"
"sha256": "6f1907b01627126d80ae688e9f2e32fc696d85f0cca3913f14fd5d26cb3ac2db"
},
"pipfile-spec": 6,
"requires": {},
@ -134,102 +134,6 @@
"markers": "python_full_version >= '3.7.0'",
"version": "==3.3.2"
},
"charset-normalizer": {
"hashes": [
"sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
"sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
"sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
"sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
"sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
"sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
"sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
"sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
"sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
"sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
"sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
"sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
"sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
"sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
"sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
"sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
"sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
"sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
"sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
"sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
"sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
"sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
"sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
"sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
"sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
"sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
"sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
"sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
"sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
"sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
"sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
"sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
"sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
"sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
"sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
"sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
"sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
"sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
"sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
"sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
"sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
"sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
"sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
"sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
"sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
"sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
"sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
"sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
"sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
"sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
"sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
"sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
"sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
"sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
"sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
"sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
"sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
"sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
"sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
"sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
"sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
"sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
"sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
"sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
"sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
"sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
"sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
"sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
"sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
"sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
"sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
"sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
"sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
"sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
"sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
"sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
"sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
"sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
"sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
"sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
"sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
"sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
"sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
"sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
"sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
"sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
"sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
"sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
"sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
"sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.3.2"
},
"click": {
"hashes": [
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
@ -254,14 +158,6 @@
"markers": "python_version >= '3.8'",
"version": "==2.2.0"
},
"exceptiongroup": {
"hashes": [
"sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
"sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
],
"markers": "python_version < '3.11'",
"version": "==1.2.2"
},
"fastapi": {
"hashes": [
"sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf",
@ -279,54 +175,6 @@
"markers": "python_version >= '3.8'",
"version": "==0.0.4"
},
"geopandas": {
"hashes": [
"sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122",
"sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397",
"sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f",
"sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d",
"sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60",
"sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169",
"sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8",
"sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31",
"sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923",
"sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2",
"sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb",
"sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab",
"sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb",
"sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a",
"sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670",
"sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8",
"sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407",
"sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671",
"sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88",
"sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f",
"sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f",
"sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0",
"sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb",
"sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2",
"sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d",
"sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c",
"sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3",
"sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719",
"sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749",
"sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4",
"sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f",
"sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02",
"sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58",
"sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1",
"sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41",
"sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4",
"sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb",
"sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb",
"sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3",
"sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d",
"sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d",
"sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"
],
"markers": "python_version >= '3.8'",
"version": "==4.53.1"
},
"geographiclib": {
"hashes": [
"sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734",
@ -335,14 +183,6 @@
"markers": "python_version >= '3.7'",
"version": "==2.0"
},
"geojson": {
"hashes": [
"sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac",
"sha256:68a9771827237adb8c0c71f8527509c8f5bef61733aa434cefc9c9d4f0ebe8f3"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.0"
},
"geopy": {
"hashes": [
"sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1",
@ -507,41 +347,6 @@
"markers": "python_version >= '3.7'",
"version": "==2.1.5"
},
"matplotlib": {
"hashes": [
"sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33",
"sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03",
"sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c",
"sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0",
"sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe",
"sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff",
"sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3",
"sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d",
"sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3",
"sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7",
"sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7",
"sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049",
"sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b",
"sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842",
"sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a",
"sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb",
"sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb",
"sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5",
"sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04",
"sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b",
"sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0",
"sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f",
"sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812",
"sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98",
"sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b",
"sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80",
"sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92",
"sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010",
"sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"
],
"markers": "python_version >= '3.9'",
"version": "==3.9.1"
},
"mdurl": {
"hashes": [
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
@ -594,64 +399,55 @@
},
"numpy": {
"hashes": [
"sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f",
"sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238",
"sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f",
"sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95",
"sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a",
"sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a",
"sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2",
"sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2",
"sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f",
"sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609",
"sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f",
"sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad",
"sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86",
"sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65",
"sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb",
"sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995",
"sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a",
"sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85",
"sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4",
"sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275",
"sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1",
"sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196",
"sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d",
"sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e",
"sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514",
"sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f",
"sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6",
"sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4",
"sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44",
"sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df",
"sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581",
"sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787",
"sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5",
"sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc",
"sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871",
"sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54",
"sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2",
"sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98",
"sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9",
"sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864",
"sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de",
"sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289",
"sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b",
"sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c",
"sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"
"sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8",
"sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134",
"sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d",
"sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67",
"sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf",
"sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02",
"sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59",
"sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55",
"sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c",
"sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f",
"sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4",
"sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018",
"sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015",
"sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9",
"sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3",
"sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8",
"sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe",
"sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1",
"sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f",
"sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a",
"sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42",
"sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac",
"sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e",
"sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06",
"sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268",
"sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1",
"sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4",
"sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868",
"sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26",
"sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef",
"sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b",
"sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82",
"sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343",
"sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b",
"sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7",
"sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171",
"sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414",
"sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7",
"sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87",
"sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368",
"sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587",
"sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990",
"sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c",
"sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101",
"sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==2.0.0"
},
"osmpythontools": {
"hashes": [
"sha256:22548d86d68d36edff3cf9ab76c45745cda86a4ea0b28442e107d6b42992a426",
"sha256:ac67bea77b521941af648ef641ae1d006101948d1112475c256ea23ef31b426a"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.9.3"
"version": "==2.0.1"
},
"packaging": {
"hashes": [
@ -662,127 +458,6 @@
"markers": "python_version >= '3.8'",
"version": "==24.1"
},
"pandas": {
"hashes": [
"sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863",
"sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2",
"sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1",
"sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad",
"sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db",
"sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76",
"sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51",
"sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32",
"sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08",
"sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b",
"sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4",
"sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921",
"sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288",
"sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee",
"sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0",
"sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24",
"sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99",
"sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151",
"sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd",
"sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce",
"sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57",
"sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef",
"sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54",
"sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a",
"sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238",
"sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23",
"sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772",
"sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce",
"sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"
],
"markers": "python_version >= '3.9'",
"version": "==2.2.2"
},
"pillow": {
"hashes": [
"sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885",
"sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea",
"sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df",
"sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5",
"sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c",
"sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d",
"sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd",
"sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06",
"sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908",
"sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a",
"sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be",
"sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0",
"sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b",
"sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80",
"sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a",
"sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e",
"sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9",
"sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696",
"sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b",
"sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309",
"sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e",
"sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab",
"sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d",
"sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060",
"sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d",
"sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d",
"sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4",
"sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3",
"sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6",
"sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb",
"sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94",
"sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b",
"sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496",
"sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0",
"sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319",
"sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b",
"sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856",
"sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef",
"sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680",
"sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b",
"sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42",
"sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e",
"sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597",
"sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a",
"sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8",
"sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3",
"sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736",
"sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da",
"sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126",
"sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd",
"sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5",
"sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b",
"sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026",
"sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b",
"sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc",
"sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46",
"sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2",
"sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c",
"sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe",
"sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984",
"sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a",
"sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70",
"sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca",
"sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b",
"sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91",
"sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3",
"sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84",
"sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1",
"sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5",
"sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be",
"sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f",
"sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc",
"sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9",
"sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e",
"sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141",
"sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef",
"sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22",
"sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27",
"sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e",
"sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"
],
"markers": "python_version >= '3.8'",
"version": "==10.4.0"
},
"pydantic": {
"hashes": [
"sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a",
@ -895,79 +570,6 @@
"markers": "python_version >= '3.8'",
"version": "==2.18.0"
},
"pyogrio": {
"hashes": [
"sha256:019731a856a9abfe909e86f50eb13f8362f6742337caf757c54b7c8acfe75b89",
"sha256:083351b258b3e08b6c6085dac560bd321b68de5cb4a66229095da68d5f3d696b",
"sha256:13642608a1cd67797ae8b5d792b0518d8ef3eb76506c8232ab5eaa1ea1159dff",
"sha256:17420febc17651876d5140b54b24749aa751d482b5f9ef6267b8053e6e962876",
"sha256:1a495ca4fb77c69595747dd688f8f17bb7d2ea9cd86603aa71c7fc98cc8b4174",
"sha256:2829615cf58b1b24a9f96fea42abedaa1a800dd351c67374cc2f6341138608f3",
"sha256:2e98913fa183f7597c609e774820a149e9329fd2a0f8d33978252fbd00ae87e6",
"sha256:2f2ec57ab74785db9c2bf47c0a6731e5175595a13f8253f06fa84136adb310a9",
"sha256:30cbeeaedb9bced7012487e7438919aa0c7dfba18ac3d4315182b46eb3139b9d",
"sha256:3a2fcaa269031dbbc8ebd91243c6452c5d267d6df939c008ab7533413c9cf92d",
"sha256:3f964002d445521ad5b8e732a6b5ef0e2d2be7fe566768e5075c1d71398da64a",
"sha256:4a289584da6df7ca318947301fe0ba9177e7f863f63110e087c80ac5f3658de8",
"sha256:4da0b9deb380bd9a200fee13182c4f95b02b4c554c923e2e0032f32aaf1439ed",
"sha256:4e0f90a6c3771ee1f1fea857778b4b6a1b64000d851b819f435f9091b3c38c60",
"sha256:6a6fa2e8cf95b3d4a7c0fac48bce6e5037579e28d3eb33b53349d6e11f15e5a8",
"sha256:6dc94a67163218581c7df275223488ac9b31dc582ccd756da607c3338908566c",
"sha256:796e4f6a4e769b2eb6fea9a10546ea4bdee16182d1e29802b4d6349363c3c1d7",
"sha256:7fcafed24371fe6e23bcf5abebbb29269f8d79915f1dd818ac85453657ea714a",
"sha256:9440466c0211ac81f3417f274da5903f15546b486f76b2f290e74a56aaf0e737",
"sha256:959022f3ad04053f8072dc9a2ad110c46edd9e4f92352061ba835fc91df3ca96",
"sha256:d668cb10f2bf6ccd7c402f91e8b06290722dd09dbe265ae95b2c13db29ebeba0",
"sha256:e38c3c6d37cf2cc969407e4d051dcb507cfd948eb26c7b0840c4f7d7d4a71bd4",
"sha256:f47c9b6818cc0f420015b672d5dcc488530a5ee63e5ba35a184957b21ea3922a",
"sha256:f5d80eb846be4fc4e642cbedc1ed0c143e8d241653382ecc76a7620bbd2a5c3a",
"sha256:f8bf193269ea9d347ac3ddada960a59f1ab2e4a5c009be95dc70e6505346b2fc",
"sha256:fb04bd80964428491951766452f0071b0bc37c7d38c45ef02502dbd83e5d74a0"
],
"markers": "python_version >= '3.8'",
"version": "==0.9.0"
},
"pyproj": {
"hashes": [
"sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3",
"sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746",
"sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb",
"sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f",
"sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2",
"sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a",
"sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf",
"sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b",
"sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe",
"sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae",
"sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f",
"sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110",
"sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915",
"sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8",
"sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0",
"sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35",
"sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf",
"sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132",
"sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877",
"sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4",
"sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f",
"sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e",
"sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85",
"sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882",
"sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1",
"sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f",
"sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"
],
"markers": "python_version >= '3.9'",
"version": "==3.6.1"
},
"python-dateutil": {
"hashes": [
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.0.post0"
},
"python-dotenv": {
"hashes": [
"sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca",
@ -983,13 +585,6 @@
"markers": "python_version >= '3.8'",
"version": "==0.0.9"
},
"pytz": {
"hashes": [
"sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
"sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
],
"version": "==2024.1"
},
"pywikibot": {
"hashes": [
"sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2",
@ -1155,14 +750,6 @@
"markers": "python_version >= '3.7'",
"version": "==1.5.4"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
@ -1195,98 +782,6 @@
"markers": "python_version >= '3.8'",
"version": "==4.12.2"
},
"tzdata": {
"hashes": [
"sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
"sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
],
"markers": "python_version >= '2'",
"version": "==2024.1"
},
"ujson": {
"hashes": [
"sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e",
"sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b",
"sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6",
"sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7",
"sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9",
"sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd",
"sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569",
"sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f",
"sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51",
"sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20",
"sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1",
"sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf",
"sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc",
"sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e",
"sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a",
"sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539",
"sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27",
"sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165",
"sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126",
"sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1",
"sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816",
"sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64",
"sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8",
"sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e",
"sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287",
"sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3",
"sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb",
"sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0",
"sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043",
"sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557",
"sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e",
"sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21",
"sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d",
"sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd",
"sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0",
"sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337",
"sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753",
"sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804",
"sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f",
"sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f",
"sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5",
"sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5",
"sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1",
"sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00",
"sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2",
"sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050",
"sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e",
"sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4",
"sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8",
"sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996",
"sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6",
"sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1",
"sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f",
"sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1",
"sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4",
"sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b",
"sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88",
"sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518",
"sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5",
"sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770",
"sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4",
"sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a",
"sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76",
"sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe",
"sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988",
"sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1",
"sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5",
"sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b",
"sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7",
"sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8",
"sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc",
"sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a",
"sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720",
"sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3",
"sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b",
"sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9",
"sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1",
"sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"
],
"markers": "python_version >= '3.8'",
"version": "==5.10.0"
},
"urllib3": {
"hashes": [
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
@ -1300,11 +795,11 @@
"standard"
],
"hashes": [
"sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81",
"sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"
"sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81",
"sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"
],
"markers": "python_version >= '3.8'",
"version": "==0.30.1"
"version": "==0.30.3"
},
"uvloop": {
"hashes": [

View File

@ -14,6 +14,6 @@ OSM_CACHE_DIR = Path(cache_dir_string)
logger = logging.getLogger(__name__)
logging.basicConfig(
level = logging.INFO,
level = logging.DEBUG,
format = '%(asctime)s - %(name)s\t- %(levelname)s\t- %(message)s'
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -2,9 +2,8 @@ import math as m
import yaml
import logging
from typing import List, Tuple
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder
from OSMPythonTools import cachingStrategy
from OSMPythonTools.cachingStrategy import CachingStrategy, JSON
from pywikibot import ItemPage, Site
from pywikibot import config
config.put_throttle = 0
@ -33,10 +32,8 @@ class LandmarkManager:
tag_coeff: float # coeff to adjust weight of tags
N_important: int # number of important landmarks to consider
preferences: Preferences # preferences of visit
location: Tuple[float] # coordinates around which to find a path
def __init__(self, preferences: Preferences, coordinates: Tuple[float, float]) -> None:
def __init__(self) -> None:
with constants.AMENITY_SELECTORS_PATH.open('r') as f:
self.amenity_selectors = yaml.safe_load(f)
@ -49,12 +46,12 @@ class LandmarkManager:
self.park_coeff = parameters['park_coeff']
self.tag_coeff = parameters['tag_coeff']
self.N_important = parameters['N_important']
self.preferences = preferences
self.location = coordinates
self.overpass = Overpass()
CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR)
def generate_landmarks_list(self) -> Tuple[List[Landmark], List[Landmark]] :
def generate_landmarks_list(self, center_coordinates: tuple[float, float], preferences: Preferences) -> tuple[list[Landmark], list[Landmark]]:
"""
Generate and prioritize a list of landmarks based on user preferences.
@ -62,62 +59,71 @@ class LandmarkManager:
and current location. It scores and corrects these landmarks, removes duplicates, and then selects the most important
landmarks based on a predefined criterion.
Parameters:
center_coordinates (tuple[float, float]): The latitude and longitude of the center location around which to search.
preferences (Preferences): The user's preference settings that influence the landmark selection.
Returns:
Tuple[List[Landmark], List[Landmark]]:
tuple[list[Landmark], list[Landmark]]:
- A list of all existing landmarks.
- A list of the most important landmarks based on the user's preferences.
"""
L = []
# List for sightseeing
if self.preferences.sightseeing.score != 0 :
L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, coordinates=self.location)
self.correct_score(L1, self.preferences.sightseeing)
bbox = self.create_bbox(center_coordinates)
# list for sightseeing
if preferences.sightseeing.score != 0:
score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)
L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], SIGHTSEEING, score_function)
self.correct_score(L1, preferences.sightseeing)
L += L1
# List for nature
if self.preferences.nature.score != 0 :
L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, coordinates=self.location)
self.correct_score(L2, self.preferences.nature)
# list for nature
if preferences.nature.score != 0:
score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)
L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], NATURE, score_function)
self.correct_score(L2, preferences.nature)
L += L2
# List for shopping
if self.preferences.shopping.score != 0 :
L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, coordinates=self.location)
self.correct_score(L3, self.preferences.shopping)
# list for shopping
if preferences.shopping.score != 0:
score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff))
L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], SHOPPING, score_function)
self.correct_score(L3, preferences.shopping)
L += L3
L = self.remove_duplicates(L)
L_constrained = take_most_important(L, self.N_important)
self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.')
return L, take_most_important(L, self.N_important)
return L, L_constrained
def remove_duplicates(self, landmarks: List[Landmark]) -> List[Landmark] :
def remove_duplicates(self, landmarks: list[Landmark]) -> list[Landmark]:
"""
Removes duplicate landmarks based on their names from the given list. Only retains the landmark with highest score
Parameters:
landmarks (List[Landmark]): A list of Landmark objects.
landmarks (list[Landmark]): A list of Landmark objects.
Returns:
List[Landmark]: A list of unique Landmark objects based on their names.
list[Landmark]: A list of unique Landmark objects based on their names.
"""
L_clean = []
names = []
for landmark in landmarks :
for landmark in landmarks:
if landmark.name in names:
continue
else :
else:
names.append(landmark.name)
L_clean.append(landmark)
return L_clean
def correct_score(self, landmarks: List[Landmark], preference: Preference) :
def correct_score(self, landmarks: list[Landmark], preference: Preference):
"""
Adjust the attractiveness score of each landmark in the list based on user preferences.
@ -125,24 +131,24 @@ class LandmarkManager:
The score adjustment is computed using a simple linear transformation based on the preference score.
Args:
landmarks (List[Landmark]): A list of landmarks whose scores need to be corrected.
landmarks (list[Landmark]): A list of landmarks whose scores need to be corrected.
preference (Preference): The user's preference settings that influence the attractiveness score adjustment.
Raises:
TypeError: If the type of any landmark in the list does not match the expected type in the preference.
"""
if len(landmarks) == 0 :
if len(landmarks) == 0:
return
if landmarks[0].type != preference.type :
if landmarks[0].type != preference.type:
raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {landmarks[0].name}")
for elem in landmarks :
for elem in landmarks:
elem.attractiveness = int(elem.attractiveness*preference.score/5) # arbitrary computation
def count_elements_close_to(self, coordinates: Tuple[float, float]) -> int:
def count_elements_close_to(self, coordinates: tuple[float, float]) -> int:
"""
Count the number of OpenStreetMap elements (nodes, ways, relations) within a specified radius of the given location.
@ -150,7 +156,7 @@ class LandmarkManager:
OpenStreetMap data to count the number of elements within that bounding box.
Args:
coordinates (Tuple[float, float]): The latitude and longitude of the location to search around.
coordinates (tuple[float, float]): The latitude and longitude of the location to search around.
Returns:
int: The number of elements (nodes, ways, relations) within the specified radius. Returns 0 if no elements
@ -164,32 +170,36 @@ class LandmarkManager:
alpha = (180*radius) / (6371000*m.pi)
bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha}
# Build the query to find elements within the radius
radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']],
elementType=['node', 'way', 'relation'])
try :
overpass = Overpass()
radius_result = overpass.query(radius_query)
# Build the query to find elements within the radius
radius_query = overpassQueryBuilder(
bbox=[bbox['latLower'],
bbox['lonLower'],
bbox['latHigher'],
bbox['lonHigher']],
elementType=['node', 'way', 'relation']
)
try:
radius_result = self.overpass.query(radius_query)
N_elem = radius_result.countWays() + radius_result.countRelations()
#print(f"There are {N_elem} ways/relations within 50m")
if N_elem is None :
self.logger.debug(f"There are {N_elem} ways/relations within 50m")
if N_elem is None:
return 0
return N_elem
except :
except:
return 0
def create_bbox(self, coordinates: Tuple[float, float]) -> Tuple[float, float, float, float]:
def create_bbox(self, coordinates: tuple[float, float]) -> tuple[float, float, float, float]:
"""
Create a bounding box around the given coordinates.
Args:
coordinates (Tuple[float, float]): The latitude and longitude of the center of the bounding box.
coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box.
Returns:
Tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
defining the bounding box.
"""
@ -212,44 +222,52 @@ class LandmarkManager:
return min_lat, min_lon, max_lat, max_lon
def fetch_landmarks(self, list_amenity: list, landmarktype: str, coordinates: Tuple[float, float]) -> List[Landmark] :
def fetch_landmarks(self, bbox: tuple, amenity_selector: dict, landmarktype: str, score_function: callable) -> list[Landmark]:
"""
Fetches landmarks of a specified type from OpenStreetMap (OSM) within a bounding box centered on given coordinates.
Args:
list_amenity (list): A list of OSM amenity queries to be used for fetching landmarks.
These queries are typically used to filter results (e.g., [''amenity'='place_of_worship']).
bbox (tuple[float, float, float, float]): The bounding box coordinates (min_lat, min_lon, max_lat, max_lon).
amenity_selector (dict): The Overpass API query selector for the desired landmark type.
landmarktype (str): The type of the landmark (e.g., 'sightseeing', 'nature', 'shopping').
coordinates (Tuple[float, float]): The central coordinates (latitude, longitude) for the bounding box.
score_function (callable): The function to compute the score of the landmark based on its attributes.
Returns:
List[Landmark]: A list of Landmark objects that were fetched and filtered based on the provided criteria.
list[Landmark]: A list of Landmark objects that were fetched and filtered based on the provided criteria.
Notes:
- The bounding box is created around the given coordinates with a side length defined by `self.city_bbox_side`.
- Landmarks are fetched using Overpass API queries.
- Selectors are translated from the dictionary to the Overpass query format. (e.g., 'amenity'='place_of_worship')
- Landmarks are filtered based on various conditions including tags and type.
- Scores are assigned to landmarks based on their attributes and surrounding elements.
"""
# Create bbox around start location
bbox = self.create_bbox(coordinates)
return_list = []
# Initialize some variables
N = 0
L = []
overpass = Overpass()
for amenity in list_amenity :
query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
result = overpass.query(query)
N += result.countElements()
# caution, when applying a list of selectors, overpass will search for elements that match ALL selectors simultaneously
# we need to split the selectors into separate queries and merge the results
for sel in dict_to_selector_list(amenity_selector):
self.logger.debug(f"Current selector: {sel}")
query = overpassQueryBuilder(
bbox = bbox,
elementType = ['way', 'relation'],
selector = sel,
# conditions = [],
includeCenter = True,
out = 'body'
)
try:
result = self.overpass.query(query)
except Exception as e:
self.logger.error(f"Error fetching landmarks: {e}")
return
for elem in result.elements():
name = elem.tag('name') # Add name
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
# TODO: exclude these from the get go
# skip if unprecise location
if name is None or location[0] is None:
continue
@ -262,65 +280,86 @@ class LandmarkManager:
if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes':
continue
else :
osm_type = elem.type() # Add type : 'way' or 'relation'
osm_id = elem.id() # Add OSM id
elem_type = landmarktype # Add the landmark type as 'sightseeing,
n_tags = len(elem.tags().keys()) # Add number of tags
osm_type = elem.type() # Add type: 'way' or 'relation'
osm_id = elem.id() # Add OSM id
elem_type = landmarktype # Add the landmark type as 'sightseeing,
n_tags = len(elem.tags().keys()) # Add number of tags
# remove specific tags
skip = False
for tag in elem.tags().keys() :
if "pay" in tag :
n_tags -= 1 # discard payment options for tags
# remove specific tags
skip = False
for tag in elem.tags().keys():
if "pay" in tag:
n_tags -= 1 # discard payment options for tags
if "disused" in tag :
skip = True # skip disused amenities
if "disused" in tag:
skip = True # skip disused amenities
break
if "wikipedia" in tag:
n_tags += 3 # wikipedia entries count more
if tag == "wikidata":
Q = elem.tag('wikidata')
site = Site("wikidata", "wikidata")
item = ItemPage(site, Q)
item.get()
n_languages = len(item.labels)
n_tags += n_languages/10
if elem_type != "nature":
if "leisure" in tag and elem.tag('leisure') == "park":
elem_type = "nature"
if landmarktype != SHOPPING:
if "shop" in tag:
skip = True
break
if "wikipedia" in tag :
n_tags += 3 # wikipedia entries count more
if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
skip = True
break
if tag == "wikidata" :
Q = elem.tag('wikidata')
site = Site("wikidata", "wikidata")
item = ItemPage(site, Q)
item.get()
n_languages = len(item.labels)
n_tags += n_languages/10
if skip:
continue
if elem_type != "nature" :
if "leisure" in tag and elem.tag('leisure') == "park":
elem_type = "nature"
score = score_function(location, n_tags)
if score != 0:
# Generate the landmark and append it to the list
landmark = Landmark(
name=name,
type=elem_type,
location=location,
osm_type=osm_type,
osm_id=osm_id,
attractiveness=score,
must_do=False,
n_tags=int(n_tags)
)
return_list.append(landmark)
self.logger.debug(f"Fetched {len(return_list)} landmarks of type {landmarktype} in {bbox}")
if amenity not in ["'shop'='department_store'", "'shop'='mall'"] :
if "shop" in tag :
skip = True
break
if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
skip = True
break
if skip:
continue
# Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT
if amenity == "'amenity'='place_of_worship'" :
#score = int((count_elements_close_to(location, radius) + (n_tags*tag_coeff) )*church_coeff)
score = int((self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)
elif amenity == "'leisure'='park'" :
score = int((self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)
else :
score = int(self.count_elements_close_to(location) + ((n_tags**1.2)*self.tag_coeff))
if score is not None :
# Generate the landmark and append it to the list
#print(f"There are {n_tags} tags on this Landmark. Total score : {score}\n")
landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=int(n_tags))
L.append(landmark)
return L
return return_list
def dict_to_selector_list(d: dict) -> list:
"""
Convert a dictionary of key-value pairs to a list of Overpass query strings.
Args:
d (dict): A dictionary of key-value pairs representing the selector.
Returns:
list: A list of strings representing the Overpass query selectors.
"""
return_list = []
for key, value in d.items():
if type(value) == list:
val = '|'.join(value)
return_list.append(f'{key}~"{val}"')
elif type(value) == str and len(value) == 0:
return_list.append(f'{key}')
else:
return_list.append(f'{key}={value}')
return return_list

View File

@ -24,9 +24,9 @@ def write_data(L: List[Landmark], file_name: str):
def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] = None) -> List[Landmark]:
manager = LandmarkManager()
preferences = Preferences(
sightseeing=Preference(
name='sightseeing',
@ -56,10 +56,12 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
#finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
manager = LandmarkManager(preferences=preferences, coordinates=start_coords)
# Generate the landmarks from the start location
landmarks, landmarks_short = manager.generate_landmarks_list()
landmarks, landmarks_short = manager.generate_landmarks_list(
center_coordinates = start_coords,
preferences = preferences
)
# Store data to file for debug purposes
# write_data(landmarks, "landmarks_Strasbourg.txt")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: placeholder

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: osm-cache
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5Gi"

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: nav-service
spec:
selector:
app: nav-backend
ports:
- protocol: TCP
port: 8000
targetPort: 8000