Merge branch 'feature/backend/unify-api-communication'
This commit is contained in:
commit
c26d9222bd
@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
registry: git.kluster.moll.re
|
||||
username: ${{ gitea.repository_owner }}
|
||||
password: ${{ secrets.GITEA_TOKEN}}
|
||||
password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
cache/
|
@ -8,5 +8,7 @@ numpy = "*"
|
||||
scipy = "*"
|
||||
fastapi = "*"
|
||||
osmpythontools = "*"
|
||||
pydantic = "*"
|
||||
shapely = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
518
backend/Pipfile.lock
generated
518
backend/Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "15cc819367b908ccf7d61e3f2b45574c248556600dfeaa795e19045d6781fe90"
|
||||
"sha256": "0f88c01cde3be9a6332acec33fa0ccf13b6e122a6df8ee5cfefa52ba1e98034f"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@ -40,11 +40,11 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
|
||||
"sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
|
||||
"sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516",
|
||||
"sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2024.2.2"
|
||||
"version": "==2024.6.2"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
@ -122,11 +122,11 @@
|
||||
},
|
||||
"email-validator": {
|
||||
"hashes": [
|
||||
"sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84",
|
||||
"sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"
|
||||
"sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631",
|
||||
"sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.1.1"
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"exceptiongroup": {
|
||||
"hashes": [
|
||||
@ -155,51 +155,51 @@
|
||||
},
|
||||
"fonttools": {
|
||||
"hashes": [
|
||||
"sha256:00d9abf4b400f98fb895566eb298f60432b4b29048e3dc02807427b09a06604e",
|
||||
"sha256:05e4291db6af66f466a203d9922e4c1d3e18ef16868f76f10b00e2c3b9814df2",
|
||||
"sha256:15efb2ba4b8c2d012ee0bb7a850c2e4780c530cc83ec8e843b2a97f8b3a5fd4b",
|
||||
"sha256:1dc626de4b204d025d029e646bae8fdbf5acd9217158283a567f4b523fda3bae",
|
||||
"sha256:21921e5855c399d10ddfc373538b425cabcf8b3258720b51450909e108896450",
|
||||
"sha256:309b617942041073ffa96090d320b99d75648ed16e0c67fb1aa7788e06c834de",
|
||||
"sha256:346d08ff92e577b2dc5a0c228487667d23fe2da35a8b9a8bba22c2b6ba8be21c",
|
||||
"sha256:35af630404223273f1d7acd4761f399131c62820366f53eac029337069f5826a",
|
||||
"sha256:46cc5d06ee05fd239c45d7935aaffd060ee773a88b97e901df50478247472643",
|
||||
"sha256:4b0b9eb0f55dce9c7278ad4175f1cbaed23b799dce5ecc20e3213da241584140",
|
||||
"sha256:4b419207e53db1599b3d385afd4bca6692c219d53732890d0814a2593104d0e2",
|
||||
"sha256:4c3ad89204c2d7f419436f1d6fde681b070c5e20b888beb57ccf92f640628cc9",
|
||||
"sha256:52f6001814ec5e0c961cabe89642f7e8d7e07892b565057aa526569b9ebb711c",
|
||||
"sha256:5ecb88318ff249bd2a715e7aec36774ce7ae3441128007ef72a39a60601f4a8f",
|
||||
"sha256:70d87f2099006304d33438bdaa5101953b7e22e23a93b1c7b7ed0f32ff44b423",
|
||||
"sha256:73ba38b98c012957940a04d9eb5439b42565ac892bba8cfc32e10d88e73921fe",
|
||||
"sha256:7467161f1eed557dbcec152d5ee95540200b1935709fa73307da16bc0b7ca361",
|
||||
"sha256:7dccf4666f716e5e0753f0fa28dad2f4431154c87747bc781c838b8a5dca990e",
|
||||
"sha256:859399b7adc8ac067be8e5c80ef4bb2faddff97e9b40896a9de75606a43d0469",
|
||||
"sha256:8873d6edd1dae5c088dd3d61c9fd4dd80c827c486fa224d368233e7f33dc98af",
|
||||
"sha256:890e7a657574610330e42dd1e38d3b9e0a8cb0eff3da080f80995460a256d3dd",
|
||||
"sha256:89b53386214197bd5b3e3c753895bad691de84726ced3c222a59cde1dd12d57b",
|
||||
"sha256:8b186cd6b8844f6cf04a7e0a174bc3649d3deddbfc10dc59846a4381f796d348",
|
||||
"sha256:9180775c9535389a665cae7c5282f8e07754beabf59b66aeba7f6bfeb32a3652",
|
||||
"sha256:95e8a5975d08d0b624a14eec0f987e204ad81b480e24c5436af99170054434b8",
|
||||
"sha256:9725687db3c1cef13c0f40b380c3c15bea0113f4d0231b204d58edd5f2a53d90",
|
||||
"sha256:9a5d1b0475050056d2e3bc378014f2ea2230e8ae434eeac8dfb182aa8efaf642",
|
||||
"sha256:9ed23a03b7d9f0e29ca0679eafe5152aeccb0580312a3fc36f0662e178b4791b",
|
||||
"sha256:a4daf2751a98c69d9620717826ed6c5743b662ef0ae7bb33dc6c205425e48eba",
|
||||
"sha256:a64e72d2c144630e017ac9c1c416ddf8ac43bef9a083bf81fe08c0695f0baa95",
|
||||
"sha256:a791f002d1b717268235cfae7e4957b7fd132e92e2c5400e521bf191f1b3a9a5",
|
||||
"sha256:b4cba644e2515d685d4ee3ca2fbb5d53930a0e9ec2cf332ed704dc341b145878",
|
||||
"sha256:b9a22cf1adaae7b2ba2ed7d8651a4193a4f348744925b4b740e6b38a94599c5b",
|
||||
"sha256:bb7d206fa5ba6e082ba5d5e1b7107731029fc3a55c71c48de65121710d817986",
|
||||
"sha256:cf694159528022daa71b1777cb6ec9e0ebbdd29859f3e9c845826cafaef4ca29",
|
||||
"sha256:d0184aa88865339d96f7f452e8c5b621186ef7638744d78bf9b775d67e206819",
|
||||
"sha256:d272c7e173c3085308345ccc7fb2ad6ce7f415d777791dd6ce4e8140e354d09c",
|
||||
"sha256:d2cc7906bc0afdd2689aaf88b910307333b1f936262d1d98f25dbf8a5eb2e829",
|
||||
"sha256:e03dae26084bb3632b4a77b1cd0419159d2226911aff6dc4c7e3058df68648c6",
|
||||
"sha256:e176249292eccd89f81d39f514f2b5e8c75dfc9cef8653bdc3021d06697e9eff",
|
||||
"sha256:ebb183ed8b789cece0bd6363121913fb6da4034af89a2fa5408e42a1592889a8",
|
||||
"sha256:fb8cd6559f0ae3a8f5e146f80ab2a90ad0325a759be8d48ee82758a0b89fa0aa"
|
||||
"sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d",
|
||||
"sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64",
|
||||
"sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2",
|
||||
"sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4",
|
||||
"sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6",
|
||||
"sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b",
|
||||
"sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f",
|
||||
"sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380",
|
||||
"sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e",
|
||||
"sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749",
|
||||
"sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20",
|
||||
"sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0",
|
||||
"sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4",
|
||||
"sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5",
|
||||
"sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206",
|
||||
"sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9",
|
||||
"sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac",
|
||||
"sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1",
|
||||
"sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce",
|
||||
"sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4",
|
||||
"sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12",
|
||||
"sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca",
|
||||
"sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d",
|
||||
"sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068",
|
||||
"sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796",
|
||||
"sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec",
|
||||
"sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea",
|
||||
"sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f",
|
||||
"sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005",
|
||||
"sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2",
|
||||
"sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06",
|
||||
"sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109",
|
||||
"sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002",
|
||||
"sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9",
|
||||
"sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a",
|
||||
"sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68",
|
||||
"sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6",
|
||||
"sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161",
|
||||
"sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd",
|
||||
"sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d",
|
||||
"sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee",
|
||||
"sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.52.4"
|
||||
"version": "==4.53.0"
|
||||
},
|
||||
"geojson": {
|
||||
"hashes": [
|
||||
@ -667,98 +667,107 @@
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b",
|
||||
"sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818",
|
||||
"sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20",
|
||||
"sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0",
|
||||
"sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010",
|
||||
"sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a",
|
||||
"sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea",
|
||||
"sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c",
|
||||
"sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71",
|
||||
"sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110",
|
||||
"sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be",
|
||||
"sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a",
|
||||
"sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a",
|
||||
"sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5",
|
||||
"sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed",
|
||||
"sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd",
|
||||
"sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c",
|
||||
"sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e",
|
||||
"sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0",
|
||||
"sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c",
|
||||
"sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a",
|
||||
"sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b",
|
||||
"sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0",
|
||||
"sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6",
|
||||
"sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2",
|
||||
"sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a",
|
||||
"sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30",
|
||||
"sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218",
|
||||
"sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5",
|
||||
"sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07",
|
||||
"sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2",
|
||||
"sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4",
|
||||
"sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764",
|
||||
"sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef",
|
||||
"sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3",
|
||||
"sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"
|
||||
"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"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==1.26.4"
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"orjson": {
|
||||
"hashes": [
|
||||
"sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2",
|
||||
"sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b",
|
||||
"sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5",
|
||||
"sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b",
|
||||
"sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0",
|
||||
"sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0",
|
||||
"sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7",
|
||||
"sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42",
|
||||
"sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5",
|
||||
"sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa",
|
||||
"sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818",
|
||||
"sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25",
|
||||
"sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08",
|
||||
"sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7",
|
||||
"sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b",
|
||||
"sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754",
|
||||
"sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0",
|
||||
"sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912",
|
||||
"sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b",
|
||||
"sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3",
|
||||
"sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700",
|
||||
"sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78",
|
||||
"sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290",
|
||||
"sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134",
|
||||
"sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294",
|
||||
"sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0",
|
||||
"sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5",
|
||||
"sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d",
|
||||
"sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d",
|
||||
"sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16",
|
||||
"sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195",
|
||||
"sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da",
|
||||
"sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8",
|
||||
"sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098",
|
||||
"sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8",
|
||||
"sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063",
|
||||
"sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534",
|
||||
"sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d",
|
||||
"sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109",
|
||||
"sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d",
|
||||
"sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3",
|
||||
"sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2",
|
||||
"sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf",
|
||||
"sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069",
|
||||
"sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7",
|
||||
"sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"
|
||||
"sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01",
|
||||
"sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa",
|
||||
"sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5",
|
||||
"sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04",
|
||||
"sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d",
|
||||
"sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e",
|
||||
"sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b",
|
||||
"sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b",
|
||||
"sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268",
|
||||
"sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211",
|
||||
"sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c",
|
||||
"sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c",
|
||||
"sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969",
|
||||
"sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a",
|
||||
"sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f",
|
||||
"sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932",
|
||||
"sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26",
|
||||
"sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6",
|
||||
"sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214",
|
||||
"sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2",
|
||||
"sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f",
|
||||
"sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96",
|
||||
"sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a",
|
||||
"sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d",
|
||||
"sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38",
|
||||
"sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807",
|
||||
"sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09",
|
||||
"sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6",
|
||||
"sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e",
|
||||
"sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5",
|
||||
"sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86",
|
||||
"sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63",
|
||||
"sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2",
|
||||
"sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4",
|
||||
"sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595",
|
||||
"sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228",
|
||||
"sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9",
|
||||
"sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7",
|
||||
"sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40",
|
||||
"sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3",
|
||||
"sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139",
|
||||
"sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1",
|
||||
"sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b",
|
||||
"sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47",
|
||||
"sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1",
|
||||
"sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==3.10.3"
|
||||
"version": "==3.10.5"
|
||||
},
|
||||
"osmpythontools": {
|
||||
"hashes": [
|
||||
@ -769,11 +778,11 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
|
||||
"sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
|
||||
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
|
||||
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==24.0"
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==24.1"
|
||||
},
|
||||
"pandas": {
|
||||
"hashes": [
|
||||
@ -887,96 +896,97 @@
|
||||
},
|
||||
"pydantic": {
|
||||
"hashes": [
|
||||
"sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7",
|
||||
"sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"
|
||||
"sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52",
|
||||
"sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.7.2"
|
||||
"version": "==2.7.4"
|
||||
},
|
||||
"pydantic-core": {
|
||||
"hashes": [
|
||||
"sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9",
|
||||
"sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b",
|
||||
"sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426",
|
||||
"sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc",
|
||||
"sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9",
|
||||
"sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc",
|
||||
"sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4",
|
||||
"sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702",
|
||||
"sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6",
|
||||
"sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231",
|
||||
"sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083",
|
||||
"sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60",
|
||||
"sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7",
|
||||
"sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a",
|
||||
"sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4",
|
||||
"sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6",
|
||||
"sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39",
|
||||
"sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0",
|
||||
"sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026",
|
||||
"sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c",
|
||||
"sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442",
|
||||
"sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7",
|
||||
"sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044",
|
||||
"sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd",
|
||||
"sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94",
|
||||
"sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361",
|
||||
"sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af",
|
||||
"sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4",
|
||||
"sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022",
|
||||
"sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7",
|
||||
"sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd",
|
||||
"sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8",
|
||||
"sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2",
|
||||
"sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5",
|
||||
"sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be",
|
||||
"sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c",
|
||||
"sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9",
|
||||
"sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417",
|
||||
"sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1",
|
||||
"sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326",
|
||||
"sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4",
|
||||
"sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb",
|
||||
"sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34",
|
||||
"sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d",
|
||||
"sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9",
|
||||
"sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812",
|
||||
"sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558",
|
||||
"sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779",
|
||||
"sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be",
|
||||
"sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c",
|
||||
"sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe",
|
||||
"sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0",
|
||||
"sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c",
|
||||
"sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2",
|
||||
"sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a",
|
||||
"sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab",
|
||||
"sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048",
|
||||
"sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3",
|
||||
"sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106",
|
||||
"sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d",
|
||||
"sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb",
|
||||
"sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6",
|
||||
"sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b",
|
||||
"sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef",
|
||||
"sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5",
|
||||
"sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb",
|
||||
"sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c",
|
||||
"sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06",
|
||||
"sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059",
|
||||
"sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943",
|
||||
"sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1",
|
||||
"sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2",
|
||||
"sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c",
|
||||
"sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522",
|
||||
"sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f",
|
||||
"sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78",
|
||||
"sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7",
|
||||
"sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a",
|
||||
"sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"
|
||||
"sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3",
|
||||
"sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8",
|
||||
"sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8",
|
||||
"sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30",
|
||||
"sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a",
|
||||
"sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8",
|
||||
"sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d",
|
||||
"sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc",
|
||||
"sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2",
|
||||
"sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab",
|
||||
"sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077",
|
||||
"sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e",
|
||||
"sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9",
|
||||
"sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9",
|
||||
"sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef",
|
||||
"sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1",
|
||||
"sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507",
|
||||
"sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528",
|
||||
"sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558",
|
||||
"sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b",
|
||||
"sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154",
|
||||
"sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724",
|
||||
"sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695",
|
||||
"sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9",
|
||||
"sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851",
|
||||
"sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805",
|
||||
"sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a",
|
||||
"sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5",
|
||||
"sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94",
|
||||
"sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c",
|
||||
"sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d",
|
||||
"sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef",
|
||||
"sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26",
|
||||
"sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2",
|
||||
"sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c",
|
||||
"sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0",
|
||||
"sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2",
|
||||
"sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4",
|
||||
"sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d",
|
||||
"sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2",
|
||||
"sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce",
|
||||
"sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34",
|
||||
"sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f",
|
||||
"sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d",
|
||||
"sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b",
|
||||
"sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07",
|
||||
"sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312",
|
||||
"sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057",
|
||||
"sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d",
|
||||
"sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af",
|
||||
"sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb",
|
||||
"sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd",
|
||||
"sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78",
|
||||
"sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b",
|
||||
"sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223",
|
||||
"sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a",
|
||||
"sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4",
|
||||
"sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5",
|
||||
"sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23",
|
||||
"sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a",
|
||||
"sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4",
|
||||
"sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8",
|
||||
"sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d",
|
||||
"sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443",
|
||||
"sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e",
|
||||
"sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f",
|
||||
"sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e",
|
||||
"sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d",
|
||||
"sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc",
|
||||
"sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443",
|
||||
"sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be",
|
||||
"sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2",
|
||||
"sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee",
|
||||
"sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f",
|
||||
"sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae",
|
||||
"sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864",
|
||||
"sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4",
|
||||
"sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951",
|
||||
"sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.18.3"
|
||||
"version": "==2.18.4"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
@ -1120,6 +1130,54 @@
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==1.13.1"
|
||||
},
|
||||
"shapely": {
|
||||
"hashes": [
|
||||
"sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f",
|
||||
"sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0",
|
||||
"sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf",
|
||||
"sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6",
|
||||
"sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa",
|
||||
"sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113",
|
||||
"sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9",
|
||||
"sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398",
|
||||
"sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0",
|
||||
"sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65",
|
||||
"sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d",
|
||||
"sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf",
|
||||
"sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242",
|
||||
"sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9",
|
||||
"sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f",
|
||||
"sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7",
|
||||
"sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07",
|
||||
"sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53",
|
||||
"sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328",
|
||||
"sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd",
|
||||
"sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8",
|
||||
"sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879",
|
||||
"sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e",
|
||||
"sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679",
|
||||
"sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47",
|
||||
"sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7",
|
||||
"sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6",
|
||||
"sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc",
|
||||
"sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7",
|
||||
"sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17",
|
||||
"sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b",
|
||||
"sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c",
|
||||
"sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa",
|
||||
"sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30",
|
||||
"sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d",
|
||||
"sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac",
|
||||
"sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58",
|
||||
"sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc",
|
||||
"sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35",
|
||||
"sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c",
|
||||
"sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.0.4"
|
||||
},
|
||||
"shellingham": {
|
||||
"hashes": [
|
||||
"sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686",
|
||||
@ -1170,11 +1228,11 @@
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8",
|
||||
"sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"
|
||||
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
|
||||
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.12.0"
|
||||
"version": "==4.12.2"
|
||||
},
|
||||
"tzdata": {
|
||||
"hashes": [
|
||||
@ -1273,11 +1331,11 @@
|
||||
"standard"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:78fa0b5f56abb8562024a59041caeb555c86e48d0efdd23c3fe7de7a4075bdab",
|
||||
"sha256:f678dec4fa3a39706bbf49b9ec5fc40049d42418716cea52b53f07828a60aa37"
|
||||
"sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81",
|
||||
"sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==0.30.0"
|
||||
"version": "==0.30.1"
|
||||
},
|
||||
"uvloop": {
|
||||
"hashes": [
|
||||
@ -1474,11 +1532,11 @@
|
||||
},
|
||||
"xarray": {
|
||||
"hashes": [
|
||||
"sha256:7ddedfe2294a0ab00f02d0fbdcb9c6300ec589f3cf436a9c7b7b577a12cd9bcf",
|
||||
"sha256:e0eb1cb265f265126795f388ed9591f3c752f2aca491f6c0576711fd15b708f2"
|
||||
"sha256:0b91e0bc4dc0296947947640fe31ec6e867ce258d2f7cbc10bedf4a6d68340c7",
|
||||
"sha256:721a7394e8ec3d592b2d8ebe21eed074ac077dc1bb1bd777ce00e41700b4866c"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==2024.5.0"
|
||||
"version": "==2024.6.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
import app.src
|
@ -1,34 +0,0 @@
|
||||
from .src.optimizer import solve_optimization
|
||||
from .src.landmarks_manager import Landmark
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/optimize/{max_steps}/{print_details}")
|
||||
def main(max_steps: int, print_details: bool):
|
||||
|
||||
# CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
|
||||
#max_steps = 16
|
||||
|
||||
|
||||
# Initialize all landmarks (+ start and goal). Order matters here
|
||||
landmarks = []
|
||||
landmarks.append(Landmark("départ", -1, (0, 0)))
|
||||
landmarks.append(Landmark("tour eiffel", 99, (0,2))) # PUT IN JSON
|
||||
landmarks.append(Landmark("arc de triomphe", 99, (0,4)))
|
||||
landmarks.append(Landmark("louvre", 99, (0,6)))
|
||||
landmarks.append(Landmark("montmartre", 99, (0,10)))
|
||||
landmarks.append(Landmark("concorde", 99, (0,8)))
|
||||
landmarks.append(Landmark("arrivée", -1, (0, 0)))
|
||||
|
||||
|
||||
visiting_order = solve_optimization(landmarks, max_steps, print_details)
|
||||
|
||||
#return visiting_order
|
||||
|
||||
return("max steps :", max_steps, "\n", visiting_order)
|
||||
|
||||
|
||||
"""if __name__ == "__main__":
|
||||
main()"""
|
Binary file not shown.
Binary file not shown.
@ -1,57 +0,0 @@
|
||||
from OSMPythonTools.api import Api
|
||||
from OSMPythonTools.overpass import Overpass
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
# Defines the landmark class (aka some place there is to visit)
|
||||
@dataclass
|
||||
class Landmarkkkk :
|
||||
name : str
|
||||
attractiveness : int
|
||||
id : int
|
||||
|
||||
@dataclass
|
||||
class Landmark :
|
||||
name : str
|
||||
attractiveness : int
|
||||
loc : tuple
|
||||
|
||||
# Converts a OSM id to a landmark
|
||||
def add_from_id(id: int, score: int) :
|
||||
|
||||
try :
|
||||
s = 'way/' + str(id) # prepare string for query
|
||||
obj = api.query(s) # object to add
|
||||
except :
|
||||
s = 'relation/' + str(id) # prepare string for query
|
||||
obj = api.query(s) # object to add
|
||||
|
||||
return Landmarkkkk(obj.tag('name:fr'), score, id) # create Landmark out of it
|
||||
|
||||
|
||||
# take a lsit of tuples (id, score) to generate a list of landmarks
|
||||
def generate_landmarks(ids_and_scores: list) :
|
||||
|
||||
L = []
|
||||
for tup in ids_and_scores :
|
||||
L.append(add_from_id(tup[0], tup[1]))
|
||||
|
||||
return L
|
||||
|
||||
api = Api()
|
||||
|
||||
|
||||
l = (7515426, 70)
|
||||
t = (5013364, 100)
|
||||
n = (201611261, 99)
|
||||
a = (226413508, 50)
|
||||
m = (23762981, 30)
|
||||
|
||||
|
||||
ids_and_scores = [t, l, n, a, m]
|
||||
|
||||
landmarks = generate_landmarks(ids_and_scores)
|
||||
|
||||
|
||||
for obj in landmarks :
|
||||
print(obj)
|
@ -1,323 +0,0 @@
|
||||
from scipy.optimize import linprog
|
||||
import numpy as np
|
||||
from scipy.linalg import block_diag
|
||||
|
||||
|
||||
# landmarks = [Landmark_1, Landmark_2, ...]
|
||||
|
||||
# Convert the solution of the optimization into the list of edges to follow. Order is taken into account
|
||||
def untangle(resx: list) :
|
||||
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
|
||||
|
||||
order = []
|
||||
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
||||
|
||||
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
||||
|
||||
indx = nonzero_tup[0].tolist()
|
||||
indy = nonzero_tup[1].tolist()
|
||||
|
||||
vert = (indx[0], indy[0])
|
||||
|
||||
order.append(vert[0])
|
||||
order.append(vert[1])
|
||||
|
||||
while len(order) < n_edges + 1 :
|
||||
ind = indx.index(vert[1])
|
||||
|
||||
vert = (indx[ind], indy[ind])
|
||||
|
||||
order.append(vert[1])
|
||||
|
||||
return order
|
||||
|
||||
# Just to print the result
|
||||
def print_res(res, landmarks: list, P) :
|
||||
X = abs(res.x)
|
||||
order = untangle(X)
|
||||
things = []
|
||||
|
||||
"""N = int(np.sqrt(len(X)))
|
||||
for i in range(N):
|
||||
print(X[i*N:i*N+N])
|
||||
print("Optimal value:", -res.fun) # Minimization, so we negate to get the maximum
|
||||
print("Optimal point:", res.x)
|
||||
for i,x in enumerate(X) : X[i] = round(x,0)
|
||||
print(order)"""
|
||||
|
||||
if (X.sum()+1)**2 == len(X) :
|
||||
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 : ')
|
||||
|
||||
for idx in order :
|
||||
print('- ' + landmarks[idx].name)
|
||||
things.append(landmarks[idx].name)
|
||||
|
||||
steps = path_length(P, abs(res.x))
|
||||
print("\nSteps walked : " + str(steps))
|
||||
|
||||
return things
|
||||
|
||||
# Checks for cases of circular symmetry in the result
|
||||
def has_circle(resx: list) :
|
||||
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))
|
||||
|
||||
indx = nonzero_tup[0].tolist()
|
||||
indy = nonzero_tup[1].tolist()
|
||||
|
||||
|
||||
verts = []
|
||||
|
||||
for i, x in enumerate(indx) :
|
||||
verts.append((x, indy[i]))
|
||||
|
||||
|
||||
for vert in verts :
|
||||
visited = []
|
||||
visited.append(vert)
|
||||
|
||||
while len(visited) < n_edges + 1 :
|
||||
|
||||
try :
|
||||
ind = indx.index(vert[1])
|
||||
|
||||
vert = (indx[ind], indy[ind])
|
||||
|
||||
if vert in visited :
|
||||
return visited
|
||||
else :
|
||||
visited.append(vert)
|
||||
except :
|
||||
break
|
||||
|
||||
return []
|
||||
|
||||
# Constraint to not have d14 and d41 simultaneously. Does not prevent circular symmetry with more elements
|
||||
def break_sym(landmarks, A_ub, b_ub):
|
||||
L = len(landmarks)
|
||||
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)
|
||||
|
||||
"""for i in range(7):
|
||||
print(l[i*7:i*7+7])
|
||||
print("\n")"""
|
||||
|
||||
return A_ub, b_ub
|
||||
|
||||
# Constraint to not have circular paths. Want to go from start -> finish without unconnected loops
|
||||
def break_circle(landmarks, A_ub, b_ub, circle) :
|
||||
N = len(landmarks)
|
||||
l = [0]*N*N
|
||||
|
||||
for index in circle :
|
||||
x = index[0]
|
||||
y = index[1]
|
||||
l[x*N+y] = 1
|
||||
|
||||
A_ub = np.vstack((A_ub,l))
|
||||
b_ub.append(len(circle)-1)
|
||||
|
||||
"""print("\n\nPREVENT CIRCLE")
|
||||
for i in range(7):
|
||||
print(l[i*7:i*7+7])
|
||||
print("\n")"""
|
||||
|
||||
return A_ub, b_ub
|
||||
|
||||
# Constraint to respect max number of travels
|
||||
def respect_number(landmarks, A_ub, b_ub):
|
||||
h = []
|
||||
for i in range(len(landmarks)) : h.append([1]*len(landmarks))
|
||||
T = block_diag(*h)
|
||||
"""for l in T :
|
||||
for i in range(7):
|
||||
print(l[i*7:i*7+7])
|
||||
print("\n")"""
|
||||
return np.vstack((A_ub, T)), b_ub + [1]*len(landmarks)
|
||||
|
||||
# Constraint to tie the problem together. Necessary but not sufficient to avoid circles
|
||||
def respect_order(landmarks: list, A_eq, b_eq):
|
||||
N = len(landmarks)
|
||||
for i in range(N-1) : # Prevent stacked ones
|
||||
if i == 0 :
|
||||
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)
|
||||
|
||||
"""for i in range(7):
|
||||
print(l[i*7:i*7+7])
|
||||
print("\n")"""
|
||||
|
||||
return A_eq, b_eq
|
||||
|
||||
# Compute manhattan distance between 2 locations
|
||||
def manhattan_distance(loc1: tuple, loc2: tuple):
|
||||
x1, y1 = loc1
|
||||
x2, y2 = loc2
|
||||
return abs(x1 - x2) + abs(y1 - y2)
|
||||
|
||||
# Constraint to not stay in position
|
||||
def init_eq_not_stay(landmarks):
|
||||
L = len(landmarks)
|
||||
l = [0]*L*L
|
||||
|
||||
|
||||
for i in range(L) :
|
||||
for j in range(L) :
|
||||
if j == i :
|
||||
l[j + i*L] = 1
|
||||
l[L-1] = 1 # cannot skip from start to finish
|
||||
#A_eq = np.array([np.array(xi) for xi in A_eq]) # Must convert A_eq into an np array
|
||||
l = np.array(np.array(l))
|
||||
|
||||
"""for i in range(7):
|
||||
print(l[i*7:i*7+7])"""
|
||||
|
||||
return [l], [0]
|
||||
|
||||
# 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, max_steps: int):
|
||||
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
|
||||
c = []
|
||||
# Coefficients of inequality constraints (left-hand side)
|
||||
A = []
|
||||
for i, spot1 in enumerate(landmarks) :
|
||||
dist_table = [0]*len(landmarks)
|
||||
c.append(-spot1.attractiveness)
|
||||
for j, spot2 in enumerate(landmarks) :
|
||||
dist_table[j] = manhattan_distance(spot1.loc, spot2.loc)
|
||||
A.append(dist_table)
|
||||
c = c*len(landmarks)
|
||||
A_ub = []
|
||||
for line in A :
|
||||
#print(line)
|
||||
A_ub += line
|
||||
return c, A_ub, [max_steps]
|
||||
|
||||
# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1
|
||||
def respect_user_mustsee(landmarks: list, A_eq: list, b_eq: list) :
|
||||
L = len(landmarks)
|
||||
H = 0 # sort of heuristic to get an idea of the number of steps needed
|
||||
for i in landmarks :
|
||||
if i.name == "départ" : elem_prev = i # list of all matches
|
||||
for i, elem in enumerate(landmarks) :
|
||||
if elem.attractiveness == -1 :
|
||||
l = [0]*L*L
|
||||
if elem.name != "arrivée" :
|
||||
for j in range(L) :
|
||||
l[j +i*L] = 1
|
||||
|
||||
else : # This ensures we go to goal
|
||||
for k in range(L-1) :
|
||||
l[k*L+L-1] = 1
|
||||
|
||||
H += manhattan_distance(elem.loc, elem_prev.loc)
|
||||
elem_prev = elem
|
||||
|
||||
"""for i in range(7):
|
||||
print(l[i*7:i*7+7])
|
||||
print("\n")"""
|
||||
|
||||
A_eq = np.vstack((A_eq,l))
|
||||
b_eq.append(1)
|
||||
return A_eq, b_eq, H
|
||||
|
||||
# Computes the path length given path matrix (dist_table) and a result
|
||||
def path_length(P: list, resx: list) :
|
||||
return np.dot(P, resx)
|
||||
|
||||
# Main optimization pipeline
|
||||
def solve_optimization (landmarks, max_steps, printing_details) :
|
||||
|
||||
# SET CONSTRAINTS FOR INEQUALITY
|
||||
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
|
||||
P = A_ub # store the paths for later. Needed to compute path length
|
||||
A_ub, b_ub = respect_number(landmarks, A_ub, b_ub) # Respect max number of visits.
|
||||
|
||||
# TODO : Problems with circular symmetry
|
||||
A_ub, b_ub = break_sym(landmarks, A_ub, b_ub) # break the symmetry. Only use the upper diagonal values
|
||||
|
||||
# SET CONSTRAINTS FOR EQUALITY
|
||||
A_eq, b_eq = init_eq_not_stay(landmarks) # Force solution not to stay in same place
|
||||
A_eq, b_eq, H = 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_order(landmarks, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
|
||||
|
||||
# Bounds for variables (x can only be 0 or 1)
|
||||
x_bounds = [(0, 1)] * len(c)
|
||||
|
||||
# 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 :
|
||||
|
||||
# Override the max_steps using the heuristic
|
||||
for i, val in enumerate(b_ub) :
|
||||
if val == max_steps : b_ub[i] = H
|
||||
|
||||
# Solve problem again :
|
||||
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)
|
||||
|
||||
if not res.success :
|
||||
s = "No solution could be found, even when increasing max_steps using the heuristic"
|
||||
return s
|
||||
#raise ValueError("No solution could be found, even when increasing max_steps using the heuristic")
|
||||
|
||||
# If there is a solution, we're good to go, just check for
|
||||
else :
|
||||
circle = has_circle(res.x)
|
||||
i = 0
|
||||
|
||||
# Break the circular symmetry if needed
|
||||
while len(circle) != 0 :
|
||||
A_ub, b_ub = break_circle(landmarks, A_ub, b_ub, circle)
|
||||
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)
|
||||
circle = has_circle(res.x)
|
||||
i += 1
|
||||
|
||||
if printing_details is True :
|
||||
if i != 0 :
|
||||
print(f"Neded to recompute paths {i} times because of unconnected loops...")
|
||||
X = print_res(res, landmarks, P)
|
||||
return X
|
||||
else :
|
||||
return untangle(res.x)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
11
backend/src/amenities/nature.am
Normal file
11
backend/src/amenities/nature.am
Normal file
@ -0,0 +1,11 @@
|
||||
'leisure'='park'
|
||||
geological
|
||||
'natural'='geyser'
|
||||
'natural'='hot_spring'
|
||||
'natural'='arch'
|
||||
'natural'='volcano'
|
||||
'natural'='stone'
|
||||
'tourism'='alpine_hut'
|
||||
'tourism'='viewpoint'
|
||||
'tourism'='zoo'
|
||||
'waterway'='waterfall'
|
2
backend/src/amenities/shopping.am
Normal file
2
backend/src/amenities/shopping.am
Normal file
@ -0,0 +1,2 @@
|
||||
'shop'='department_store'
|
||||
'shop'='mall'
|
8
backend/src/amenities/sightseeing.am
Normal file
8
backend/src/amenities/sightseeing.am
Normal file
@ -0,0 +1,8 @@
|
||||
'tourism'='museum'
|
||||
'tourism'='attraction'
|
||||
'tourism'='gallery'
|
||||
historic
|
||||
'amenity'='planetarium'
|
||||
'amenity'='place_of_worship'
|
||||
'amenity'='fountain'
|
||||
'water'='reflecting_pool'
|
365
backend/src/landmarks_manager.py
Normal file
365
backend/src/landmarks_manager.py
Normal file
@ -0,0 +1,365 @@
|
||||
import math as m
|
||||
import json, os
|
||||
|
||||
from typing import List, Tuple, Optional
|
||||
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder
|
||||
|
||||
from structs.landmarks import Landmark, LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
|
||||
|
||||
SIGHTSEEING = LandmarkType(landmark_type='sightseeing')
|
||||
NATURE = LandmarkType(landmark_type='nature')
|
||||
SHOPPING = LandmarkType(landmark_type='shopping')
|
||||
|
||||
|
||||
# Include the json here
|
||||
# Create a list of all things to visit given some preferences and a city. Ready for the optimizer
|
||||
def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float]) :
|
||||
|
||||
l_sights, l_nature, l_shop = get_amenities()
|
||||
L = []
|
||||
|
||||
# List for sightseeing
|
||||
if preferences.sightseeing.score != 0 :
|
||||
L1 = get_landmarks(l_sights, SIGHTSEEING, coordinates=coordinates)
|
||||
correct_score(L1, preferences.sightseeing)
|
||||
L += L1
|
||||
|
||||
# List for nature
|
||||
if preferences.nature.score != 0 :
|
||||
L2 = get_landmarks(l_nature, NATURE, coordinates=coordinates)
|
||||
correct_score(L2, preferences.nature)
|
||||
L += L2
|
||||
|
||||
# List for shopping
|
||||
if preferences.shopping.score != 0 :
|
||||
L3 = get_landmarks(l_shop, SHOPPING, coordinates=coordinates)
|
||||
correct_score(L3, preferences.shopping)
|
||||
L += L3
|
||||
|
||||
L = remove_duplicates(L)
|
||||
|
||||
return L, take_most_important(L)
|
||||
|
||||
|
||||
"""def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] :
|
||||
|
||||
l_sights, l_nature, l_shop = get_amenities()
|
||||
L = []
|
||||
|
||||
# List for sightseeing
|
||||
if preferences.sightseeing.score != 0 :
|
||||
L1 = get_landmarks(l_sights, SIGHTSEEING, city_country=city_country, coordinates=coordinates)
|
||||
correct_score(L1, preferences.sightseeing)
|
||||
L += L1
|
||||
|
||||
# List for nature
|
||||
if preferences.nature.score != 0 :
|
||||
L2 = get_landmarks(l_nature, NATURE, city_country=city_country, coordinates=coordinates)
|
||||
correct_score(L2, preferences.nature)
|
||||
L += L2
|
||||
|
||||
# List for shopping
|
||||
if preferences.shopping.score != 0 :
|
||||
L3 = get_landmarks(l_shop, SHOPPING, city_country=city_country, coordinates=coordinates)
|
||||
correct_score(L3, preferences.shopping)
|
||||
L += L3
|
||||
|
||||
return remove_duplicates(L), take_most_important(L)
|
||||
"""
|
||||
# Helper function to gather the amenities list
|
||||
def get_amenities() -> List[List[str]] :
|
||||
|
||||
# Get the list of amenities from the files
|
||||
sightseeing = get_list('/amenities/sightseeing.am')
|
||||
nature = get_list('/amenities/nature.am')
|
||||
shopping = get_list('/amenities/shopping.am')
|
||||
|
||||
return sightseeing, nature, shopping
|
||||
|
||||
|
||||
# Helper function to read a .am file and generate the corresponding list
|
||||
def get_list(path: str) -> List[str] :
|
||||
|
||||
with open(os.path.dirname(os.path.abspath(__file__)) + path) as f :
|
||||
content = f.readlines()
|
||||
|
||||
amenities = []
|
||||
for line in content :
|
||||
amenities.append(line.strip('\n'))
|
||||
|
||||
return amenities
|
||||
|
||||
|
||||
# Take the most important landmarks from the list
|
||||
def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] :
|
||||
|
||||
# Read the parameters from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
N_important = parameters['N important']
|
||||
|
||||
L_copy = []
|
||||
L_clean = []
|
||||
scores = [0]*len(L)
|
||||
names = []
|
||||
name_id = {}
|
||||
|
||||
for i, elem in enumerate(L) :
|
||||
if elem.name not in names :
|
||||
names.append(elem.name)
|
||||
name_id[elem.name] = [i]
|
||||
L_copy.append(elem)
|
||||
else :
|
||||
name_id[elem.name] += [i]
|
||||
scores = []
|
||||
for j in name_id[elem.name] :
|
||||
scores.append(L[j].attractiveness)
|
||||
best_id = max(range(len(scores)), key=scores.__getitem__)
|
||||
t = name_id[elem.name][best_id]
|
||||
if t == i :
|
||||
for old in L_copy :
|
||||
if old.name == elem.name :
|
||||
old.attractiveness = L[t].attractiveness
|
||||
|
||||
scores = [0]*len(L_copy)
|
||||
for i, elem in enumerate(L_copy) :
|
||||
scores[i] = elem.attractiveness
|
||||
|
||||
res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(N_important-N):]
|
||||
|
||||
for i, elem in enumerate(L_copy) :
|
||||
if i in res :
|
||||
L_clean.append(elem)
|
||||
|
||||
return L_clean
|
||||
|
||||
|
||||
# Remove duplicate elements and elements with low score
|
||||
def remove_duplicates(L: List[Landmark]) -> List[Landmark] :
|
||||
"""
|
||||
Removes duplicate landmarks based on their names from the given list.
|
||||
|
||||
Parameters:
|
||||
L (List[Landmark]): A list of Landmark objects.
|
||||
|
||||
Returns:
|
||||
List[Landmark]: A list of unique Landmark objects based on their names.
|
||||
"""
|
||||
|
||||
L_clean = []
|
||||
names = []
|
||||
|
||||
for landmark in L :
|
||||
if landmark.name in names:
|
||||
continue
|
||||
|
||||
|
||||
else :
|
||||
names.append(landmark.name)
|
||||
L_clean.append(landmark)
|
||||
|
||||
return L_clean
|
||||
|
||||
|
||||
# Correct the score of a list of landmarks by taking into account preference settings
|
||||
def correct_score(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(coordinates: Tuple[float, float], radius: int) -> int:
|
||||
|
||||
lat = coordinates[0]
|
||||
lon = coordinates[1]
|
||||
|
||||
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)
|
||||
return radius_result.countElements()
|
||||
|
||||
except :
|
||||
return None
|
||||
|
||||
|
||||
# Creates a bounding box around given coordinates
|
||||
def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[float, float, float, float]:
|
||||
|
||||
lat = coordinates[0]
|
||||
lon = coordinates[1]
|
||||
|
||||
# Half the side length in km (since it's a square bbox)
|
||||
half_side_length_km = side_length / 2.0
|
||||
|
||||
# Convert distance to degrees
|
||||
lat_diff = half_side_length_km / 111 # 1 degree latitude is approximately 111 km
|
||||
lon_diff = half_side_length_km / (111 * m.cos(m.radians(lat))) # Adjust for longitude based on latitude
|
||||
|
||||
# Calculate bbox
|
||||
min_lat = lat - lat_diff
|
||||
max_lat = lat + lat_diff
|
||||
min_lon = lon - lon_diff
|
||||
max_lon = lon + lon_diff
|
||||
|
||||
return min_lat, min_lon, max_lat, max_lon
|
||||
|
||||
|
||||
def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: Tuple[float, float]) -> List[Landmark] :
|
||||
|
||||
# Read the parameters from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
tag_coeff = parameters['tag coeff']
|
||||
park_coeff = parameters['park coeff']
|
||||
church_coeff = parameters['church coeff']
|
||||
radius = parameters['radius close to']
|
||||
bbox_side = parameters['city bbox side']
|
||||
|
||||
# Create bbox around start location
|
||||
bbox = create_bbox(coordinates, bbox_side)
|
||||
|
||||
# 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()
|
||||
|
||||
for elem in result.elements():
|
||||
|
||||
name = elem.tag('name') # Add name
|
||||
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
|
||||
|
||||
# skip if unprecise location
|
||||
if name is None or location[0] is None:
|
||||
continue
|
||||
|
||||
# skip if unused
|
||||
if 'disused:leisure' in elem.tags().keys():
|
||||
continue
|
||||
|
||||
# skip if part of another building
|
||||
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
|
||||
|
||||
# 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_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff)
|
||||
elif amenity == "'leisure'='park'" :
|
||||
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff)
|
||||
else :
|
||||
score = count_elements_within_radius(location, radius) + n_tags*tag_coeff
|
||||
|
||||
if score is not None :
|
||||
# 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=n_tags)
|
||||
L.append(landmark)
|
||||
|
||||
return L
|
||||
|
||||
|
||||
|
||||
"""def get_landmarks(list_amenity: list, landmarktype: LandmarkType, city_country: str = None, coordinates: Tuple[float, float] = None) -> List[Landmark] :
|
||||
|
||||
if city_country is None and coordinates is None :
|
||||
raise ValueError("Either one of 'city_country' and 'coordinates' arguments must be specified")
|
||||
|
||||
if city_country is not None and coordinates is not None :
|
||||
raise ValueError("Cannot specify both 'city_country' and 'coordinates' at the same time, please choose either one")
|
||||
|
||||
# Read the parameters from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
tag_coeff = parameters['tag coeff']
|
||||
park_coeff = parameters['park coeff']
|
||||
church_coeff = parameters['church coeff']
|
||||
radius = parameters['radius close to']
|
||||
bbox_side = parameters['city bbox side']
|
||||
|
||||
# If city_country is specified :
|
||||
if city_country is not None :
|
||||
nominatim = Nominatim()
|
||||
areaId = nominatim.query(city_country).areaId()
|
||||
bbox = None
|
||||
|
||||
# If coordinates are specified :
|
||||
elif coordinates is not None :
|
||||
bbox = create_bbox(coordinates, bbox_side)
|
||||
areaId = None
|
||||
|
||||
else :
|
||||
raise ValueError("Argument number is not corresponding.")
|
||||
|
||||
# Initialize some variables
|
||||
N = 0
|
||||
L = []
|
||||
overpass = Overpass()
|
||||
|
||||
for amenity in list_amenity :
|
||||
query = overpassQueryBuilder(area=areaId, bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
|
||||
result = overpass.query(query)
|
||||
N += result.countElements()
|
||||
|
||||
for elem in result.elements():
|
||||
|
||||
name = elem.tag('name') # Add name
|
||||
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
|
||||
|
||||
# skip if unprecise location
|
||||
if name is None or location[0] is None:
|
||||
continue
|
||||
|
||||
# skip if unused
|
||||
if 'disused:leisure' in elem.tags().keys():
|
||||
continue
|
||||
|
||||
# skip if part of another building
|
||||
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
|
||||
|
||||
# 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_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff)
|
||||
elif amenity == "'leisure'='park'" :
|
||||
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff)
|
||||
else :
|
||||
score = count_elements_within_radius(location, radius) + n_tags*tag_coeff
|
||||
|
||||
if score is not None :
|
||||
# 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=n_tags)
|
||||
L.append(landmark)
|
||||
|
||||
return L
|
||||
"""
|
70
backend/src/main.py
Normal file
70
backend/src/main.py
Normal file
@ -0,0 +1,70 @@
|
||||
from optimizer import solve_optimization
|
||||
from refiner import refine_optimization
|
||||
from landmarks_manager import generate_landmarks
|
||||
from structs.landmarks import Landmark
|
||||
from structs.landmarktype import LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
from fastapi import FastAPI, Query, Body
|
||||
from typing import List
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# Assuming frontend is calling like this :
|
||||
#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}"
|
||||
@app.post("/optimizer_coords/{start_lat}/{start_lon}/{finish_lat}/{finish_lon}")
|
||||
def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(...), finish_lat: float = None, finish_lon: float = None) -> List[Landmark]:
|
||||
|
||||
if preferences is None :
|
||||
raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.")
|
||||
if bool(start_lat) ^ bool(start_lon) :
|
||||
raise ValueError("Please provide both latitude and longitude for the starting point")
|
||||
if bool(finish_lat) ^ bool(finish_lon) :
|
||||
raise ValueError("Please provide both latitude and longitude for the finish point")
|
||||
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start_lat, start_lon), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
if bool(finish_lat) and bool(finish_lon) :
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(finish_lat, finish_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
else :
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(start_lat, start_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.8375946, 2.2949904), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8375946, 2.2949904), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
# Generate the landmarks from the start location
|
||||
landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location)
|
||||
|
||||
# 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 = 4 # 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 refined_tour
|
||||
|
||||
|
||||
|
||||
# input city, country in the form of 'Paris, France'
|
||||
@app.post("/test2/{city_country}")
|
||||
def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landmark]:
|
||||
|
||||
landmarks = generate_landmarks(city_country, preferences)
|
||||
|
||||
max_steps = 9000000
|
||||
|
||||
visiting_order = solve_optimization(landmarks, max_steps, True)
|
||||
|
||||
|
||||
|
||||
|
411
backend/src/optimizer.py
Normal file
411
backend/src/optimizer.py
Normal file
@ -0,0 +1,411 @@
|
||||
import numpy as np
|
||||
import json, os
|
||||
|
||||
from typing import List, Tuple
|
||||
from scipy.optimize import linprog
|
||||
from math import radians, sin, cos, acos
|
||||
from shapely import Polygon
|
||||
|
||||
from structs.landmarks import Landmark
|
||||
|
||||
|
||||
# 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.name != 'start' :
|
||||
print('- ' + elem.name + ', time to reach = ' + str(elem.time_to_reach))
|
||||
dist += elem.time_to_reach
|
||||
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):
|
||||
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
detour = parameters['detour factor']
|
||||
speed = parameters['average walking speed']
|
||||
|
||||
# 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 open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
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 add_time_to_reach(order: List[int], landmarks: List[Landmark])->List[Landmark] :
|
||||
|
||||
# Read the parameters from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
detour_factor = parameters['detour factor']
|
||||
speed = parameters['average walking speed']
|
||||
|
||||
j = 0
|
||||
L = []
|
||||
prev = landmarks[0]
|
||||
|
||||
while(len(L) != len(order)) :
|
||||
|
||||
elem = landmarks[order[j]]
|
||||
if elem != prev :
|
||||
elem.time_to_reach = get_distance(elem.location, prev.location, detour_factor, speed)[1]
|
||||
elem.must_do = True
|
||||
L.append(elem)
|
||||
prev = elem
|
||||
j += 1
|
||||
|
||||
return L
|
||||
|
||||
|
||||
def add_time_to_reach_simple(ordered_visit: List[Landmark])-> List[Landmark] :
|
||||
|
||||
# Read the parameters from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
detour_factor = parameters['detour factor']
|
||||
speed = parameters['average walking speed']
|
||||
|
||||
L = []
|
||||
prev = ordered_visit[0]
|
||||
L.append(prev)
|
||||
total_dist = 0
|
||||
|
||||
for elem in ordered_visit[1:] :
|
||||
elem.time_to_reach = get_distance(elem.location, prev.location, detour_factor, speed)[1]
|
||||
elem.must_do = True
|
||||
L.append(elem)
|
||||
prev = elem
|
||||
total_dist += get_distance(elem.location, prev.location, detour_factor, speed)[1]
|
||||
|
||||
|
||||
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 = add_time_to_reach(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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
8
backend/src/parameters/landmarks_manager.params
Normal file
8
backend/src/parameters/landmarks_manager.params
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"city bbox side" : 10,
|
||||
"radius close to" : 27.5,
|
||||
"church coeff" : 0.6,
|
||||
"park coeff" : 1.5,
|
||||
"tag coeff" : 100,
|
||||
"N important" : 40
|
||||
}
|
5
backend/src/parameters/optimizer.params
Normal file
5
backend/src/parameters/optimizer.params
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"detour factor" : 1.4,
|
||||
"average walking speed" : 4.8,
|
||||
"max landmarks" : 10
|
||||
}
|
293
backend/src/refiner.py
Normal file
293
backend/src/refiner.py
Normal file
@ -0,0 +1,293 @@
|
||||
from collections import defaultdict
|
||||
from heapq import heappop, heappush
|
||||
from itertools import permutations
|
||||
import os, json
|
||||
|
||||
from shapely import buffer, LineString, Point, Polygon, MultiPoint, convex_hull, concave_hull, LinearRing
|
||||
from typing import List, Tuple
|
||||
from math import pi
|
||||
|
||||
from structs.landmarks import Landmark
|
||||
from landmarks_manager import take_most_important
|
||||
from optimizer import solve_optimization, add_time_to_reach_simple, print_res, get_distance
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def create_linestring(landmarks: List[Landmark])->List[Point] :
|
||||
|
||||
points = []
|
||||
|
||||
for landmark in landmarks :
|
||||
points.append(Point(landmark.location))
|
||||
|
||||
return LineString(points)
|
||||
|
||||
|
||||
def is_in_area(area: Polygon, coordinates) -> bool :
|
||||
point = Point(coordinates)
|
||||
return point.within(area)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
def find_shortest_path(landmarks: List[Landmark]) -> List[Landmark]:
|
||||
|
||||
# Read from data
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
detour = parameters['detour factor']
|
||||
speed = parameters['average walking speed']
|
||||
|
||||
# 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 from data
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
detour = parameters['detour factor']
|
||||
speed = parameters['average walking speed']
|
||||
|
||||
# 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_distance(current_landmark.location, lm.location, detour, speed)[1])
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
return take_most_important(second_order_landmarks, len(visited_landmarks))
|
||||
|
||||
|
||||
|
||||
"""def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
|
||||
|
||||
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
|
||||
|
||||
if print_infos : print("There are " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
|
||||
|
||||
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
|
||||
full_set.append(base_tour[-1]) # add finish back
|
||||
|
||||
new_tour = solve_optimization(full_set, max_time, print_infos)
|
||||
|
||||
return new_tour"""
|
||||
|
||||
|
||||
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
|
||||
|
||||
# Read from the file
|
||||
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
|
||||
parameters = json.loads(f.read())
|
||||
max_landmarks = parameters['max landmarks']
|
||||
|
||||
if len(base_tour)-2 >= max_landmarks :
|
||||
return base_tour
|
||||
|
||||
|
||||
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, False)
|
||||
new_tour, new_dist = add_time_to_reach_simple(new_tour)
|
||||
|
||||
"""#if base_tour[0].location == base_tour[-1].location :
|
||||
if False :
|
||||
coords = [] # Coordinates of the new tour
|
||||
coords_dict = {} # maps the location of an element to the element itself. Used to access the elements back once we get the geometry
|
||||
|
||||
# Iterate through the new tour without finish
|
||||
for elem in new_tour[:-1] :
|
||||
coords.append(Point(elem.location))
|
||||
coords_dict[elem.location] = elem # if start = goal, only finish remains
|
||||
|
||||
# Create a concave polygon using the coordinates
|
||||
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]) :
|
||||
better_tour.append(coords_dict[tuple((x,ys[i]))])
|
||||
name_index[coords_dict[tuple((x,ys[i]))].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(new_tour[-1])
|
||||
|
||||
# Rearrange only if polygon
|
||||
better_tour = rearrange(better_tour)
|
||||
|
||||
# Add the time to reach
|
||||
better_tour = add_time_to_reach_simple(better_tour)
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
if not better_poly.is_simple :
|
||||
|
||||
coords_dict = {}
|
||||
better_tour2 = []
|
||||
for elem in better_tour :
|
||||
coords_dict[elem.location] = elem
|
||||
|
||||
better_poly2 = better_poly.buffer(0)
|
||||
new_coords = better_poly2.exterior.coords[:]
|
||||
start_coords = base_tour[0].location
|
||||
start_index = new_coords.
|
||||
|
||||
#for point in new_coords :
|
||||
"""
|
||||
|
||||
|
||||
better_tour, better_poly = find_shortest_path_through_all_landmarks(new_tour)
|
||||
better_tour, better_dist = add_time_to_reach_simple(better_tour)
|
||||
|
||||
if new_dist < better_dist :
|
||||
final_tour = new_tour
|
||||
else :
|
||||
final_tour = better_tour
|
||||
|
||||
if print_infos :
|
||||
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
|
||||
print("\nRefined tour (result of second stage optimization): ")
|
||||
print_res(final_tour, len(full_set))
|
||||
|
||||
|
||||
|
||||
return final_tour
|
20
backend/src/structs/landmarks.py
Normal file
20
backend/src/structs/landmarks.py
Normal file
@ -0,0 +1,20 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .landmarktype import LandmarkType
|
||||
|
||||
|
||||
|
||||
# Output to frontend
|
||||
class Landmark(BaseModel) :
|
||||
name : str
|
||||
type: LandmarkType # De facto mapping depending on how the query was executed with overpass. Should still EXACTLY correspond to the preferences
|
||||
location : tuple
|
||||
osm_type : str
|
||||
osm_id : int
|
||||
attractiveness : int
|
||||
must_do : bool
|
||||
n_tags : int
|
||||
time_to_reach : Optional[int] = 0
|
||||
|
||||
|
4
backend/src/structs/landmarktype.py
Normal file
4
backend/src/structs/landmarktype.py
Normal file
@ -0,0 +1,4 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class LandmarkType(BaseModel):
|
||||
landmark_type: str
|
28
backend/src/structs/preferences.py
Normal file
28
backend/src/structs/preferences.py
Normal file
@ -0,0 +1,28 @@
|
||||
from pydantic import BaseModel
|
||||
from .landmarktype import LandmarkType
|
||||
|
||||
class Preference(BaseModel) :
|
||||
name: str
|
||||
type: LandmarkType # should match the attributes of the Preferences class
|
||||
score: int # score could be from 1 to 5
|
||||
|
||||
# Input for optimization
|
||||
class Preferences(BaseModel) :
|
||||
# Sightseeing / History & Culture (Musées, bâtiments historiques, opéras, églises)
|
||||
sightseeing : Preference
|
||||
|
||||
# Nature (parcs, jardins, rivières, plages)
|
||||
nature: Preference
|
||||
|
||||
# Shopping (diriger plutôt vers des zones / rues commerçantes)
|
||||
shopping : Preference
|
||||
|
||||
""" # Food (price low or high. Combien on veut dépenser pour manger à midi/soir)
|
||||
food_budget : Preference
|
||||
|
||||
# Tolérance au détour (ce qui détermine (+ ou -) le chemin emprunté)
|
||||
detour_tol : Preference"""
|
||||
|
||||
|
||||
|
||||
|
116
backend/src/tester.py
Normal file
116
backend/src/tester.py
Normal file
@ -0,0 +1,116 @@
|
||||
import pandas as pd
|
||||
|
||||
from typing import List
|
||||
from landmarks_manager import generate_landmarks
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from 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 test3(city_country: str) -> List[Landmark]:
|
||||
|
||||
|
||||
preferences = Preferences(
|
||||
sightseeing=Preference(
|
||||
name='sightseeing',
|
||||
type=LandmarkType(landmark_type='sightseeing'),
|
||||
score = 5),
|
||||
nature=Preference(
|
||||
name='nature',
|
||||
type=LandmarkType(landmark_type='nature'),
|
||||
score = 0),
|
||||
shopping=Preference(
|
||||
name='shopping',
|
||||
type=LandmarkType(landmark_type='shopping'),
|
||||
score = 5))
|
||||
|
||||
coordinates = None
|
||||
|
||||
landmarks, landmarks_short = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=coordinates)
|
||||
|
||||
#write_data(landmarks)
|
||||
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.2044576, 16.3870242), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.2044576, 16.3870242), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
|
||||
test = landmarks_short
|
||||
|
||||
test.insert(0, start)
|
||||
test.append(finish)
|
||||
|
||||
max_walking_time = 2 # hours
|
||||
|
||||
visiting_list = solve_optimization(test, max_walking_time*60, True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
|
||||
|
||||
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)
|
||||
#finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8777055, 2.3640967), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
#start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.847132, 2.312359), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
#finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.843185, 2.344533), 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)
|
||||
|
||||
# Generate the landmarks from the start location
|
||||
landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location)
|
||||
#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 = 2 # 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 refined_tour
|
||||
|
||||
|
||||
#test4(tuple((48.8344400, 2.3220540))) # Café Chez César
|
||||
#test4(tuple((48.8375946, 2.2949904))) # Point random
|
||||
test4(tuple((47.377859, 8.540585))) # Zurich HB
|
||||
#test3('Vienna, Austria')
|
9730
landmarks.txt
Normal file
9730
landmarks.txt
Normal file
File diff suppressed because it is too large
Load Diff
482
minor_landmarks.txt
Normal file
482
minor_landmarks.txt
Normal file
@ -0,0 +1,482 @@
|
||||
{
|
||||
"0":{
|
||||
"name":"Atelier Brancusi",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8614029,
|
||||
2.3519903
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":55503399,
|
||||
"attractiveness":13,
|
||||
"must_do":false,
|
||||
"n_tags":13,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"1":{
|
||||
"name":"Musée de Cluny",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8506604,
|
||||
2.3437398
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":56640163,
|
||||
"attractiveness":18,
|
||||
"must_do":false,
|
||||
"n_tags":18,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"2":{
|
||||
"name":"Musée du Luxembourg",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8485965,
|
||||
2.3340156
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":170226810,
|
||||
"attractiveness":15,
|
||||
"must_do":false,
|
||||
"n_tags":15,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"3":{
|
||||
"name":"Musée national Eugène Delacroix",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8546662,
|
||||
2.3353836
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":395785603,
|
||||
"attractiveness":15,
|
||||
"must_do":false,
|
||||
"n_tags":15,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"4":{
|
||||
"name":"Hôtel de Sully",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.854635,
|
||||
2.3638126
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":403146,
|
||||
"attractiveness":14,
|
||||
"must_do":false,
|
||||
"n_tags":13,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"5":{
|
||||
"name":"Hôtel de la Monnaie",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.856403,
|
||||
2.3388625
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":967664,
|
||||
"attractiveness":11,
|
||||
"must_do":false,
|
||||
"n_tags":11,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"6":{
|
||||
"name":"Musée Bourdelle",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8432078,
|
||||
2.3186583
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":1212876,
|
||||
"attractiveness":23,
|
||||
"must_do":false,
|
||||
"n_tags":23,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"7":{
|
||||
"name":"Musée Carnavalet",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8576819,
|
||||
2.3627147
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":2405955,
|
||||
"attractiveness":25,
|
||||
"must_do":false,
|
||||
"n_tags":25,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"8":{
|
||||
"name":"Pont Neuf",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8565248,
|
||||
2.3408132
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":53574149,
|
||||
"attractiveness":16,
|
||||
"must_do":false,
|
||||
"n_tags":15,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"9":{
|
||||
"name":"Jardin du Luxembourg",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8467137,
|
||||
2.3363649
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":128206209,
|
||||
"attractiveness":35,
|
||||
"must_do":false,
|
||||
"n_tags":23,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"10":{
|
||||
"name":"Cathédrale Notre-Dame de Paris",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8529372,
|
||||
2.3498701
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":201611261,
|
||||
"attractiveness":55,
|
||||
"must_do":false,
|
||||
"n_tags":54,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"11":{
|
||||
"name":"Maison à l'enseigne du Faucheur",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8558553,
|
||||
2.3568266
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":1123456865,
|
||||
"attractiveness":13,
|
||||
"must_do":false,
|
||||
"n_tags":11,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"12":{
|
||||
"name":"Maison à l'enseigne du Mouton",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8558431,
|
||||
2.3568914
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":1123456866,
|
||||
"attractiveness":13,
|
||||
"must_do":false,
|
||||
"n_tags":11,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"13":{
|
||||
"name":"Palais de Justice de Paris",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8556537,
|
||||
2.3446072
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":536982,
|
||||
"attractiveness":24,
|
||||
"must_do":false,
|
||||
"n_tags":24,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"14":{
|
||||
"name":"Mémorial des Martyrs de la Déportation",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8517365,
|
||||
2.3524734
|
||||
],
|
||||
"osm_type":"relation",
|
||||
"osm_id":9396191,
|
||||
"attractiveness":21,
|
||||
"must_do":false,
|
||||
"n_tags":21,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"15":{
|
||||
"name":"Fontaine des Innocents",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8606368,
|
||||
2.3480233
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":52469222,
|
||||
"attractiveness":16,
|
||||
"must_do":false,
|
||||
"n_tags":15,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"16":{
|
||||
"name":"Hôtel de Sens",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8535257,
|
||||
2.3588733
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":55541122,
|
||||
"attractiveness":21,
|
||||
"must_do":false,
|
||||
"n_tags":20,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"17":{
|
||||
"name":"Hôtel d'Ourscamp",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8555465,
|
||||
2.3571206
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":55620201,
|
||||
"attractiveness":10,
|
||||
"must_do":false,
|
||||
"n_tags":9,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"18":{
|
||||
"name":"Hôtel de Choiseul-Praslin",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8478008,
|
||||
2.3203873
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":65756922,
|
||||
"attractiveness":12,
|
||||
"must_do":false,
|
||||
"n_tags":12,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"19":{
|
||||
"name":"Chapelle Notre-Dame-des-Anges",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8462836,
|
||||
2.3235558
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":219378497,
|
||||
"attractiveness":16,
|
||||
"must_do":false,
|
||||
"n_tags":16,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"20":{
|
||||
"name":"Fontaine du Palmier",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8575005,
|
||||
2.3472864
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":261092850,
|
||||
"attractiveness":23,
|
||||
"must_do":false,
|
||||
"n_tags":14,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"21":{
|
||||
"name":"Église Saint-Séverin",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8520913,
|
||||
2.3457237
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":19740659,
|
||||
"attractiveness":15,
|
||||
"must_do":false,
|
||||
"n_tags":25,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"22":{
|
||||
"name":"Église Orthodoxe Roumaine des Saints Archanges",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.849488,
|
||||
2.3471975
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":55366403,
|
||||
"attractiveness":10,
|
||||
"must_do":false,
|
||||
"n_tags":16,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"23":{
|
||||
"name":"Église Saint-Merri",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8590777,
|
||||
2.3508448
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":55742120,
|
||||
"attractiveness":16,
|
||||
"must_do":false,
|
||||
"n_tags":26,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"24":{
|
||||
"name":"Église Saint-Germain des Prés",
|
||||
"type":{
|
||||
"landmark_type":"sightseeing"
|
||||
},
|
||||
"location":[
|
||||
48.8539667,
|
||||
2.334463
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":62287173,
|
||||
"attractiveness":12,
|
||||
"must_do":false,
|
||||
"n_tags":21,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"25":{
|
||||
"name":"Square de la Tour Saint-Jacques",
|
||||
"type":{
|
||||
"landmark_type":"nature"
|
||||
},
|
||||
"location":[
|
||||
48.857913,
|
||||
2.3487608
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":15895172,
|
||||
"attractiveness":19,
|
||||
"must_do":false,
|
||||
"n_tags":12,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"26":{
|
||||
"name":"Square Jean XXIII",
|
||||
"type":{
|
||||
"landmark_type":"nature"
|
||||
},
|
||||
"location":[
|
||||
48.8523775,
|
||||
2.3503406
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":20444455,
|
||||
"attractiveness":23,
|
||||
"must_do":false,
|
||||
"n_tags":15,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"27":{
|
||||
"name":"Square Félix Desruelles",
|
||||
"type":{
|
||||
"landmark_type":"nature"
|
||||
},
|
||||
"location":[
|
||||
48.8536974,
|
||||
2.3345069
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":62287123,
|
||||
"attractiveness":12,
|
||||
"must_do":false,
|
||||
"n_tags":7,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"28":{
|
||||
"name":"Jardin des Rosiers – Joseph-Migneret",
|
||||
"type":{
|
||||
"landmark_type":"nature"
|
||||
},
|
||||
"location":[
|
||||
48.8572946,
|
||||
2.3602516
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":365878540,
|
||||
"attractiveness":14,
|
||||
"must_do":false,
|
||||
"n_tags":8,
|
||||
"time_to_reach":0
|
||||
},
|
||||
"29":{
|
||||
"name":"Jardin du Père-Armand-David",
|
||||
"type":{
|
||||
"landmark_type":"nature"
|
||||
},
|
||||
"location":[
|
||||
48.8478674,
|
||||
2.3220972
|
||||
],
|
||||
"osm_type":"way",
|
||||
"osm_id":588324415,
|
||||
"attractiveness":10,
|
||||
"must_do":false,
|
||||
"n_tags":7,
|
||||
"time_to_reach":0
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user