diff --git a/.github/workflows/buildbase.yaml b/.github/workflows/buildbase.yaml new file mode 100644 index 00000000000000..6c41299d10b7f0 --- /dev/null +++ b/.github/workflows/buildbase.yaml @@ -0,0 +1,31 @@ +name: buildbase +on: + workflow_dispatch: + +env: + BASE_IMAGE: openpilot-base + DOCKER_REGISTRY: ghcr.io/move-fast + + DOCKER_LOGIN: docker login ghcr.io -u ${GITHUB_ACTOR} -p ${{ secrets.GITHUB_TOKEN }} + BUILD: | + docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true + docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true + docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + +jobs: + build_base: + name: build base + runs-on: ubuntu-20.04 + timeout-minutes: 60 + if: github.repository == 'move-fast/openpilot' + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Build Docker image + run: | + eval "$BUILD" + - name: Push to container registry + run: | + $DOCKER_LOGIN + docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 73784fdfec2e58..6dd96c20a43bd7 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -1,15 +1,16 @@ name: selfdrive on: push: - branches-ignore: - - 'testing-closet*' + branches: + - 'release_**' + - 'develop' pull_request: env: BASE_IMAGE: openpilot-base - DOCKER_REGISTRY: ghcr.io/commaai + DOCKER_REGISTRY: ghcr.io/move-fast - DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} + DOCKER_LOGIN: docker login ghcr.io -u ${GITHUB_ACTOR} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true @@ -24,6 +25,7 @@ jobs: name: build release runs-on: ubuntu-20.04 timeout-minutes: 50 + if: github.repository == 'commaai/openpilot' env: STRIPPED_DIR: tmppilot steps: @@ -115,6 +117,7 @@ jobs: name: build webcam runs-on: ubuntu-20.04 timeout-minutes: 90 + if: github.repository == 'commaai/openpilot' env: IMAGE_NAME: openpilotwebcamci steps: @@ -212,6 +215,7 @@ jobs: $UNIT_TEST selfdrive/locationd && \ $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ + $UNIT_TEST selfdrive/mapd && \ $UNIT_TEST tools/lib/tests && \ ./selfdrive/common/tests/test_util && \ ./selfdrive/loggerd/tests/test_logger &&\ @@ -224,6 +228,7 @@ jobs: name: process replay runs-on: ubuntu-20.04 timeout-minutes: 50 + if: github.repository == 'commaai/openpilot' # Do not run on move-fast repo. steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index b6b45bb24e2df4..e50f80ff2e0047 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -1,12 +1,15 @@ name: tools on: push: + branches: + - 'release_**' + - 'develop' pull_request: env: BASE_IMAGE: openpilot-base - DOCKER_REGISTRY: ghcr.io/commaai - DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} + DOCKER_REGISTRY: ghcr.io/move-fast + DOCKER_LOGIN: docker login ghcr.io -u ${GITHUB_ACTOR} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true diff --git a/.gitmodules b/.gitmodules index 1e6110b7a6941e..d0a6c7e1cbdb7d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = ../../commaai/laika.git [submodule "cereal"] path = cereal - url = ../../commaai/cereal.git + url = ../../move-fast/cereal.git [submodule "rednose_repo"] path = rednose_repo url = ../../commaai/rednose.git diff --git a/.python-version b/.python-version deleted file mode 100644 index 0cbfaed0d9fe75..00000000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.8.5 diff --git a/Pipfile b/Pipfile index ae0765b1c8784e..4ebfa910498131 100644 --- a/Pipfile +++ b/Pipfile @@ -117,6 +117,7 @@ onnx = "*" onnxruntime = "*" timezonefinder = "*" sentry-sdk = "*" +overpy = "*" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 90e2343ce189cf..f406ce3e730777 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "83811b11c46064c2b45869bab831f3b0dcac5d35df84407d0f1bdd8f6ff7463a" + "sha256": "5efcda7eb0bcf9d05e18db31f500b7850f9f2d922e7cb65916737268befa426d" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "astroid": { "hashes": [ - "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", - "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" + "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c", + "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e" ], "markers": "python_version ~= '3.6'", - "version": "==2.5.6" + "version": "==2.7.3" }, "atomicwrites": { "hashes": [ @@ -41,66 +41,62 @@ }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", - "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", - "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", - "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" - ], - "index": "pypi", - "version": "==1.14.5" - }, - "chardet": { - "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "index": "pypi", + "version": "==1.14.6" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.0.0" + "markers": "python_version >= '3'", + "version": "==2.0.4" }, "click": { "hashes": [ @@ -122,21 +118,26 @@ }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" - ], - "index": "pypi", - "version": "==3.4.7" + "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e", + "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b", + "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", + "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", + "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", + "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", + "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", + "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", + "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", + "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", + "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb", + "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14", + "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af", + "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e", + "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5", + "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06", + "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7" + ], + "index": "pypi", + "version": "==3.4.8" }, "cysignals": { "hashes": [ @@ -147,44 +148,48 @@ }, "cython": { "hashes": [ - "sha256:0c4b9f7e3aa004cf3f364e3e772f55fec5740485bafea99d1f13bdc9bbd8a545", - "sha256:20402ef316393168909926ab21848aa6e08e39bed5003b657139774e66166cd0", - "sha256:20cb50d9fede8029bdb50875458f07a27f909289aeed4cdb9c19544dd9a9bc45", - "sha256:2365f3b5e6451b6bc6dcd262230656f4ade1d862ec2f6c22154deebef37c08b6", - "sha256:266459c7e48fe3c6c492b297e4033e42d4c6863cc1a1ff7cc4034949fc574fa6", - "sha256:282263628c5d601b313d5920f7b6d7e08c7fedbddacd080c4858aa04d86b6b4b", - "sha256:2a3bbce689a2fddb85aa66712d93875c99bf7f64ac82b1d149ecce522a7a4e0c", - "sha256:2af52d312e96b38ded38b34d06e22685c226b1b0e58278bd27209f5d2385d115", - "sha256:355a6e768d91e21fbf477b61881bab64b7a2da386a166898997bccefd532cf5d", - "sha256:37ff66039e3d138ec968ee1d1e12441fa5fb4e6a9c5458bc3c3a232f01be4a7d", - "sha256:3b29224eb62309a10819d923dc6262f769e4f3facfee3cd06372c355e5b38b33", - "sha256:3ef530f975e3a760e7282fce2a25f900fa63f96d17321b4aa5f5542eb9859cdf", - "sha256:41cd0dd2ff5d78466e73409db509887a84449b400074d4f217980cedbb18e4be", - "sha256:474c1a29ab43e29d990df279e2cf6aa96baa9208f5cd4bc76ac87ffcdf1e2945", - "sha256:4858043ac5f96a8f0277cf63760bb39b9521c1f897678cf1d22423f3e758f4ed", - "sha256:4b0bcf2e06a9063fc78c3243ed4003228375d532ef13b9e5d7183be8f0a52cf5", - "sha256:4b6824b58d4373224fc76ee8bee6b35c2d17c91a1ed0fa67b88440f63daebe50", - "sha256:4d7c3b0882d8757c601eaf288fc0d321d5c7ac6c3afb8c42eddf9325a3419cf5", - "sha256:519fccf526d26b377e1db22f22aa44889b28bc5833ec106588cb13557e8ba2da", - "sha256:58dc06871bfdb0592542d779714fe9f918e11ba20ac07757dd63b198bdc704fe", - "sha256:5a6792153b728a0240e55bbb5b643f4f7e45c76319e03abf15bf367471ea1d1a", - "sha256:5be3ae3189cf7d0e9bbeafb854496dc7030c6f6a5602d809435fab8223543a41", - "sha256:625a16103770fd92b487b701fb0c07e5790b080f40fa11ce572a2d56d9e9fcca", - "sha256:6a0d31452f0245daacb14c979c77e093eb1a546c760816b5eed0047686baad8e", - "sha256:794e3df0b57e16bce7583ac909126f4cb381fe566adadb20484d89095855eedb", - "sha256:7b7a766726d207d7cd57aff0fcb4b35ce042d3cc88a421fcdb45eeb61a5b9d12", - "sha256:7d6a33c8a11f05f698e215bfdb837f32c27f63c20f3af863557ed91c748dc2be", - "sha256:a8eed9c82e8fe07b8a8ffbd36018871a17458903fc25c9d015f37b54513a3efd", - "sha256:aa3bb0928fb2aa3a8828801eb8b29af2261c199f805ae835467489e2bdd00372", - "sha256:b0699f0dc90181f2458fdb8170455e7798a309e18f41379eda7a2dc8c7aadee0", - "sha256:c4b82461edbbcf90f19b319006345b77474a2d7514e1476d49a14bbd55d6b797", - "sha256:ceccc03b633113ede1f14ad914a6db5c278ce108c8ddb308a5c01c1567d8a02a", - "sha256:ef21c51350462160456eb71df31b0869e5141e940f22c61c358bdb6e3ebc3388", - "sha256:f4aca6bffb1c1c3c4ada3347d0b162a699c18a66e097ee08b63b3a35118fdfcc", - "sha256:ff885f18d169759b57f116d3956e45cd2b9cba989fde348bba091544c668dc11" - ], - "index": "pypi", - "version": "==0.29.23" + "sha256:09ac3087ac7a3d489ebcb3fb8402e00c13d1a3a1c6bc73fd3b0d756a3e341e79", + "sha256:0a142c6b862e6ed6b02209d543062c038c110585b5e32d1ad7c9717af4f07e41", + "sha256:0d414458cb22f8a90d64260da6dace5d5fcebde43f31be52ca51f818c46db8cb", + "sha256:10cb3def9774fa99e4583617a5616874aed3255dc241fd1f4a3c2978c78e1c53", + "sha256:112efa54a58293a4fb0acf0dd8e5b3736e95b595eee24dd88615648e445abe41", + "sha256:166f9f29cd0058ce1a14a7b3a2458b849ed34b1ec5fd4108af3fdd2c24afcbb0", + "sha256:2d9e61ed1056a3b6a4b9156b62297ad18b357a7948e57a2f49b061217696567e", + "sha256:2f41ef7edd76dd23315925e003f0c58c8585f3ab24be6885c4b3f60e77c82746", + "sha256:37bcfa5df2a3009f49624695d917c3804fccbdfcdc5eda6378754a879711a4d5", + "sha256:416046a98255eff97ec02077d20ebeaae52682dfca1c35aadf31260442b92514", + "sha256:4cf4452f0e4d50e11701bca38f3857fe6fa16593e7fd6a4d5f7be66f611b7da2", + "sha256:55b0ee28c2c8118bfb3ad9b25cf7a6cbd724e442ea96956e32ccd908d5e3e043", + "sha256:5dd56d0be50073f0e54825a8bc3393852de0eed126339ecbca0ae149dba55cfc", + "sha256:5fa12ebafc2f688ea6d26ab6d1d2e634a9872509ba7135b902bb0d8b368fb04b", + "sha256:5fb977945a2111f6b64501fdf7ed0ec162cc502b84457fd648d6a558ea8de0d6", + "sha256:60c958bcab0ff315b4036a949bed1c65334e1f6a69e17e9966d742febb59043a", + "sha256:661dbdea519d9cfb288867252b75fef73ffa8e8bb674cec27acf70646afb369b", + "sha256:6a2cf2ccccc25413864928dfd730c29db6f63eaf98206c1e600003a445ca7f58", + "sha256:6ade74eece909fd3a437d9a5084829180751d7ade118e281e9824dd75eafaff2", + "sha256:73ac33a4379056a02031baa4def255717fadb9181b5ac2b244792d53eae1c925", + "sha256:76cbca0188d278e93d12ebdaf5990678e6e436485fdfad49dbe9b07717d41a3c", + "sha256:774cb8fd931ee1ba52c472bc1c19077cd6895c1b24014ae07bb27df59aed5ebe", + "sha256:821c2d416ad7d006b069657ee1034c0e0cb45bdbe9ab6ab631e8c495dfcfa4ac", + "sha256:84826ec1c11cda56261a252ddecac0c7d6b02e47e81b94f40b27b4c23c29c17c", + "sha256:854fe2193d3ad4c8b61932ff54d6dbe10c5fa8749eb8958d72cc0ab28243f833", + "sha256:88dc3c250dec280b0489a83950b15809762e27232f4799b1b8d0bad503f5ab84", + "sha256:8cb87777e82d1996aef6c146560a19270684271c9c669ba62ac6803b3cd2ff82", + "sha256:91339ee4b465924a3ea4b2a9cec7f7227bc4cadf673ce859d24c2b9ef60b1214", + "sha256:9164aeef1af6f837e4fc20402a31d256188ba4d535e262c6cb78caf57ad744f8", + "sha256:a102cfa795c6b3b81a29bdb9dbec545367cd7f353c03e6f30a056fdfefd92854", + "sha256:ad43e684ade673565f6f9d6638015112f6c7f11aa2a632167b79014f613f0f5f", + "sha256:afb521523cb46ddaa8d269b421f88ea2731fee05e65b952b96d4db760f5a2a1c", + "sha256:b28f92e617f540d3f21f8fd479a9c6491be920ffff672a4c61b7fc4d7f749f39", + "sha256:bc05de569f811be1fcfde6756c9048ae518f0c4b6d9f8f024752c5365d934cac", + "sha256:cdf04d07c3600860e8c2ebaad4e8f52ac3feb212453c1764a49ac08c827e8443", + "sha256:d8d1a087f35e39384303f5e6b75d465d6f29d746d7138eae9d3b6e8e6f769eae", + "sha256:eb2843f8cc01c645725e6fc690a84e99cdb266ce8ebe427cf3a680ff09f876aa", + "sha256:f2e9381497b12e8f622af620bde0d1d094035d79b899abb2ddd3a7891f535083", + "sha256:f96411f0120b5cae483923aaacd2872af8709be4b46522daedc32f051d778385" + ], + "index": "pypi", + "version": "==0.29.24" }, "flake8": { "hashes": [ @@ -226,19 +231,19 @@ }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "isort": { "hashes": [ - "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", - "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" + "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899", + "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2" ], - "markers": "python_version < '4' and python_full_version >= '3.6.1'", - "version": "==5.9.1" + "markers": "python_full_version >= '3.6.1' and python_version < '4.0'", + "version": "==5.9.3" }, "itsdangerous": { "hashes": [ @@ -294,16 +299,16 @@ }, "libusb1": { "hashes": [ - "sha256:27aec6aa1ff9ca845d0035023f3cf39710afac56903c51cd96a95404d064189e", - "sha256:2dff68819350bf8a8c157c7fa40d3efc741cb57868687d1714c8125ee99e8ac8", - "sha256:8ee4a963d4ecc20d9f4543b9151729c9cc9a229c2f9119e12bff762e84d8859f", - "sha256:a323588902fbd3693f8fddd7eac016700b24116c31b00756b9f52cf06c2a6629", - "sha256:b4f25a2d66f62ec740edba3597038a7e9cd45b43456acfdb7a2bca8c2ad4aa30", - "sha256:c19d49136ef262474dbbac8bd40a2c4b65660220571de8564efec631c56bdc09", - "sha256:c3dd4df43b5c38f65bf599413810d021f5f98396c4b6f66765fb98193aca11b0" + "sha256:60e6ce37be064f6e51d02b25da44230ecc9c0b1fdb6f14568c71457d963c1749", + "sha256:8465e042c95b0a3fb8ea11472d2b3a73987af28780a802637ff70c4e6de46335", + "sha256:86792164d5b5b96bdd4f186b2502f70e19c09b2086fa9b39711c3b16703948a9", + "sha256:891d331836d4a97f06c484c44c2e6ecafc1402551f395aa753d436c6aa6304ad", + "sha256:98376b9e40df25dfc2dcdec1b056f28c2b28ca6caf46e93d9bebeea29a949c0e", + "sha256:a3227b010d8b6322d47a66c438a562cbb02793db0ecd68f35cb3bd2d575f2a6e", + "sha256:f40a09e37b656c9bb48b1c83ef74ca0c8d7e5b0888168ea54733832f6eac1ec4" ], "index": "pypi", - "version": "==1.9.2" + "version": "==1.9.3" }, "markupsafe": { "hashes": [ @@ -312,30 +317,50 @@ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", + "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", + "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", + "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", + "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", + "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -370,126 +395,152 @@ }, "numpy": { "hashes": [ - "sha256:1a784e8ff7ea2a32e393cc53eb0003eca1597c7ca628227e34ce34eb11645a0e", - "sha256:2ba579dde0563f47021dcd652253103d6fd66165b18011dce1a0609215b2791e", - "sha256:3537b967b350ad17633b35c2f4b1a1bbd258c018910b518c30b48c8e41272717", - "sha256:3c40e6b860220ed862e8097b8f81c9af6d7405b723f4a7af24a267b46f90e461", - "sha256:598fe100b2948465cf3ed64b1a326424b5e4be2670552066e17dfaa67246011d", - "sha256:620732f42259eb2c4642761bd324462a01cdd13dd111740ce3d344992dd8492f", - "sha256:709884863def34d72b183d074d8ba5cfe042bc3ff8898f1ffad0209161caaa99", - "sha256:75579acbadbf74e3afd1153da6177f846212ea2a0cc77de53523ae02c9256513", - "sha256:7c55407f739f0bfcec67d0df49103f9333edc870061358ac8a8c9e37ea02fcd2", - "sha256:a1f2fb2da242568af0271455b89aee0f71e4e032086ee2b4c5098945d0e11cf6", - "sha256:a290989cd671cd0605e9c91a70e6df660f73ae87484218e8285c6522d29f6e38", - "sha256:ac4fd578322842dbda8d968e3962e9f22e862b6ec6e3378e7415625915e2da4d", - "sha256:ad09f55cc95ed8d80d8ab2052f78cc21cb231764de73e229140d81ff49d8145e", - "sha256:b9205711e5440954f861ceeea8f1b415d7dd15214add2e878b4d1cf2bcb1a914", - "sha256:bba474a87496d96e61461f7306fba2ebba127bed7836212c360f144d1e72ac54", - "sha256:bebab3eaf0641bba26039fb0b2c5bf9b99407924b53b1ea86e03c32c64ef5aef", - "sha256:cc367c86eb87e5b7c9592935620f22d13b090c609f1b27e49600cd033b529f54", - "sha256:ccc6c650f8700ce1e3a77668bb7c43e45c20ac06ae00d22bdf6760b38958c883", - "sha256:cf680682ad0a3bef56dae200dbcbac2d57294a73e5b0f9864955e7dd7c2c2491", - "sha256:d2910d0a075caed95de1a605df00ee03b599de5419d0b95d55342e9a33ad1fb3", - "sha256:d5caa946a9f55511e76446e170bdad1d12d6b54e17a2afe7b189112ed4412bb8", - "sha256:d89b0dc7f005090e32bb4f9bf796e1dcca6b52243caf1803fdd2b748d8561f63", - "sha256:d95d16204cd51ff1a1c8d5f9958ce90ae190be81d348b514f9be39f878b8044a", - "sha256:e4d5a86a5257843a18fb1220c5f1c199532bc5d24e849ed4b0289fb59fbd4d8f", - "sha256:e58ddb53a7b4959932f5582ac455ff90dcb05fac3f8dcc8079498d43afbbde6c", - "sha256:e80fe25cba41c124d04c662f33f6364909b985f2eb5998aaa5ae4b9587242cce", - "sha256:eda2829af498946c59d8585a9fd74da3f810866e05f8df03a86f70079c7531dd", - "sha256:fd0a359c1c17f00cb37de2969984a74320970e0ceef4808c32e00773b06649d9" - ], - "index": "pypi", - "version": "==1.21.0" + "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf", + "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2", + "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6", + "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad", + "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc", + "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254", + "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0", + "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218", + "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13", + "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef", + "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737", + "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723", + "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3", + "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1", + "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d", + "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52", + "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3", + "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5", + "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f", + "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496", + "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f", + "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724", + "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931", + "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f", + "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7", + "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38", + "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557", + "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57", + "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310", + "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419" + ], + "index": "pypi", + "version": "==1.21.2" }, "onnx": { "hashes": [ - "sha256:0377f223ad45944fd51992384af2ca464e5c5f22b3caa76abc24d8cf17d4f2b8", - "sha256:0c3082f038642479076dd4887055007049cf18b1f5752c4a5839da056982ec79", - "sha256:154301728c91aa475ad8d8eb901216eef2a48ef9ed414ee13bfe4b82a38326c6", - "sha256:1dc523a34ca9746c5d43a50efd4f71e04f36d548dcf05ef4bd7ca91f834bb3ad", - "sha256:2380520f71bd76be02f0e4e65a58dba0b59e2229a30a54fa80d6407bb3775e5b", - "sha256:34062d0fde8c49fba96c8c0c02a087533f8e943c2459f2893c540ddc6a11aaf6", - "sha256:348c7968cfd03916d703d52c6f63a946a33197ad2195142be1a6789162bcb614", - "sha256:43f63a78aff55ee732a7cb4d333b9db85601e14418d041c17671059f9596e5e1", - "sha256:5e70148cd4d9ed97eeef3690ec0a78ebfed585184f3e286a6f7d2526c3a7bd34", - "sha256:60fea75e9147dec520f2231dd3549ac0d429f976c2b0c429e32c98ae7e548a11", - "sha256:685b670336ab23bc03c6112d164cfcc58bd4e27e7aa2bd73c917e1a3b973f609", - "sha256:6f4b1e37c1ff5f3e16480a458d07d26c6c798deedd20fa258627505ec8646d26", - "sha256:71576e5088bd0e93f749a8a849b971b338f4f69a4d4895d0424730d1dac76a54", - "sha256:7bd6ad02f63ce4226a1ea30a4aa80e6ac5c555ad52e0dd61a80b0a39052f1994", - "sha256:9e8026d49340eb7b5eb8bee25028a4a4b6c4b1c55f9fee72ad3ab964f1dc62d6", - "sha256:a5fe019aa33098727d04f4598ebe6edca26a1008092761225acd732b2f62354b", - "sha256:a9055a654c4dd87b45ce59f1e08371686937cad554471eadf6826df1bb125b7a", - "sha256:abd9ab9b7c90f6885d417b979a4c94d6046ad7ce8d058fd0f469d5c50d5c1908", - "sha256:aef24defef248625aeb37c24f1b014b5f6fc2acdda0c4f52f6c9bdad02a17ccb", - "sha256:b5ba35be377c19792d0b710e7f50f81c5d99971718414f6f95648c80d49e5449", - "sha256:c74bb7068653ac7ecac3aa674d40652d65bce47090e8d0eb6a988f40dd5f0f78" - ], - "index": "pypi", - "version": "==1.9.0" + "sha256:0170ff8e6fba268d2feae7a934e474988c2157bc1dbe399ac9974be0a53a08f7", + "sha256:0176c94e3c4c9ef999ddd8b1d6667ecd7128f334071af532dd9d7c0780da49b8", + "sha256:11597a1ef11381aee22eae0dfd4f06c7c396b19a833f635fc99f634f843ac0a1", + "sha256:1bd15b90dff6b51a3da81ce127a2a049b2b9f038a7b3e002c877a32cfb6fb0fa", + "sha256:26c9b55d5fa17153908b7af599a23ed928d9b6158cf233fd7809de216f4e54e6", + "sha256:45ffeef54c7f52193b52abccc9e53a8692913a6d240cc8d8b669306a6964a406", + "sha256:4cbb1a648f8964f2acaee1f519c3d0cefa2b5a8dd8777b38cc4adda085982263", + "sha256:606e792bb2602ae5af5f44c0ef604d232f1b1a7cfde68d059488cc088e9793c3", + "sha256:6b09b8464004240f3d48f6f1a6709e2a09f635dc3a88fd0d94d4d5d7dce9c09c", + "sha256:6f4489d0a004ccf8a39ce9271d6fde7e0a187fc3f94927b65998e8007d41b938", + "sha256:7b5a2ae95efe3eee65d908287227398435926b5753fe97d49749060ed1838262", + "sha256:7fc911b35abb613aca642678ff85f6855c7bb1e39dd07de8761d0867e2e3c677", + "sha256:837fd82c67b609d1bc54b478de8489eb17fe0775f5d0772a1153de8478c59e24", + "sha256:8deec423c16a335aee3470d32204d77f43639a46c8b4ae92362bdab73a6666ce", + "sha256:9553dbcc80b9a94a7c44779d52341de93623891daf7d4196f2089c17523e56bf", + "sha256:9a3dfa2ea2c6096a6495ca754e9b81032cfbf2c59bd8b92e36e81611fe04675b", + "sha256:9d941ba76cab55db8913ecad9dc50cefeb368460f6338a91783a5d7643f3a044", + "sha256:aa81aaf4b879ba9f3ea0f30f4bf29c36f828c504b7faeb47f23adc0038d0a226", + "sha256:aaf3b2e9303b8f198e68b1c86da81407fa6a910b7342b8e720d96b11cf83cb6e", + "sha256:b57f3a18cb53147b5659edaed92ef566ca5dce23412dbefb0c8a3097f61b7015", + "sha256:baca16d0caa8274facda67dd8daa6f1af47b5770a926b24450149fa5abce3908", + "sha256:d768204d157ea21c19b1377094ae5ff6837223e805e375274d6a91a11ce371db", + "sha256:d882b2db5dc23af2b2850370429b02a213c112f731b48a2b15e73498406c5535", + "sha256:f4dc742bfc786291126069aa7ee9d0495cd3e5871ee82e3c7d25c8b1cc9104aa", + "sha256:ff5ddd411599bf536954b6f069f95379c6529aac7e71b1a35e691709d6bffb87" + ], + "index": "pypi", + "version": "==1.10.1" }, "onnxruntime": { "hashes": [ - "sha256:0086c5d54d71f01e0c6bb4c91c46bd49017f58daa0f16b7c006d97d1e0a01e4a", - "sha256:1357b159e340c0acbf2f95c842d477f70c7d3ecefe575d98ec33277b6f45dd56", - "sha256:1c9d998cc6bd59e0018afc7277562b5b910f0f7bc9b3a208544d2dcfdc946f28", - "sha256:229c684f3f91dd830ff00dedcd3e97b9124360248eb109fb893324e3131ec2cc", - "sha256:2855272a3bb3c3992fabc478f00c92758011db60bc9b8a1517cca6b43a11fb63", - "sha256:2a4022d357c38c1c028b20a815deaa12f609fb6902f514de1d574810c860b70d", - "sha256:3d88648e9191c85865561b9a4cf0dfd25ee8db566e3d6147d460adb8b0f10c24", - "sha256:45560ed71fc656bb73b13f6eb630c81e2b4747276d3a0dbf79807e602f565ad9", - "sha256:60493b41d414df99cab2b048f751e6c03ec21560876c37be559f4e19350b3eeb", - "sha256:69836f00688cf2cb84e17dc9c1edc4ee1b0cde65fac45023269816c3ed732f22", - "sha256:7190ad81bca865e538fc98cd5b29ac7151276b65bcdacebe318ca0da9c2263d7", - "sha256:b337534228373c7582667f3fc1080be0dfff7ebd38a782d84ede4beb04535e65", - "sha256:e9d0c9069d34307458e4bd1275ac5e64746b393a7c0ef500724afca558df6207", - "sha256:f7be85c074d947352003f999c355130c4ba6018740e1b306b86dd593caccad1e", - "sha256:f8cfdb4601267ed42eb5a33bd611b8ee27c7700b2dfc00401a8031e2ab897c97", - "sha256:f96f020f5659a1b6b0fa3b19139ae19e8915aeaae938274c9db4d7619515d824" + "sha256:1c2cf7d5fe89e74f363596d90b952c367653763f16655a8b5060c7dea83868b9", + "sha256:2201b12c736c7c1ce683289ff012a768dc6ea0eeb81c9276fde31c86d6aebdf6", + "sha256:2d8eb89d4d62ba956f5b3392b0a02e50dfa3ba6e255561c16e3fd10d5a2b0de5", + "sha256:2df2999fdc0f1f5bcc87365b93fd951adbcdb615d312b1eb69aa3ffaeb0d66c3", + "sha256:417d09475d3928b224225e4bd1a6aa8ce061de7458ccda2b86dfe12508f34480", + "sha256:579a38f5abee3e89684ee223701d62409be20742767356d982e92cf2d70bbf0f", + "sha256:695e9badeb538bae87e25ee17c330c50a32114ebe6eae9bf2da03d66bda49888", + "sha256:7395d86c1c18c5e191f6d7cfe79ecc590fe8ed30f4b01a65e1da6c6cf3300e42", + "sha256:89fa6e392637c94c17e693772294ed9afe2403832f78c5efa1ccd79f463c5646", + "sha256:8fd9200c327042359ff733739ca4d0e2491d52f540b31a3fa29e280503aba11d", + "sha256:923721385a33d681d20878a090ae9e9e653c40eaa57665a4a87ddbcdbbd85e0b", + "sha256:a7a98e820b6a2d9a23908efad28ce4b0c7181e897273e70daa22b6110019bd7b", + "sha256:aa7346af63eade9a041b79363b2be04a60e4a5d4fe82bce4de2b1cdc3f63ccf4", + "sha256:affda6e92c4de3a3d7b35c8da35dedf8fc618b17638f719bb5398accb5d0bb57", + "sha256:c140299b0f94f4a3cdf872308b21941622b973868f491a37e7f3148f96d69834", + "sha256:cac8e35f953ff9898f0b5fdca0b76f492ae508e99e2eeb02ea70339e25b96715" ], "index": "pypi", - "version": "==1.8.0" + "version": "==1.8.1" + }, + "overpy": { + "hashes": [ + "sha256:75fa462c445a3d8ade4dad84df6f150d273f45548639229316829a3a8c3e2190" + ], + "index": "pypi", + "version": "==0.6" }, "pillow": { "hashes": [ - "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5", - "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4", - "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9", - "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a", - "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9", - "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727", - "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120", - "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c", - "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2", - "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797", - "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b", - "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f", - "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef", - "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232", - "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb", - "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9", - "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812", - "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178", - "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291", - "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b", - "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5", - "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b", - "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1", - "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713", - "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4", - "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484", - "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c", - "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9", - "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388", - "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d", - "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602", - "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9", - "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e", - "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2" - ], - "index": "pypi", - "version": "==8.2.0" + "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc", + "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63", + "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d", + "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e", + "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd", + "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4", + "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77", + "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723", + "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba", + "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792", + "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae", + "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e", + "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367", + "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77", + "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856", + "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4", + "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de", + "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8", + "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a", + "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636", + "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab", + "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79", + "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d", + "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229", + "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf", + "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500", + "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04", + "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093", + "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844", + "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8", + "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82", + "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf", + "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83", + "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0", + "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c", + "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8", + "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37", + "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24", + "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14" + ], + "index": "pypi", + "version": "==8.3.1" + }, + "platformdirs": { + "hashes": [ + "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", + "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.0" }, "protobuf": { "hashes": [ @@ -501,19 +552,23 @@ "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87", "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e", "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde", + "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1", "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d", "sha256:6ce4d8bf0321e7b2d4395e253f8002a1a5ffbcfd7bcc0a6ba46712c07d47d0b4", "sha256:6d847c59963c03fd7a0cd7c488cadfa10cda4fff34d8bc8cba92935a91b7a037", "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b", "sha256:7a4c97961e9e5b03a56f9a6c82742ed55375c4a25f2692b625d4087d02ed31b9", + "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d", "sha256:8727ee027157516e2c311f218ebf2260a18088ffb2d29473e82add217d196b1c", "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1", "sha256:9b7a5c1022e0fa0dbde7fd03682d07d14624ad870ae52054849d8960f04bc764", "sha256:a22b3a0dbac6544dacbafd4c5f6a29e389a50e3b193e2c70dae6bbf7930f651d", + "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554", "sha256:a981222367fb4210a10a929ad5983ae93bd5a050a0824fc35d6371c07b78caf6", "sha256:ab6bb0e270c6c58e7ff4345b3a803cc59dbee19ddf77a4719c5b635f1d547aa8", "sha256:c56c050a947186ba51de4f94ab441d7f04fcd44c56df6e922369cc2e1a92d683", "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47", + "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2", "sha256:f0e59430ee953184a703a324b8ec52f571c6c4259d496a19d1cabcdc19dabc62", "sha256:ffea251f5cd3c0b9b43c7a7a912777e0bc86263436a87c2555242a348817221b" ], @@ -642,11 +697,11 @@ }, "pylint": { "hashes": [ - "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8", - "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484" + "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1", + "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852" ], "index": "pypi", - "version": "==2.8.3" + "version": "==2.10.2" }, "pyserial": { "hashes": [ @@ -658,11 +713,11 @@ }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.2" }, "pyyaml": { "hashes": [ @@ -701,65 +756,70 @@ }, "pyzmq": { "hashes": [ - "sha256:089b974ec04d663b8685ac90e86bfe0e4da9d911ff3cf52cb765ff22408b102d", - "sha256:0ea7f4237991b0f745a4432c63e888450840bf8cb6c48b93fb7d62864f455529", - "sha256:0f0f27eaab9ba7b92d73d71c51d1a04464a1da6097a252d007922103253d2313", - "sha256:12ffcf33db6ba7c0e5aaf901e65517f5e2b719367b80bcbfad692f546a297c7a", - "sha256:1389b615917d4196962a9b469e947ba862a8ec6f5094a47da5e7a8d404bc07a4", - "sha256:18dd2ca4540c476558099891c129e6f94109971d110b549db2a9775c817cedbd", - "sha256:24fb5bb641f0b2aa25fc3832f4b6fc62430f14a7d328229fe994b2bcdc07c93a", - "sha256:285514956c08c7830da9d94e01f5414661a987831bd9f95e4d89cc8aaae8da10", - "sha256:41049cff5265e9cd75606aa2c90a76b9c80b98d8fe70ee08cf4af3cedb113358", - "sha256:461ed80d741692d9457ab820b1cc057ba9c37c394e67b647b639f623c8b321f6", - "sha256:4b8fb1b3174b56fd020e4b10232b1764e52cf7f3babcfb460c5253bdc48adad0", - "sha256:4c4fe69c7dc0d13d4ae180ad650bb900854367f3349d3c16f0569f6c6447f698", - "sha256:4e9b9a2f6944acdaf57316436c1acdcb30b8df76726bcf570ad9342bc5001654", - "sha256:6355f81947e1fe6e7bb9e123aeb3067264391d3ebe8402709f824ef8673fa6f3", - "sha256:68be16107f41563b9f67d93dff1c9f5587e0f76aa8fd91dc04c83d813bcdab1f", - "sha256:68e2c4505992ab5b89f976f89a9135742b18d60068f761bef994a6805f1cae0c", - "sha256:7040d6dd85ea65703904d023d7f57fab793d7ffee9ba9e14f3b897f34ff2415d", - "sha256:734ea6565c71fc2d03d5b8c7d0d7519c96bb5567e0396da1b563c24a4ac66f0c", - "sha256:9ee48413a2d3cd867fd836737b4c89c24cea1150a37f4856d82d20293fa7519f", - "sha256:a1c77796f395804d6002ff56a6a8168c1f98579896897ad7e35665a9b4a9eec5", - "sha256:b2f707b52e09098a7770503e39294ca6e22ae5138ffa1dd36248b6436d23d78e", - "sha256:bf80b2cec42d96117248b99d3c86e263a00469c840a778e6cb52d916f4fdf82c", - "sha256:c4674004ed64685a38bee222cd75afa769424ec603f9329f0dd4777138337f48", - "sha256:c6a81c9e6754465d09a87e3acd74d9bb1f0039b2d785c6899622f0afdb41d760", - "sha256:c6d0c32532a0519997e1ded767e184ebb8543bdb351f8eff8570bd461e874efc", - "sha256:c8fff75af4c7af92dce9f81fa2a83ed009c3e1f33ee8b5222db2ef80b94e242e", - "sha256:cb9f9fe1305ef69b65794655fd89b2209b11bff3e837de981820a8aa051ef914", - "sha256:d3ecfee2ee8d91ab2e08d2d8e89302c729b244e302bbc39c5b5dde42306ff003", - "sha256:d5e5be93e1714a59a535bbbc086b9e4fd2448c7547c5288548f6fd86353cad9e", - "sha256:de5806be66c9108e4dcdaced084e8ceae14100aa559e2d57b4f0cceb98c462de", - "sha256:f49755684a963731479ff3035d45a8185545b4c9f662d368bd349c419839886d", - "sha256:fc712a90401bcbf3fa25747f189d6dcfccbecc32712701cad25c6355589dac57" - ], - "index": "pypi", - "version": "==22.1.0" + "sha256:021e22a8c58ab294bd4b96448a2ca4e716e1d76600192ff84c33d71edb1fbd37", + "sha256:0471d634c7fe48ff7d3849798da6c16afc71676dd890b5ae08eb1efe735c6fec", + "sha256:0d17bac19e934e9f547a8811b7c2a32651a7840f38086b924e2e3dcb2fae5c3a", + "sha256:200ac096cee5499964c90687306a7244b79ef891f773ed4cf15019fd1f3df330", + "sha256:240b83b3a8175b2f616f80092cbb019fcd5c18598f78ffc6aa0ae9034b300f14", + "sha256:246f27b88722cfa729bb04881e94484e40b085720d728c1b05133b3f331b0b7b", + "sha256:2534a036b777f957bd6b89b55fb2136775ca2659fb0f1c85036ba78d17d86fd5", + "sha256:262f470e7acde18b7217aac78d19d2e29ced91a5afbeb7d98521ebf26461aa7e", + "sha256:2dd3896b3c952cf6c8013deda53c1df16bf962f355b5503d23521e0f6403ae3d", + "sha256:31c5dfb6df5148789835128768c01bf6402eb753d06f524f12f6786caf96fb44", + "sha256:4842a8263cbaba6fce401bbe4e2b125321c401a01714e42624dabc554bfc2629", + "sha256:50d007d5702171bc810c1e74498fa2c7bc5b50f9750697f7fd2a3e71a25aad91", + "sha256:5933d1f4087de6e52906f72d92e1e4dcc630d371860b92c55d7f7a4b815a664c", + "sha256:620b0abb813958cb3ecb5144c177e26cde92fee6f43c4b9de6b329515532bf27", + "sha256:631f932fb1fa4b76f31adf976f8056519bc6208a3c24c184581c3dd5be15066e", + "sha256:66375a6094af72a6098ed4403b15b4db6bf00013c6febc1baa832e7abda827f4", + "sha256:6a5b4566f66d953601d0d47d4071897f550a265bafd52ebcad5ac7aad3838cbb", + "sha256:6d18c76676771fd891ca8e0e68da0bbfb88e30129835c0ade748016adb3b6242", + "sha256:6e9c030222893afa86881d7485d3e841969760a16004bd23e9a83cca28b42778", + "sha256:89200ab6ef9081c72a04ed84c52a50b60dcb0655375aeedb40689bc7c934715e", + "sha256:93705cb90baa9d6f75e8448861a1efd3329006f79095ab18846bd1eaa342f7c3", + "sha256:a649065413ba4eab92a783a7caa4de8ce14cf46ba8a2a09951426143f1298adb", + "sha256:ac4497e4b7d134ee53ce5532d9cc3b640d6e71806a55062984e0c99a2f88f465", + "sha256:b2c16d20bd0aef8e57bc9505fdd80ea0d6008020c3740accd96acf1b3d1b5347", + "sha256:b3f57bee62e36be5c97712de32237c5589caee0d1154c2ad01a888accfae20bc", + "sha256:b4428302c389fffc0c9c07a78cad5376636b9d096f332acfe66b321ae9ff2c63", + "sha256:b4a51c7d906dc263a0cc5590761e53e0a68f2c2fefe549cbef21c9ee5d2d98a4", + "sha256:b921758f8b5098faa85f341bbdd5e36d5339de5e9032ca2b07d8c8e7bec5069b", + "sha256:c1b6619ceb33a8907f1cb82ff8afc8a133e7a5f16df29528e919734718600426", + "sha256:c9cb0bd3a3cb7ccad3caa1d7b0d18ba71ed3a4a3610028e506a4084371d4d223", + "sha256:d60a407663b7c2af781ab7f49d94a3d379dd148bb69ea8d9dd5bc69adf18097c", + "sha256:da7f7f3bb08bcf59a6b60b4e53dd8f08bb00c9e61045319d825a906dbb3c8fb7", + "sha256:e66025b64c4724ba683d6d4a4e5ee23de12fe9ae683908f0c7f0f91b4a2fd94e", + "sha256:ed67df4eaa99a20d162d76655bda23160abdf8abf82a17f41dfd3962e608dbcc", + "sha256:f520e9fee5d7a2e09b051d924f85b977c6b4e224e56c0551c3c241bbeeb0ad8d", + "sha256:f5c84c5de9a773bbf8b22c51e28380999ea72e5e85b4db8edf5e69a7a0d4d9f9", + "sha256:ff345d48940c834168f81fa1d4724675099f148f1ab6369748c4d712ed71bf7c" + ], + "index": "pypi", + "version": "==22.2.1" }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], "index": "pypi", - "version": "==2.25.1" + "version": "==2.26.0" }, "scons": { "hashes": [ - "sha256:52272288986f3e401d28590562c573405dff0decfbf701926e751c0954b64da6", - "sha256:ecb062482b9d80319b56758c0341eb717735437f86a575bac3552804428bd73e" + "sha256:663f819e744ddadcdf4f46b03289a7210313b86041efe1b9c8dde81dba437b72", + "sha256:691893b63f38ad14295f5104661d55cb738ec6514421c6261323351c25432b0a" ], "index": "pypi", - "version": "==4.1.0.post1" + "version": "==4.2.0" }, "sentry-sdk": { "hashes": [ - "sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739", - "sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada" + "sha256:ebe99144fa9618d4b0e7617e7929b75acd905d258c3c779edcd34c0adfffe26c", + "sha256:f33d34c886d0ba24c75ea8885a8b3a172358853c7cbde05979fc99c29ef7bc52" ], "index": "pypi", - "version": "==1.1.0" + "version": "==1.3.1" }, "setproctitle": { "hashes": [ @@ -825,32 +885,32 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tqdm": { "hashes": [ - "sha256:24be966933e942be5f074c29755a95b315c69a91f839a29139bf26ffffe2d3fd", - "sha256:aa0c29f03f298951ac6318f7c8ce584e48fa22ec26396e6411e43d038243bdb2" + "sha256:80aead664e6c1672c4ae20dc50e1cdc5e20eeff9b14aa23ecd426375b28be588", + "sha256:a4d6d112e507ef98513ac119ead1159d286deab17dffedd96921412c2d236ff5" ], "index": "pypi", - "version": "==4.61.1" + "version": "==4.62.2" }, "typing-extensions": { "hashes": [ - "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", - "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", - "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" ], - "version": "==3.10.0.0" + "version": "==3.10.0.2" }, "urllib3": { "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], "index": "pypi", - "version": "==1.26.5" + "version": "==1.26.6" }, "utm": { "hashes": [ @@ -861,11 +921,11 @@ }, "websocket-client": { "hashes": [ - "sha256:b68e4959d704768fa20e35c9d508c8dc2bbc041fd8d267c0d7345cffe2824568", - "sha256:e5c333bfa9fa739538b652b6f8c8fc2559f1d364243c8a689d7c0e1d41c2e611" + "sha256:0133d2f784858e59959ce82ddac316634229da55b498aac311f1620567a710ec", + "sha256:8dfb715d8a992f5712fff8c843adae94e22b22a99b2c5e6b0ec4a1a981cc4e0d" ], "index": "pypi", - "version": "==1.1.0" + "version": "==1.2.1" }, "werkzeug": { "hashes": [ @@ -942,13 +1002,6 @@ "index": "pypi", "version": "==3.7.4.post0" }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" - }, "applicationinsights": { "hashes": [ "sha256:0b761f3ef0680acf4731906dfc1807faa6f2a57168ae74592db0084a6099f7b3", @@ -965,46 +1018,28 @@ }, "argon2-cffi": { "hashes": [ - "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf", - "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5", - "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5", - "sha256:36320372133a003374ef4275fbfce78b7ab581440dfca9f9471be3dd9a522428", - "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b", - "sha256:3aa804c0e52f208973845e8b10c70d8957c9e5a666f702793256242e9167c4e0", - "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc", - "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203", - "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003", - "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78", - "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe", - "sha256:8282b84ceb46b5b75c3a882b28856b8cd7e647ac71995e71b6705ec06fc232c3", - "sha256:8a84934bd818e14a17943de8099d41160da4a336bcc699bb4c394bbb9b94bd32", - "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361", - "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2", - "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647", - "sha256:b94042e5dcaa5d08cf104a54bfae614be502c6f44c9c89ad1535b2ebdaacbd4c", - "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496", - "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b", - "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d", - "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa", - "sha256:e2db6e85c057c16d0bd3b4d2b04f270a7467c147381e8fd73cbbe5bc719832be" + "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057", + "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a", + "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a", + "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8", + "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb", + "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9", + "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a", + "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378", + "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659", + "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870", + "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41" ], - "version": "==20.1.0" + "markers": "python_version >= '3.5'", + "version": "==21.1.0" }, "astroid": { "hashes": [ - "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", - "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" + "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c", + "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e" ], "markers": "python_version ~= '3.6'", - "version": "==2.5.6" - }, - "async-generator": { - "hashes": [ - "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b", - "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144" - ], - "markers": "python_version >= '3.5'", - "version": "==1.10" + "version": "==2.7.3" }, "async-timeout": { "hashes": [ @@ -1024,11 +1059,11 @@ }, "azure-cli-core": { "hashes": [ - "sha256:679fbaeab0224cb721d27070feaf61510c3628c4af463af518b59e30735335ae", - "sha256:a4bff7b9ff5e9e658208c712f75376035a482a80b5dae808b8c1489c982da58f" + "sha256:8ebfc4c3a83b5a59448a0e2ec7c9198168586bb0c6235a7cfde4444830af3877", + "sha256:e7d99cbd910d6d0554313726f4d41f1d2c16b4b2eb77f52ad5d011d549d5714f" ], "index": "pypi", - "version": "==2.25.0" + "version": "==2.27.2" }, "azure-cli-telemetry": { "hashes": [ @@ -1047,11 +1082,11 @@ }, "azure-core": { "hashes": [ - "sha256:197917b98fec661c35392e32abec4f690ac2117371a814e25e57c224ce23cf1f", - "sha256:74631dff314fd44419ac6a3a38e8af68418b08a1b6e6793128555db20501dd07" + "sha256:25407390dde142d3e41ecf78bb18cedda9b7f7a0af558d082dec711c4a334f46", + "sha256:906e031a8241fe0794ec4137aca77a1aeab2ebde5cd6049c377d05cb6b87b691" ], "index": "pypi", - "version": "==1.15.0" + "version": "==1.17.0" }, "azure-mgmt-core": { "hashes": [ @@ -1100,6 +1135,14 @@ ], "version": "==0.2.0" }, + "backports.entry-points-selectable": { + "hashes": [ + "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a", + "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc" + ], + "markers": "python_version >= '2.7'", + "version": "==1.1.0" + }, "backports.lzma": { "hashes": [ "sha256:16d8b68e4d3cd4e6c9ddb059850452946da3914c8a8e197a7f2b0954559f2df4" @@ -1130,11 +1173,11 @@ }, "bleach": { "hashes": [ - "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125", - "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433" + "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da", + "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==3.3.0" + "markers": "python_version >= '3.6'", + "version": "==4.1.0" }, "boto": { "hashes": [ @@ -1146,19 +1189,19 @@ }, "boto3": { "hashes": [ - "sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31", - "sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73" + "sha256:5116e9bdec19adcc5531a9b7b535be77d5314eef092aaf7033ace48a9be65036", + "sha256:658ddf4ba552f654fd4d48335fa95ff4e3e1a4e82f90021a1a1d3de4a5428ba4" ], "index": "pypi", - "version": "==1.17.98" + "version": "==1.18.34" }, "botocore": { "hashes": [ - "sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f", - "sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6" + "sha256:1b4999fb0e1a4c050c4d9118ebdaac8d83761ef32c3c0f13a25f9204045998fe", + "sha256:ec2cdf1c8ed64a7f392f352125d248c76103fa9d137b275b7c76836776cedf56" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.98" + "markers": "python_version >= '3.6'", + "version": "==1.21.34" }, "casadi": { "hashes": [ @@ -1213,66 +1256,62 @@ }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", - "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", - "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", - "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" - ], - "index": "pypi", - "version": "==1.14.5" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "index": "pypi", + "version": "==1.14.6" }, "cfgv": { "hashes": [ - "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1", - "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1" + "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", + "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.3.0" + "version": "==3.3.1" }, "chardet": { "hashes": [ @@ -1365,21 +1404,26 @@ }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" - ], - "index": "pypi", - "version": "==3.4.7" + "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e", + "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b", + "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", + "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", + "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", + "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", + "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", + "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", + "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", + "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", + "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb", + "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14", + "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af", + "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e", + "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5", + "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06", + "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7" + ], + "index": "pypi", + "version": "==3.4.8" }, "cycler": { "hashes": [ @@ -1390,18 +1434,81 @@ }, "datadog": { "hashes": [ - "sha256:3de1a43b8a8d5f6b19d162ec1b482dc5ab2636c59cf65e60589702304510a689", - "sha256:ab79ed38fb09ff1942c341e32849c4eeaf8b2e4d467b9e6bb1c6071808f454d6" + "sha256:140b51f5db3f46d6f3ec022c05830c6b3e13e4f62c19c823e1227ac322b26667", + "sha256:7a6fac17a7d09f1883ab9a45ce4ff7a16aa1a5eb3cc4c6cddac7f8c53e7d1e9b" + ], + "index": "pypi", + "version": "==0.42.0" + }, + "debugpy": { + "hashes": [ + "sha256:00f9d14da52b87e98e26f5c3c8f1937cc496915b38f8ccb7b329336b21898678", + "sha256:129312b01ec46ab303a8c0667d559a0de0bed1a394cc128039b6f008f1c376b7", + "sha256:12cb415e7394c6738527cbc482935aa9414e9b4cc87dd040015d0e5cb8b4471a", + "sha256:1762908202b0b0b481ec44125edb625d136d16c4991d3a7c1310c85672ffe5ba", + "sha256:1bc8e835a48ef23280cbaf2b70a5a2b629b9ee79685b64d974bfb8d467f4aa67", + "sha256:2bfda2721046fb43a7074d475a12adcd55a65bfd23a1ff675427b09a01ba40cc", + "sha256:2d4c4ab934fbe1c7095d19b3d4246afe119396b49540ca5d5ad34ef01b27bd2a", + "sha256:309909b6c85f89aea3fa10fc256b52fef3c25fee4d00e1b5f5db1ace57203a2c", + "sha256:3756cd421be701d06490635372327ebd1ccb44b37d59682c994f6bd59e040a91", + "sha256:399b2c60c8e67a5d30c6e4522129e8be8d484e6064286f8ba3ce857a3927312a", + "sha256:3a6dee475102d0169732162b735878e8787500719ccb4d54b1458afe992a4c4d", + "sha256:3d92cb2e8b4f9591f6d6e17ccf8c1a55a58857949d9a5aae0ff37b64faaa3b80", + "sha256:4655824321b36b353b12d1617a29c79320412f085ecabf54524603b4c0c791e8", + "sha256:4e0d57a8c35b20b4e363db943b909aa83f12594e2f34070a1db5fa9b7213336b", + "sha256:52920ccb4acdbb2a9a42e0a4d60a7bbc4a34bf16fd23c674b280f8e9a8cacbd6", + "sha256:595170ac17567773b546d40a0ff002dc350cfcd95c9233f65e79370954fb9a01", + "sha256:67d496890d1cada5ce924cb30178684e7b82a36b80b8868beb148db54fd9e44c", + "sha256:6bb62615b3ad3d7202b7b7eb85f3d000aa17a61303af5f11eab048c91a1f30a6", + "sha256:71e67d352cabdc6a3f4dc3e39a1d2d1e76763a2102a276904e3495ede48a9832", + "sha256:732ac8bb79694cb4127c08bfc6128274f3dee9e6fd2ddde7bf026a40efeb202d", + "sha256:7376bd8f4272ab01342940bd020955f021e26954e1f0df91cfa8bf1fa4451b56", + "sha256:768f393ffaa66a3b3ed92b06e21912a5df3e01f18fb531bcbba2f94cad1725a7", + "sha256:7b332ce0d1a46f0f4200d59ee78428f18301d1fb85d07402723b94e1de96951c", + "sha256:7b4e399790a301c83ad6b153452233695b2f15450d78956a6d297859eb44d185", + "sha256:7e12e94aa2c9a0017c0a84cd475063108d06e305360b69c933bde17a6a527f80", + "sha256:84ff51b8b5c847d5421324ca419db1eec813a4dd2bbf19dbbbe132e2ab2b2fc6", + "sha256:86cd13162b752664e8ef048287a6973c8fba0a71f396b31cf36394880ec2a6bf", + "sha256:889316de0b8ff3732927cb058cfbd3371e4cd0002ecc170d34c755ad289c867c", + "sha256:89d53d57001e54a3854489e898c697aafb2d6bb81fca596da2400f3fd7fd397c", + "sha256:8a2be4e5d696ad39be6c6c37dc580993d04aad7d893fd6e449e1a055d7b5dddb", + "sha256:8e63585c372873cd88c2380c0b3c4815c724a9713f5b86d1b3a1f1ac30df079e", + "sha256:939c94d516e6ed5433cc3ba12d9d0d8108499587158ae5f76f6db18d49e21b5b", + "sha256:959d39f3d724d25b7ab79278f032e33df03c6376d51b3517abaf2f8e83594ee0", + "sha256:9a0cd73d7a76222fbc9f9180612ccb4ad7d7f7e4f26e55ef1fbd459c0f2f5322", + "sha256:9d559bd0e4c288487349e0723bc70ff06390638446ee8087d4d5711486119643", + "sha256:a19def91a0a166877c2a26b611c1ad0473ce85b1df61ae5276197375d574228b", + "sha256:a2c5a1c49239707ed5bc8e97d8f9252fb392d9e13c79c7b477593d7dde4ae24a", + "sha256:a4368c79a2c4458d5a0540381a32f8fdc02b3c9ba9dd413a49b42929297b29b3", + "sha256:a9f582203af34c6978bffaba77425662e949251998276e9dece113862e753459", + "sha256:ab37f189b1dd0d8420545c9f3d066bd1601a1ae85b26de38f5c1ccb96cf0b042", + "sha256:ac2d1cdd3279806dab2119937c0769f11dee13166650aaa84b6700b30a845d10", + "sha256:bad668e9edb21199017ab31f52a05e14506ad6566110560796d2a8f258e0b819", + "sha256:c5e771fcd12727f734caf2a10ff92966ae9857db0ccb6bebd1a4f776c54186a8", + "sha256:c96e82d863db97d3eb498cc8e55773004724bdeaa58fb0eb7ee7d5a21d240d6a", + "sha256:cd36e75c0f71a924f4b4cdb5f74b3321952cf636aadf70e0f85fd9cd2edfc1d0", + "sha256:cf6b26f26f97ef3033008db7b3df7233363407d7b6cacd4bc4f8e02ce8e11df4", + "sha256:d89ab3bd51d6a3f13b093bc3881a827d8f6c9588d9a493bddb3b47f9d078fd1d", + "sha256:dea62527a4a2770a0d12ce46564636d892bba29baaf5dba5bfe98bb55bf17a11", + "sha256:e47c42bc1a68ead3c39d9a658d3ccf311bc45dc84f3c90fa5cb7de1796243f47", + "sha256:e6711106aafc26ecb78e43c4be0a49bd0ae4a1f3e1aa502de151e38f4717b2a2", + "sha256:e7e049a4e8e362183a5a5b4ad058a1543211970819d0c11011c87c3a9dec2eaf", + "sha256:ebc241351791595796864a960892e1cd58627064feda939d0377edd0730bbff2", + "sha256:eee2224ce547d2958ffc0d63cd280a9cc6377043f32ce370cfe4ca6be4e05476", + "sha256:f20a07ac5fb0deee9be1ad1a9a124d858a8b79c66c7ec5e1767d78aa964f86c4", + "sha256:f77406f33760e6f13a7ff0ac375d9c8856844b61cd95f7502b57116858f0cfe1", + "sha256:fece69933d17e0918b73ddeb5e23bcf789edd2a6eb0d438b09c40d51e76b9c74" ], - "index": "pypi", - "version": "==0.41.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.4.1" }, "decorator": { "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" + "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323", + "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5" ], - "version": "==4.4.2" + "markers": "python_version >= '3.5'", + "version": "==5.0.9" }, "defusedxml": { "hashes": [ @@ -1413,11 +1520,11 @@ }, "dictdiffer": { "hashes": [ - "sha256:1adec0d67cdf6166bda96ae2934ddb5e54433998ceab63c984574d187cc563d2", - "sha256:d79d9a39e459fe33497c858470ca0d2e93cb96621751de06d631856adfd9c390" + "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", + "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595" ], "index": "pypi", - "version": "==0.8.1" + "version": "==0.9.0" }, "distlib": { "hashes": [ @@ -1428,11 +1535,11 @@ }, "elasticsearch": { "hashes": [ - "sha256:583459eaa864d0e4c11b4e0314569999fa780748856bfaeb8d714fc9243c26a2", - "sha256:fd8b3a267da279ae78f7923c549d488403cdbf83a98299bb130feb832d014b8f" + "sha256:1a9f146b7126a7e0621085f1825b6b2d091693a714d3b16c208749762e79c2bc", + "sha256:f928898fe06869516f2603f9a96a6f166c06888233806b31ac6568bac0266501" ], "index": "pypi", - "version": "==7.13.2" + "version": "==7.14.1" }, "entrypoints": { "hashes": [ @@ -1444,26 +1551,26 @@ }, "fastcluster": { "hashes": [ - "sha256:0b571b7ca52afd966c1fe3248855429c231a7552d2b4253b3a20a85aa1339af9", - "sha256:1dcf39669f9a9140799e375684355221c8abcc2d9a88b8786d9fa0a60e49bc9d", - "sha256:4531507d0ba9efba7b8fb01fe1e1a5cce0a3e5fcdd5ea0d4c80acf41ad38f681", - "sha256:5fb25c16bed6bf90164ad02c566f53a9a3015a05679167fb169c920cb77e0f65", - "sha256:638da3c5cac3fc2ce73e158a14516a01a165456670f156a0a7fc398141575e97", - "sha256:667d011550596b33132d01b0124fd9084f3a0c530496f6ff04515d812e67dfda", - "sha256:67bc9fd44a4ef506e3adee83dc1a79db17b73365f8f9f9d52aba418e18236836", - "sha256:77d83c79befa61a3469a36fb999a91a4e3b21275a1f934dfd1171a34c678edb5", - "sha256:9b797d8e9da5bf44f76f2ea73b475284f5880eb96947b796209405594ca80f61", - "sha256:9e154d95629535d3c81adc800cfad7c3f76c2d94a62d2f1922ed40379a63b2e7", - "sha256:a186f5457ca2ffc455eeb456dfd7670e30a9e24baaf2d607e0d5a0602771d5f2", - "sha256:a88ebf0e6185d9f7dbbd7d5cd412315907bcb69fa20f8faf181e4ec7c931a067", - "sha256:b2def480b2053bc54a03345b03a992a34d54f64b4abcf84b56668fe19de0eed8", - "sha256:bace0f06703e748d62c3bee54e224c134b661e60fd6aeb5c0f0f02a1cf71f1d5", - "sha256:d31476a79e115ec7be19f260c8eeb9df17427e8e857204279afddab42d32921a", - "sha256:e83ac1def0cfac381db164abb419db89806e600c94d89a2af8f4f1f381b1ff8f", - "sha256:f66146f1b612eeffa5023ac052b828e6d609ac91672a84781262068f2059e878" - ], - "index": "pypi", - "version": "==1.2.3" + "sha256:181af434d47c0628a98182f6d1483d0fd1da2a65ed4acd5f04f9bd1038098e63", + "sha256:2208336bdf4deb9096baeac54e7ac605750010fb23a325e22240655f86fbb1a9", + "sha256:2ec0435e53f60180de29f2e0928f6710b34a19e20fe29b5d45e8848603e3a67d", + "sha256:3f715a6a9f19dccb1c6fd319d2543097c9dd4fb2d9568d6f2e97dffff8a67fe3", + "sha256:47fdf82cd1f060052e5230884493d7f828292d5daf57fb9def54436bb93a9c30", + "sha256:56706ba56f1c9a31530a270da28e29ec392bc3c7695b5cb0b3ba3b4b27b0f6e2", + "sha256:7dff20b20c2f4b63c42884f35733a710318d0d4800135db016245dd71d57fcae", + "sha256:8248b94e37731e61a2477246707af6e5c96156fc5c0814ac9469b288cf4d3195", + "sha256:8d5e6635cdef150b971b3622917d9f803a5f3a6b19ff2267f1ec972e6d252f15", + "sha256:8e125919826251cbe6bcda3a5105b5a880d1210a7a2fcf28b23f02e21f685aff", + "sha256:8f286fa2180fc4738063a54844008dcab539616be2a3220fdfb7e833a6a872b1", + "sha256:98dd400e9e3045dd100e6c83b3b08c852209b1ed2dee3536cda136064952d2e3", + "sha256:ac9ee520ca95f28293f55e7dbe5603bdf777b088ca21ab964539a493bcc72008", + "sha256:b5697a26b5397004bba4ac6308e9e9b7a832dcccfcc0333554bc3898a55601a3", + "sha256:c39527d0cc2a6194b1338a769be7286e570d4585c01fa656bdc83cadfdeb0d4d", + "sha256:d72292749151435426666e3d2ff019fbd7550755e79ce6ab5a64388ccf42ca44", + "sha256:dcfc967608b1edd49eb542087bebb6224315b477d80e62dcef567ca3f72141bf" + ], + "index": "pypi", + "version": "==1.2.4" }, "filelock": { "hashes": [ @@ -1490,11 +1597,11 @@ }, "flask-socketio": { "hashes": [ - "sha256:68ae373a41d906819f5129d4d28ad062fcf0085dd06424753a79ca47de509ab5", - "sha256:b41b9f6fb0d7f3fcadd54c44653307a9b96e985c7da73f92779480248b5b6874" + "sha256:07e1899e3b4851978b2ac8642080156c6294f8d0fc5212b4e4bcca713830306a", + "sha256:1efdaacc7a26e94f2b197a80079b1058f6aa644a6094c0a322349e2b9c41f6b1" ], "index": "pypi", - "version": "==5.1.0" + "version": "==5.1.1" }, "ft4222": { "hashes": [ @@ -1514,7 +1621,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.18.2" }, "future-fstrings": { @@ -1527,11 +1634,11 @@ }, "geoalchemy2": { "hashes": [ - "sha256:1984f7b189b1994edad7800154973ebd48a26094376a2c9cd7447133051faf60", - "sha256:de0d0b1ff2f7f26ddff0a69d9a33940020707caedd442fa13b28affec842dafb" + "sha256:2e3323a8442d4b1f8de69106315c17d8ce729fc127a38a5d7e2b53e1a19b9dd5", + "sha256:b0e56d4a945bdc0f8fa9edd50ecc912889ea68e0e3558a19160dcb0d5b1b65fc" ], "index": "pypi", - "version": "==0.9.1" + "version": "==0.9.4" }, "git-pylint-commit-hook": { "hashes": [ @@ -1542,58 +1649,59 @@ }, "greenlet": { "hashes": [ - "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c", - "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832", - "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08", - "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e", - "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22", - "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f", - "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c", - "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea", - "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8", - "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad", - "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc", - "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16", - "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8", - "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5", - "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99", - "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e", - "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a", - "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56", - "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c", - "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed", - "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959", - "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922", - "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927", - "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e", - "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a", - "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131", - "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919", - "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319", - "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae", - "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535", - "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505", - "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11", - "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47", - "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821", - "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857", - "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da", - "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc", - "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5", - "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb", - "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05", - "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5", - "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee", - "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e", - "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831", - "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f", - "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3", - "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6", - "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3", - "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f" - ], - "markers": "python_version >= '3'", - "version": "==1.1.0" + "sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3", + "sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16", + "sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c", + "sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f", + "sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45", + "sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b", + "sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384", + "sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c", + "sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f", + "sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1", + "sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc", + "sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68", + "sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142", + "sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92", + "sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83", + "sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e", + "sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c", + "sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d", + "sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687", + "sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117", + "sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6", + "sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd", + "sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b", + "sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649", + "sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3", + "sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c", + "sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378", + "sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6", + "sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272", + "sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e", + "sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5", + "sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04", + "sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885", + "sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf", + "sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662", + "sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722", + "sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de", + "sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837", + "sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481", + "sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7", + "sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629", + "sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f", + "sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663", + "sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362", + "sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b", + "sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334", + "sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4", + "sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c", + "sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563", + "sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1" + ], + "markers": "python_version >= '3' and platform_machine in 'x86_64 X86_64 aarch64 AARCH64 ppc64le PPC64LE amd64 AMD64 win32 WIN32'", + "version": "==1.1.1" }, "gunicorn": { "hashes": [ @@ -1605,19 +1713,19 @@ }, "h5py": { "hashes": [ - "sha256:09e78cefdef0b7566ab66366c5c7d9984c7b23142245bd51b82b744ad1eebf65", - "sha256:13355234c004ff8bd819f7d3420188aa1936b17d7f8470d622974a373421b7a5", - "sha256:5e2f22e66a3fb1815405cfe5711670450c973b8552507c535a546a23a468af3d", - "sha256:7ca7d23ebbdd59a4be9b4820de52fe67adc74e6a44d5084881305461765aac47", - "sha256:89d7e10409b62fed81c571e35798763cb8375442b98f8ebfc52ba41ac019e081", - "sha256:8e09b682e4059c8cd259ddcc34bee35d639b9170105efeeae6ad195e7c1cea7a", - "sha256:baef1a2cdef287a83e7f95ce9e0f4d762a9852fe7117b471063442c78b973695", - "sha256:e0dac887d779929778b3cfd13309a939359cc9e74756fc09af7c527a82797186", - "sha256:e0ea3330bf136f8213e43db67448994046ce501585dddc7ea4e8ceef0ef1600c", - "sha256:f3bba8ffddd1fd2bf06127c5ff7b73f022cc1c8b7164355ddc760dc3f8570136" + "sha256:0b0f002f5f341afe7d3d7e15198e80d9021da24a4d182d88068d79bfc91fba86", + "sha256:1edf33e722d47c6eb3878d51173b23dd848939f006f41b498bafceff87fb4cbd", + "sha256:46917f20021dde02865572a5fd2bb620945f7b7cd268bdc8e3f5720c32b38140", + "sha256:708ddff49af12c01d77e0f9782bb1a0364d96459ec0d1f85d90baea6d203764b", + "sha256:8745e5159830d7975a9cf38690455f22601509cda04de29b7e88b3fbdc747611", + "sha256:8e809149f95d9a3a33b1279bfbf894c78635a5497e8d5ac37420fa5ec0cf4f29", + "sha256:aa511bd05a9174c3008becdc93bd5785e254d34a6ab5f0425e6b2fbbc88afa6d", + "sha256:bb4ce46095e3b16c872aaf62adad33f40039fecae04674eb62c035386affcb91", + "sha256:be2a545f09074546f73305e0db6d36aaf1fb6ea2fcf1add2ce306b9c7f78e55a", + "sha256:ee1c683d91ab010d5e85cb61e8f9e7ee0d8eab545bf3dd50a9618f1d0e8f615e" ], "index": "pypi", - "version": "==3.3.0" + "version": "==3.4.0" }, "hexdump": { "hashes": [ @@ -1636,27 +1744,27 @@ }, "hypothesis": { "hashes": [ - "sha256:27aa2af763af06b8b61ce65c09626cf1da6d3a6ff155900f3c581837b453313a", - "sha256:9bdee01ae260329b16117e9b0229a839b4a77747a985922653f595bd2a6a541a" + "sha256:47b86bda10dba94bb9e5733dcb4df27fca8b421ebe81aad2516441272c374c10", + "sha256:fd5a2207aaaaea430fe1dd0fc4392edb1c95cb21034fdf69231113cc1ab86fa0" ], "index": "pypi", - "version": "==6.14.0" + "version": "==6.17.4" }, "identify": { "hashes": [ - "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421", - "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306" + "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c", + "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79" ], "markers": "python_full_version >= '3.6.1'", - "version": "==2.2.10" + "version": "==2.2.13" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "imageio": { "hashes": [ @@ -1676,19 +1784,19 @@ }, "ipykernel": { "hashes": [ - "sha256:29eee66548ee7c2edb7941de60c0ccf0a7a8dd957341db0a49c5e8e6a0fcb712", - "sha256:e976751336b51082a89fc2099fb7f96ef20f535837c398df6eab1283c2070884" + "sha256:3d34530e031067f04e88b72715e92c27871368c15998692d665d57027ceb18d9", + "sha256:6dd4b107ab755ed9286c820b2f69c2cd895046ef2a25c878929ac8b5540477a1" ], "index": "pypi", - "version": "==5.5.5" + "version": "==6.3.1" }, "ipython": { "hashes": [ - "sha256:9bc24a99f5d19721fb8a2d1408908e9c0520a17fff2233ffe82620847f17f1b6", - "sha256:d513e93327cf8657d6467c81f1f894adc125334ffe0e4ddd1abbb1c78d828703" + "sha256:58b55ebfdfa260dad10d509702dc2857cb25ad82609506b070cf2d7b7df5af13", + "sha256:75b5e060a3417cf64f138e0bb78e58512742c57dc29db5a5058a2b1f0c10df02" ], "index": "pypi", - "version": "==7.24.1" + "version": "==7.27.0" }, "ipython-genutils": { "hashes": [ @@ -1699,18 +1807,18 @@ }, "ipywidgets": { "hashes": [ - "sha256:9f1a43e620530f9e570e4a493677d25f08310118d315b00e25a18f12913c41f0", - "sha256:e6513cfdaf5878de30f32d57f6dc2474da395a2a2991b94d487406c0ab7f55ca" + "sha256:028bf014a0b1d77cb676fe163115f145aacdde0bb9a51c4166940e5b62a7d1d0", + "sha256:3ffd1baa741eb631e7a3a69d4df290de074ef697e0ef3176e33361b44cd91711" ], - "version": "==7.6.3" + "version": "==7.6.4" }, "isort": { "hashes": [ - "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", - "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" + "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899", + "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2" ], - "markers": "python_version < '4' and python_full_version >= '3.6.1'", - "version": "==5.9.1" + "markers": "python_full_version >= '3.6.1' and python_version < '4.0'", + "version": "==5.9.3" }, "itsdangerous": { "hashes": [ @@ -1741,7 +1849,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.0" }, "joblib": { @@ -1777,11 +1885,11 @@ }, "jupyter-client": { "hashes": [ - "sha256:c4bca1d0846186ca8be97f4d2fa6d2bae889cce4892a167ffa1ba6bd1f73e782", - "sha256:e053a2c44b6fa597feebe2b3ecb5eea3e03d1d91cc94351a52931ee1426aecfc" + "sha256:0c6cabd07e003a2e9692394bf1ae794188ad17d2e250ed747232d7a473aa772c", + "sha256:37a30c13d3655b819add61c830594090af7fca40cd2d74f41cad9e2e12118501" ], - "markers": "python_version >= '3.5'", - "version": "==6.1.12" + "markers": "python_full_version >= '3.6.1'", + "version": "==7.0.2" }, "jupyter-console": { "hashes": [ @@ -1808,11 +1916,11 @@ }, "jupyterlab-widgets": { "hashes": [ - "sha256:5c1a29a84d3069208cb506b10609175b249b6486d6b1cbae8fcde2a11584fb78", - "sha256:caeaf3e6103180e654e7d8d2b81b7d645e59e432487c1d35a41d6d3ee56b3fef" + "sha256:841925a349bd9a9197c5506bd5461a321b09e6659a9b179a0096b561a92898c3", + "sha256:f94fb7fa1ddc8668e3f98d67a97cabe322e8d04b78b9eb988c7fde415d7a02df" ], "markers": "python_version >= '3.6'", - "version": "==1.0.0" + "version": "==1.0.1" }, "keras-applications": { "hashes": [ @@ -1824,41 +1932,53 @@ }, "kiwisolver": { "hashes": [ - "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d", - "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31", - "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9", - "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0", - "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72", - "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3", - "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6", - "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e", - "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000", - "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3", - "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18", - "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21", - "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621", - "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b", - "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc", - "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131", - "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882", - "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454", - "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248", - "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de", - "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598", - "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54", - "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278", - "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6", - "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81", - "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030", - "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8", - "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689", - "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4", - "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0", - "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05", - "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9" + "sha256:0007840186bacfaa0aba4466d5890334ea5938e0bb7e28078a0eb0e63b5b59d5", + "sha256:19554bd8d54cf41139f376753af1a644b63c9ca93f8f72009d50a2080f870f77", + "sha256:1d45d1c74f88b9f41062716c727f78f2a59a5476ecbe74956fafb423c5c87a76", + "sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6", + "sha256:2210f28778c7d2ee13f3c2a20a3a22db889e75f4ec13a21072eabb5693801e84", + "sha256:22521219ca739654a296eea6d4367703558fba16f98688bd8ce65abff36eaa84", + "sha256:25405f88a37c5f5bcba01c6e350086d65e7465fd1caaf986333d2a045045a223", + "sha256:2b65bd35f3e06a47b5c30ea99e0c2b88f72c6476eedaf8cfbc8e66adb5479dcf", + "sha256:2ddb500a2808c100e72c075cbb00bf32e62763c82b6a882d403f01a119e3f402", + "sha256:2f8f6c8f4f1cff93ca5058d6ec5f0efda922ecb3f4c5fb76181f327decff98b8", + "sha256:30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6", + "sha256:3dbb3cea20b4af4f49f84cffaf45dd5f88e8594d18568e0225e6ad9dec0e7967", + "sha256:4116ba9a58109ed5e4cb315bdcbff9838f3159d099ba5259c7c7fb77f8537492", + "sha256:44e6adf67577dbdfa2d9f06db9fbc5639afefdb5bf2b4dfec25c3a7fbc619536", + "sha256:5326ddfacbe51abf9469fe668944bc2e399181a2158cb5d45e1d40856b2a0589", + "sha256:70adc3658138bc77a36ce769f5f183169bc0a2906a4f61f09673f7181255ac9b", + "sha256:72be6ebb4e92520b9726d7146bc9c9b277513a57a38efcf66db0620aec0097e0", + "sha256:7843b1624d6ccca403a610d1277f7c28ad184c5aa88a1750c1a999754e65b439", + "sha256:7ba5a1041480c6e0a8b11a9544d53562abc2d19220bfa14133e0cdd9967e97af", + "sha256:80efd202108c3a4150e042b269f7c78643420cc232a0a771743bb96b742f838f", + "sha256:82f49c5a79d3839bc8f38cb5f4bfc87e15f04cbafa5fbd12fb32c941cb529cfb", + "sha256:83d2c9db5dfc537d0171e32de160461230eb14663299b7e6d18ca6dca21e4977", + "sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470", + "sha256:8dc3d842fa41a33fe83d9f5c66c0cc1f28756530cd89944b63b072281e852031", + "sha256:9661a04ca3c950a8ac8c47f53cbc0b530bce1b52f516a1e87b7736fec24bfff0", + "sha256:a498bcd005e8a3fedd0022bb30ee0ad92728154a8798b703f394484452550507", + "sha256:a7a4cf5bbdc861987a7745aed7a536c6405256853c94abc9f3287c3fa401b174", + "sha256:b5074fb09429f2b7bc82b6fb4be8645dcbac14e592128beeff5461dcde0af09f", + "sha256:b6a5431940f28b6de123de42f0eb47b84a073ee3c3345dc109ad550a3307dd28", + "sha256:ba677bcaff9429fd1bf01648ad0901cea56c0d068df383d5f5856d88221fe75b", + "sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560", + "sha256:bf7eb45d14fc036514c09554bf983f2a72323254912ed0c3c8e697b62c4c158f", + "sha256:c358721aebd40c243894298f685a19eb0491a5c3e0b923b9f887ef1193ddf829", + "sha256:c4550a359c5157aaf8507e6820d98682872b9100ce7607f8aa070b4b8af6c298", + "sha256:c6572c2dab23c86a14e82c245473d45b4c515314f1f859e92608dcafbd2f19b8", + "sha256:cba430db673c29376135e695c6e2501c44c256a81495da849e85d1793ee975ad", + "sha256:dedc71c8eb9c5096037766390172c34fb86ef048b8e8958b4e484b9e505d66bc", + "sha256:e6f5eb2f53fac7d408a45fbcdeda7224b1cfff64919d0f95473420a931347ae9", + "sha256:ec2eba188c1906b05b9b49ae55aae4efd8150c61ba450e6721f64620c50b59eb", + "sha256:ee040a7de8d295dbd261ef2d6d3192f13e2b08ec4a954de34a6fb8ff6422e24c", + "sha256:eedd3b59190885d1ebdf6c5e0ca56828beb1949b4dfe6e5d0256a461429ac386", + "sha256:f441422bb313ab25de7b3dbfd388e790eceb76ce01a18199ec4944b369017009", + "sha256:f8eb7b6716f5b50e9c06207a14172cf2de201e41912ebe732846c02c830455b9", + "sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c" ], - "markers": "python_version >= '3.6'", - "version": "==1.3.1" + "markers": "python_version >= '3.7'", + "version": "==1.3.2" }, "knack": { "hashes": [ @@ -1909,30 +2029,50 @@ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", + "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", + "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", + "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", + "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", + "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -1944,28 +2084,30 @@ }, "matplotlib": { "hashes": [ - "sha256:0bea5ec5c28d49020e5d7923c2725b837e60bc8be99d3164af410eb4b4c827da", - "sha256:1c1779f7ab7d8bdb7d4c605e6ffaa0614b3e80f1e3c8ccf7b9269a22dbc5986b", - "sha256:21b31057bbc5e75b08e70a43cefc4c0b2c2f1b1a850f4a0f7af044eb4163086c", - "sha256:32fa638cc10886885d1ca3d409d4473d6a22f7ceecd11322150961a70fab66dd", - "sha256:3a5c18dbd2c7c366da26a4ad1462fe3e03a577b39e3b503bbcf482b9cdac093c", - "sha256:5826f56055b9b1c80fef82e326097e34dc4af8c7249226b7dd63095a686177d1", - "sha256:6382bc6e2d7e481bcd977eb131c31dee96e0fb4f9177d15ec6fb976d3b9ace1a", - "sha256:6475d0209024a77f869163ec3657c47fed35d9b6ed8bccba8aa0f0099fbbdaa8", - "sha256:6a6a44f27aabe720ec4fd485061e8a35784c2b9ffa6363ad546316dfc9cea04e", - "sha256:7a58f3d8fe8fac3be522c79d921c9b86e090a59637cb88e3bc51298d7a2c862a", - "sha256:7ad19f3fb6145b9eb41c08e7cbb9f8e10b91291396bee21e9ce761bb78df63ec", - "sha256:85f191bb03cb1a7b04b5c2cca4792bef94df06ef473bc49e2818105671766fee", - "sha256:956c8849b134b4a343598305a3ca1bdd3094f01f5efc8afccdebeffe6b315247", - "sha256:a9d8cb5329df13e0cdaa14b3b43f47b5e593ec637f13f14db75bb16e46178b05", - "sha256:b1d5a2cedf5de05567c441b3a8c2651fbde56df08b82640e7f06c8cd91e201f6", - "sha256:b26535b9de85326e6958cdef720ecd10bcf74a3f4371bf9a7e5b2e659c17e153", - "sha256:c541ee5a3287efe066bbe358320853cf4916bc14c00c38f8f3d8d75275a405a9", - "sha256:d8d994cefdff9aaba45166eb3de4f5211adb4accac85cbf97137e98f26ea0219", - "sha256:df815378a754a7edd4559f8c51fc7064f779a74013644a7f5ac7a0c31f875866" - ], - "index": "pypi", - "version": "==3.4.2" + "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda", + "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684", + "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5", + "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919", + "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f", + "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913", + "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838", + "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235", + "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea", + "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3", + "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506", + "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330", + "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a", + "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b", + "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617", + "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5", + "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40", + "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa", + "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd", + "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318", + "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da" + ], + "index": "pypi", + "version": "==3.4.3" }, "matplotlib-inline": { "hashes": [ @@ -2006,10 +2148,10 @@ }, "msal": { "hashes": [ - "sha256:5cc93f09523c703d4e00a901cf719ade4faf2c3d14961ba52060ae78d5b25327", - "sha256:c7550f960916a9fb0bed6ebd23b73432415f81eeb3469264ab26c511d53b2652" + "sha256:0d389ef5db19ca8a30ae88fe05ba633a4623d3202d90f8dfcc81973dc28ee834", + "sha256:143c1f5dc6011d140027d34d06ee57ce46c950b5f105576d28609f365f964773" ], - "version": "==1.12.0" + "version": "==1.14.0" }, "msgpack-python": { "hashes": [ @@ -2099,19 +2241,19 @@ }, "nbclient": { "hashes": [ - "sha256:db17271330c68c8c88d46d72349e24c147bb6f34ec82d8481a8f025c4d26589c", - "sha256:e79437364a2376892b3f46bedbf9b444e5396cfb1bc366a472c37b48e9551500" + "sha256:6c8ad36a28edad4562580847f9f1636fe5316a51a323ed85a24a4ad37d4aefce", + "sha256:95a300c6fbe73721736cf13972a46d8d666f78794b832866ed7197a504269e11" ], "markers": "python_full_version >= '3.6.1'", - "version": "==0.5.3" + "version": "==0.5.4" }, "nbconvert": { "hashes": [ - "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d", - "sha256:cbbc13a86dfbd4d1b5dee106539de0795b4db156c894c2c5dc382062bbc29002" + "sha256:37cd92ff2ae6a268e62075ff8b16129e0be4939c4dfcee53dc77cc8a7e06c684", + "sha256:d22a8ff202644d31db254d24d52c3a96c82156623fcd7c7f987bba2612303ec9" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.7" + "markers": "python_version >= '3.7'", + "version": "==6.1.0" }, "nbformat": { "hashes": [ @@ -2131,11 +2273,11 @@ }, "networkx": { "hashes": [ - "sha256:0635858ed7e989f4c574c2328380b452df892ae85084144c73d8cd819f0c4e06", - "sha256:109cd585cac41297f71103c3c42ac6ef7379f29788eb54cb751be5a663bb235a" + "sha256:2306f1950ce772c5a59a57f5486d59bb9cab98497c45fc49cbc45ac0dec119bb", + "sha256:5fcb7004be69e8fbdf07dcb502efa5c77cadcaad6982164134eeb9721f826c2e" ], "index": "pypi", - "version": "==2.5.1" + "version": "==2.6.2" }, "nodeenv": { "hashes": [ @@ -2146,124 +2288,130 @@ }, "notebook": { "hashes": [ - "sha256:9c4625e2a2aa49d6eae4ce20cbc3d8976db19267e32d2a304880e0c10bf8aef9", - "sha256:f7f0a71a999c7967d9418272ae4c3378a220bd28330fbfb49860e46cf8a5838a" + "sha256:b50eafa8208d5db966efd1caa4076b4dfc51815e02a805b32ecd717e9e6cc071", + "sha256:e6b6dfed36b00cf950f63c0d42e947c101d4258aec21624de62b9e0c11ed5c0d" ], "markers": "python_version >= '3.6'", - "version": "==6.4.0" + "version": "==6.4.3" }, "numpy": { "hashes": [ - "sha256:1a784e8ff7ea2a32e393cc53eb0003eca1597c7ca628227e34ce34eb11645a0e", - "sha256:2ba579dde0563f47021dcd652253103d6fd66165b18011dce1a0609215b2791e", - "sha256:3537b967b350ad17633b35c2f4b1a1bbd258c018910b518c30b48c8e41272717", - "sha256:3c40e6b860220ed862e8097b8f81c9af6d7405b723f4a7af24a267b46f90e461", - "sha256:598fe100b2948465cf3ed64b1a326424b5e4be2670552066e17dfaa67246011d", - "sha256:620732f42259eb2c4642761bd324462a01cdd13dd111740ce3d344992dd8492f", - "sha256:709884863def34d72b183d074d8ba5cfe042bc3ff8898f1ffad0209161caaa99", - "sha256:75579acbadbf74e3afd1153da6177f846212ea2a0cc77de53523ae02c9256513", - "sha256:7c55407f739f0bfcec67d0df49103f9333edc870061358ac8a8c9e37ea02fcd2", - "sha256:a1f2fb2da242568af0271455b89aee0f71e4e032086ee2b4c5098945d0e11cf6", - "sha256:a290989cd671cd0605e9c91a70e6df660f73ae87484218e8285c6522d29f6e38", - "sha256:ac4fd578322842dbda8d968e3962e9f22e862b6ec6e3378e7415625915e2da4d", - "sha256:ad09f55cc95ed8d80d8ab2052f78cc21cb231764de73e229140d81ff49d8145e", - "sha256:b9205711e5440954f861ceeea8f1b415d7dd15214add2e878b4d1cf2bcb1a914", - "sha256:bba474a87496d96e61461f7306fba2ebba127bed7836212c360f144d1e72ac54", - "sha256:bebab3eaf0641bba26039fb0b2c5bf9b99407924b53b1ea86e03c32c64ef5aef", - "sha256:cc367c86eb87e5b7c9592935620f22d13b090c609f1b27e49600cd033b529f54", - "sha256:ccc6c650f8700ce1e3a77668bb7c43e45c20ac06ae00d22bdf6760b38958c883", - "sha256:cf680682ad0a3bef56dae200dbcbac2d57294a73e5b0f9864955e7dd7c2c2491", - "sha256:d2910d0a075caed95de1a605df00ee03b599de5419d0b95d55342e9a33ad1fb3", - "sha256:d5caa946a9f55511e76446e170bdad1d12d6b54e17a2afe7b189112ed4412bb8", - "sha256:d89b0dc7f005090e32bb4f9bf796e1dcca6b52243caf1803fdd2b748d8561f63", - "sha256:d95d16204cd51ff1a1c8d5f9958ce90ae190be81d348b514f9be39f878b8044a", - "sha256:e4d5a86a5257843a18fb1220c5f1c199532bc5d24e849ed4b0289fb59fbd4d8f", - "sha256:e58ddb53a7b4959932f5582ac455ff90dcb05fac3f8dcc8079498d43afbbde6c", - "sha256:e80fe25cba41c124d04c662f33f6364909b985f2eb5998aaa5ae4b9587242cce", - "sha256:eda2829af498946c59d8585a9fd74da3f810866e05f8df03a86f70079c7531dd", - "sha256:fd0a359c1c17f00cb37de2969984a74320970e0ceef4808c32e00773b06649d9" - ], - "index": "pypi", - "version": "==1.21.0" + "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf", + "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2", + "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6", + "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad", + "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc", + "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254", + "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0", + "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218", + "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13", + "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef", + "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737", + "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723", + "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3", + "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1", + "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d", + "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52", + "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3", + "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5", + "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f", + "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496", + "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f", + "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724", + "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931", + "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f", + "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7", + "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38", + "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557", + "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57", + "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310", + "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419" + ], + "index": "pypi", + "version": "==1.21.2" }, "opencv-python": { "hashes": [ - "sha256:0118a086fad8d77acdf46ac68df49d4167fbb85420f8bcf2615d7b74fc03aae0", - "sha256:050227e5728ea8316ec114aca8f43d56253cbb1c50983e3b136a988254a83118", - "sha256:08327a38564786bf73e387736f080e8ad4c110b394ca4af2ecec8277b305bf44", - "sha256:0a3aef70b7c53bbd22ade86a4318b8a2ad98d3c3ed3d0c315f18bf1a2d868709", - "sha256:10325c3fd571e33a11eb5f0e5d265d73baef22dbb34c977f28df7e22de47b0bc", - "sha256:2436b71346d1eed423577fac8cd3aa9c0832ea97452444dc7f856b2f09600dba", - "sha256:4b8814d3f0cf01e8b8624125f7dcfb095893abcc04083cb4968fa1629bc81161", - "sha256:4e6c2d8320168a4f76822fbb76df3b18688ac5e068d49ac38a4ce39af0f8e1a6", - "sha256:6b2573c6367ec0052b37e375d18638a885dd7a10a5ef8dd726b391969c227f23", - "sha256:6e2070e35f2aaca3d1259093c786d4e373004b36d89a94e81943247c6ed3d4e1", - "sha256:89a2b45429bf945988a17b0404431d9d8fdc9e04fb2450b56fa01f6f9477101d", - "sha256:8cf81f53ac5ad900ca443a8252c4e0bc1256f1c2cb2d8459df2ba1ac014dfa36", - "sha256:9680ab256ab31bdafd74f6cf55eb570e5629b5604d50fd69dd1bd2a8124f0611", - "sha256:a8020cc6145c6934192189058743a55189750df6dff894396edb8b35a380cc48", - "sha256:b3bef3f2a2ab3c201784d12ec6b5c9e61c920c15b6854d8d2f62fd019e3df846", - "sha256:b724a96eeb88842bd2371b1ffe2da73b6295063ba5c029aa34139d25b8315a3f", - "sha256:c446555cbbc4f5e809f9c15ac1b6200024032d9859f5ac5a2ca7669d09e4c91c", - "sha256:d9004e2cc90bb2862cdc1d062fac5163d3def55b200081d4520d3e90b4c7197b", - "sha256:ef3102b70aa59ab3fed69df30465c1b7587d681e963dfff5146de233c75df7ba", - "sha256:f12f39c1e5001e1c00df5873e3eee6f0232b7723a60b7ef438b1e23f1341df0e" - ], - "index": "pypi", - "version": "==4.5.2.54" + "sha256:05c5139d620e8d02f7ce0921796d55736fa19fa15e2ec00a388db2eb1ae1e9a1", + "sha256:085232718f28bddd265da480874c37db5c7354cb08f23f4a68a8639b16276a89", + "sha256:18a4a14015eee30d9cd514db8cdefbf594b1d5c234762d27abe512d62a333bc3", + "sha256:205a73adb29c37e42475645519e612e843a985475da993d10b4d5daa6afec36a", + "sha256:3c001d3feec7f3140f1fb78dfc52ca28122db8240826882d175a208a89d2731b", + "sha256:437f30e300725e1d1b3744dbfbc66a523a4744792b58f3dbe1e9140c8f4dfba5", + "sha256:5366fcd6eae4243add3c8c92142045850f1db8e464bcf0b75313e1596b2e3671", + "sha256:54c64e86a087841869901fd34462bb6bec01cd4652800fdf5d92fe7b0596c82f", + "sha256:6763729fcfee2a08e069aa1982c9a8c1abf55b9cdf2fb9640eda1d85bdece19a", + "sha256:68813b720b88e4951e84399b9a8a7b532d45a07a96ea8f539636242f862e32e0", + "sha256:7f41b97d84ac66bdf13cb4d9f4dad3e159525ba1e3f421e670c787ce536eb70a", + "sha256:831b92fe63ce18dd628f71104da7e60596658b75e2fa16b83aefa3eb10c115e2", + "sha256:881f3d85269500e0c7d72b140a6ebb5c14a089f8140fb9da7ce01f12a245858e", + "sha256:8852be06c0749fef0d9c58f532bbcb0570968c59e41cf56b90f5c92593c6e108", + "sha256:8b5bc61be7fc8565140b746288b370a4bfdb4edb9d680b66bb914e7690485db1", + "sha256:8d3282138f3a8646941089aae142684910ebe40776266448eab5f4bb609fc63f", + "sha256:9a78558b5ae848386edbb843c761e5fed5a8480be9af16274a5a78838529edeb", + "sha256:b42bbba9f5421865377c7960bd4f3dd881003b322a6bf46ed2302b89224d102b", + "sha256:c360cb76ad1ddbd5d2d3e730b42f2ff6e4be08ea6f4a6eefacca175d27467e8f", + "sha256:cdc3363c2911d7cfc6c9f55308c51c2841a7aecbf0bf5e791499d220ce89d880", + "sha256:e1f54736272830a1e895cedf7a4ee67737e31e966d380c82a81ef22515d043a3", + "sha256:e42c644a70d5c54f53a4b114dbd88b4eb83f42a9ca998f07bd5682f3f404efcc", + "sha256:f1bda4d144f5204e077ca4571453ebb2015e5748d5e0043386c92c2bbf7f52eb", + "sha256:f3ac2355217114a683f3f72a9c40a5890914a59c4a2df62e4083c66ff65c9cf9" + ], + "index": "pypi", + "version": "==4.5.3.56" }, "osmium": { "hashes": [ - "sha256:08136f5b1b7b522cf450e0da6aa9dd1f93cc47bd8216843c3ef9293cb52aef35", - "sha256:098b1cff3b815619e6173cee158bdd1d6c0a30690f51d1bdd3fffc55208e535a", - "sha256:1bdc6e6a4303f91a524126210e2b28066192259f9aa6a8bf2038c67e7f916e5f", - "sha256:26a89d8a22679aaf707a338b9648e4368a4a08b44b3c0dafac5c154eaf3dc2b5", - "sha256:7609b0b95b770fb4ba67aa96da3c8cf34cad8340935a4488963f541882ffede8", - "sha256:8126fdabbb8d64c454a8939c3a2b1262cef1f79ad467821f88b478c029e7db71", - "sha256:81df4393802ed7d93d296773b4e2d03a799b2fee6244f31f0f92528d53301428", - "sha256:8b8ea3e5dfe6c487edc04f930a78fb46c20f989284c860d62ca3daa416c3b3a7", - "sha256:b050a63226de2927a8b1127c95f28b3598e75c49a23089bf4f76e44f9ada27da", - "sha256:b55711e3aaef39e8261a29e651ffada1be8b53fc3fe0bed8fe241327132713b1", - "sha256:bdcbbb5e4f12de10aab9c209b4d436eafaf75bd30a91b81039b5dc3d22771465", - "sha256:cc6d3096a6581f1c71d01987362fe8fc6d8788169ed859555e3d679d88cfc59d", - "sha256:d08d5ccd8308318fc05cc0d1bd66ea8239ed3498f93973b31b3b37a7b7f783e0", - "sha256:d8878d85b56cf07a016e111cee5a5ee6400d97ff820ba3a94bfdc6380e127d05", - "sha256:e345f3da191367e7108f0a25d7913e21ce2e946c1cc672e33623aef829ed3926" - ], - "index": "pypi", - "version": "==3.1.3" + "sha256:36e2fec6ef00f0c13ec81cdb13b1d70a8d8a9a61094ea08b2a448f98bb62a6e2", + "sha256:3f3a79c895f86109b72f048bac50d36c9527b61a1701df74aa94495e1fc9ef46", + "sha256:5dcfc48205785ad56524e883e121866258d951ac6765cf181c213a37de3d5071", + "sha256:6a4a36328c85dcacda88e6d838bf5b6ecc04cacc427660a3fddb9bba701fe436", + "sha256:82102179fd3fefb61db6a70e96ed668d335fee29f6e45ef8d484b21e7daa02a4", + "sha256:a572773655e9fefcb51aacf90b2f599064f0f855c968ccf66f74c3f410e68665", + "sha256:ab691a1a4f077946f139658bf11c4b73f0c59606d747d30c6a7bd61841f67771", + "sha256:acbbf5ebf7e77e753d96431f4f4b0598663a2eec287120f0184b3d894cc54ddb", + "sha256:ae4e8e52ddf67db9f499fb25010933bf36e976ebd820600d0e67720ab90bccf1", + "sha256:b94b3c792a8f148e654f838382f4c5fb34eeea5dd13c2ebc34f5ce43c4744956", + "sha256:c23c6aa1903ddebbaff6e1969c37c46bfc00b845f51e35be2f52c1153b7f4fff", + "sha256:d285c91b1d87cdc25e44243f8685ccc09de26a6287e6cbd891cf4b0eb0b2fc40", + "sha256:e156143a6e0661a6f04608f6c73cb939bccb96f51e59bb46f59cfa0fd6a93303", + "sha256:eb6d42a550997bbe1de12d6a1747d51ddc26c7d3118c6d22159703efa57c9d5a" + ], + "index": "pypi", + "version": "==3.2.0" }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" + "markers": "python_version >= '3.6'", + "version": "==21.0" }, "pandas": { "hashes": [ - "sha256:0c34b89215f984a9e4956446e0a29330d720085efa08ea72022387ee37d8b373", - "sha256:0dbd125b0e44e5068163cbc9080a00db1756a5e36309329ae14fd259747f2300", - "sha256:1102d719038e134e648e7920672188a00375f3908f0383fd3b202fbb9d2c3a95", - "sha256:14abb8ea73fce8aebbb1fb44bec809163f1c55241bcc1db91c2c780e97265033", - "sha256:25fc8ef6c6beb51c9224284a1ad89dfb591832f23ceff78845f182de35c52356", - "sha256:38e7486410de23069392bdf1dc7297ae75d2d67531750753f3149c871cd1c6e3", - "sha256:4bfbf62b00460f78a8bc4407112965c5ab44324f34551e8e1f4cac271a07706c", - "sha256:78de96c1174bcfdbe8dece9c38c2d7994e407fd8bb62146bb46c61294bcc06ef", - "sha256:7b09293c7119ab22ab3f7f086f813ac2acbfa3bcaaaeb650f4cddfb5b9fa9be4", - "sha256:821d92466fcd2826656374a9b6fe4f2ec2ba5e370cce71d5a990577929d948df", - "sha256:9244fb0904512b074d8c6362fb13aac1da6c4db94372760ddb2565c620240264", - "sha256:94ca6ea3f46f44a979a38a4d5a70a88cee734f7248d7aeeed202e6b3ba485af1", - "sha256:a67227e17236442c6bc31c02cb713b5277b26eee204eac14b5aecba52492e3a3", - "sha256:c862cd72353921c102166784fc4db749f1c3b691dd017fc36d9df2c67a9afe4e", - "sha256:d9e6edddeac9a8e473391d2d2067bb3c9dc7ad79fd137af26a39ee425c2b4c78", - "sha256:e36515163829e0e95a6af10820f178dd8768102482c01872bff8ae592e508e58", - "sha256:f20e4b8a7909f5a0c0a9e745091e3ea18b45af9f73496a4d498688badbdac7ea", - "sha256:fc9215dd1dd836ff26b896654e66b2dfcf4bbb18aa4c1089a79bab527b665a90" + "sha256:0cd5776be891331a3e6b425b5abeab9596abea18435c5982191356f9b24ae731", + "sha256:1099e2a0cd3a01ec62cca183fc1555833a2d43764950ef8cb5948c8abfc51014", + "sha256:132def05e73d292c949b02e7ef873debb77acc44a8b119d215921046f0c3a91d", + "sha256:1738154049062156429a5cf2fd79a69c9f3fa4f231346a7ec6fd156cd1a9a621", + "sha256:34ced9ce5d5b17b556486da7256961b55b471d64a8990b56e67a84ebeb259416", + "sha256:53b17e4debba26b7446b1e4795c19f94f0c715e288e08145e44bdd2865e819b3", + "sha256:59a78d7066d1c921a77e3306aa0ebf6e55396c097d5dfcc4df8defe3dcecb735", + "sha256:66a95361b81b4ba04b699ecd2416b0591f40cd1e24c60a8bfe0d19009cfa575a", + "sha256:69e1b2f5811f46827722fd641fdaeedb26002bd1e504eacc7a8ec36bdc25393e", + "sha256:7996d311413379136baf0f3cf2a10e331697657c87ced3f17ac7c77f77fe34a3", + "sha256:89f40e5d21814192802421df809f948247d39ffe171e45fe2ab4abf7bd4279d8", + "sha256:9cce01f6d655b4add966fcd36c32c5d1fe84628e200626b3f5e2f40db2d16a0f", + "sha256:a56246de744baf646d1f3e050c4653d632bc9cd2e0605f41051fea59980e880a", + "sha256:ba7ceb8abc6dbdb1e34612d1173d61e4941f1a1eb7e6f703b2633134ae6a6c89", + "sha256:c9e8e0ce5284ebebe110efd652c164ed6eab77f5de4c3533abc756302ee77765", + "sha256:cbcb84d63867af3411fa063af3de64902665bb5b3d40b25b2059e40603594e87", + "sha256:f07a9745ca075ae73a5ce116f5e58f691c0dc9de0bff163527858459df5c176f", + "sha256:fa54dc1d3e5d004a09ab0b1751473698011ddf03e14f1f59b84ad9a6ac630975", + "sha256:fcb71b1935249de80e3a808227189eee381d4d74a31760ced2df21eedc92a8e3" ], "markers": "python_full_version >= '3.7.1'", - "version": "==1.2.5" + "version": "==1.3.2" }, "pandocfilters": { "hashes": [ @@ -2312,50 +2460,63 @@ }, "pillow": { "hashes": [ - "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5", - "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4", - "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9", - "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a", - "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9", - "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727", - "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120", - "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c", - "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2", - "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797", - "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b", - "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f", - "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef", - "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232", - "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb", - "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9", - "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812", - "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178", - "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291", - "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b", - "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5", - "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b", - "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1", - "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713", - "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4", - "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484", - "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c", - "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9", - "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388", - "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d", - "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602", - "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9", - "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e", - "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2" - ], - "index": "pypi", - "version": "==8.2.0" + "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc", + "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63", + "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d", + "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e", + "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd", + "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4", + "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77", + "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723", + "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba", + "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792", + "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae", + "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e", + "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367", + "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77", + "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856", + "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4", + "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de", + "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8", + "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a", + "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636", + "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab", + "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79", + "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d", + "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229", + "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf", + "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500", + "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04", + "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093", + "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844", + "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8", + "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82", + "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf", + "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83", + "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0", + "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c", + "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8", + "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37", + "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24", + "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14" + ], + "index": "pypi", + "version": "==8.3.1" }, "pkginfo": { "hashes": [ - "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4", - "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75" + "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779", + "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd" ], - "version": "==1.7.0" + "version": "==1.7.1" + }, + "platformdirs": { + "hashes": [ + "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", + "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.0" }, "portalocker": { "hashes": [ @@ -2373,11 +2534,11 @@ }, "pre-commit": { "hashes": [ - "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378", - "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4" + "sha256:7977a3103927932d4823178cbe4719ab55bb336f42a9f3bb2776cff99007a117", + "sha256:a22d12a02da4d8df314187dfe7a61bda6291d57992060522feed30c8cd658b68" ], "index": "pypi", - "version": "==2.13.0" + "version": "==2.14.1" }, "prometheus-client": { "hashes": [ @@ -2389,11 +2550,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f", - "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88" + "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c", + "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c" ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.19" + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.20" }, "psutil": { "hashes": [ @@ -2447,10 +2608,10 @@ }, "pycurl": { "hashes": [ - "sha256:8301518689daefa53726b59ded6b48f33751c383cf987b0ccfbbc4ed40281325" + "sha256:5bcef4d988b74b99653602101e17d8401338d596b9234d263c728a0c3df003e8" ], "index": "pypi", - "version": "==7.43.0.6" + "version": "==7.44.1" }, "pygame": { "hashes": [ @@ -2494,11 +2655,11 @@ }, "pygments": { "hashes": [ - "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", - "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" + "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", + "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" ], "index": "pypi", - "version": "==2.9.0" + "version": "==2.10.0" }, "pyjwt": { "hashes": [ @@ -2510,81 +2671,115 @@ }, "pylint": { "hashes": [ - "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8", - "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484" + "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1", + "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852" ], "index": "pypi", - "version": "==2.8.3" + "version": "==2.10.2" }, "pymongo": { "hashes": [ - "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e", - "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f", - "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee", - "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988", - "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171", - "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d", - "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04", - "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812", - "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca", - "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469", - "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd", - "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3", - "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858", - "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828", - "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194", - "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2", - "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680", - "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46", - "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8", - "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac", - "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3", - "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f", - "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58", - "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594", - "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466", - "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6", - "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043", - "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8", - "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70", - "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6", - "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b", - "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01", - "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9", - "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1", - "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e", - "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372", - "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982", - "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa", - "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549", - "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f", - "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702", - "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994", - "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61", - "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef", - "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad", - "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9", - "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3", - "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251", - "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db", - "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386", - "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0", - "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f", - "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a", - "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168", - "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092", - "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39", - "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6", - "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f", - "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73", - "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1", - "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce", - "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8", - "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0", - "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484" - ], - "index": "pypi", - "version": "==3.11.4" + "sha256:02dc0b0f48ed3cd06c13b7e31b066bf91e00dac5f8147b0a0a45f9009bfab857", + "sha256:053b4ebf91c7395d1fcd2ce6a9edff0024575b7b2de6781554a4114448a8adc9", + "sha256:070a4ef689c9438a999ec3830e69b208ff0d12251846e064d947f97d819d1d05", + "sha256:072ba7cb65c8aa4d5c5659bf6722ee85781c9d7816dc00679b8b6f3dff1ddafc", + "sha256:0b6055e0ef451ff73c93d0348d122a0750dddf323b9361de5835dac2f6cf7fc1", + "sha256:11f9e0cfc84ade088a38df2708d0b958bb76360181df1b2e1e1a41beaa57952b", + "sha256:18290649759f9db660972442aa606f845c368db9b08c4c73770f6da14113569b", + "sha256:186104a94d39b8412f8e3de385acd990a628346a4402d4f3a288a82b8660bd22", + "sha256:1970cfe2aec1bf74b40cf30c130ad10cd968941694630386db33e1d044c22a2e", + "sha256:19d4bd0fc29aa405bb1781456c9cfff9fceabb68543741eb17234952dbc2bbb0", + "sha256:1bab889ae7640eba739f67fcbf8eff252dddc60d4495e6ddd3a87cd9a95fdb52", + "sha256:1bc6fe7279ff40c6818db002bf5284aa03ec181ea1b1ceaeee33c289d412afa7", + "sha256:208debdcf76ed39ebf24f38509f50dc1c100e31e8653817fedb8e1f867850a13", + "sha256:2399a85b54f68008e483b2871f4a458b4c980469c7fe921595ede073e4844f1e", + "sha256:246ec420e4c8744fceb4e259f906211b9c198e1f345e6158dcd7cbad3737e11e", + "sha256:24f8aeec4d6b894a6128844e50ff423dd02462ee83addf503c598ee3a80ddf3d", + "sha256:255a35bf29185f44b412e31a927d9dcedda7c2c380127ecc4fbf2f61b72fa978", + "sha256:2dbfbbded947a83a3dffc2bd1ec4750c17e40904692186e2c55a3ad314ca0222", + "sha256:2e92aa32300a0b5e4175caec7769f482b292769807024a86d674b3f19b8e3755", + "sha256:316c1b8723afa9870567cd6dff35d440b2afeda53aa13da6c5ab85f98ed6f5ca", + "sha256:333bfad77aa9cd11711febfb75eed0bb537a1d022e1c252714dad38993590240", + "sha256:39dafa2eaf577d1969f289dc9a44501859a1897eb45bd589e93ce843fc610800", + "sha256:3ce83f17f641a62a4dfb0ba1b8a3c1ced7c842f511b5450d90c030c7828e3693", + "sha256:46d5ec90276f71af3a29917b30f2aec2315a2759b5f8d45b3b63a07ca8a070a3", + "sha256:48d5bc80ab0af6b60c4163c5617f5cd23f2f880d7600940870ea5055816af024", + "sha256:4ba0def4abef058c0e5101e05e3d5266e6fffb9795bbf8be0fe912a7361a0209", + "sha256:5af390fa9faf56c93252dab09ea57cd020c9123aa921b63a0ed51832fdb492e7", + "sha256:5e574664f1468872cd40f74e4811e22b1aa4de9399d6bcfdf1ee6ea94c017fcf", + "sha256:625befa3bc9b40746a749115cc6a15bf20b9bd7597ca55d646205b479a2c99c7", + "sha256:6261bee7c5abadeac7497f8f1c43e521da78dd13b0a2439f526a7b0fc3788824", + "sha256:657ad80de8ec9ed656f28844efc801a0802961e8c6a85038d97ff6f555ef4919", + "sha256:6b89dc51206e4971c5568c797991eaaef5dc2a6118d67165858ad11752dba055", + "sha256:6e66780f14c2efaf989cd3ac613b03ee6a8e3a0ba7b96c0bb14adca71a427e55", + "sha256:6fb3f85870ae26896bb44e67db94045f2ebf00c5d41e6b66cdcbb5afd644fc18", + "sha256:701e08457183da70ed96b35a6b43e6ba1df0b47c837b063cde39a1fbe1aeda81", + "sha256:70761fd3c576b027eec882b43ee0a8e5b22ff9c20cdf4d0400e104bc29e53e34", + "sha256:73b400fdc22de84bae0dbf1a22613928a41612ec0a3d6ed47caf7ad4d3d0f2ff", + "sha256:7412a36798966624dc4c57d64aa43c2d1100b348abd98daaac8e99e57d87e1d7", + "sha256:78ecb8d42f50d393af912bfb1fb1dcc9aabe9967973efb49ee577e8f1cea494c", + "sha256:7c6a9948916a7bbcc6d3a9f6fb75db1acb5546078023bfb3db6efabcd5a67527", + "sha256:7c72d08acdf573455b2b9d2b75b8237654841d63a48bc2327dc102c6ee89b75a", + "sha256:7d98ce3c42921bb91566121b658e0d9d59a9082a9bd6f473190607ff25ab637f", + "sha256:845a8b83798b2fb11b09928413cb32692866bfbc28830a433d9fa4c8c3720dd0", + "sha256:94d38eba4d1b5eb3e6bfece0651b855a35c44f32fd91f512ab4ba41b8c0d3e66", + "sha256:9a13661681d17e43009bb3e85e837aa1ec5feeea1e3654682a01b8821940f8b3", + "sha256:a0e5dff6701fa615f165306e642709e1c1550d5b237c5a7a6ea299886828bd50", + "sha256:a2239556ff7241584ce57be1facf25081669bb457a9e5cbe68cce4aae6567aa1", + "sha256:a325600c83e61e3c9cebc0c2b1c8c4140fa887f789085075e8f44c8ff2547eb9", + "sha256:a3566acfbcde46911c52810374ecc0354fdb841284a3efef6ff7105bc007e9a8", + "sha256:a634a4730ce0b0934ed75e45beba730968e12b4dafbb22f69b3b2f616d9e644e", + "sha256:a6d055f01b83b1a4df8bb0c61983d3bdffa913764488910af3620e5c2450bf83", + "sha256:a752ecd1a26000a6d67be7c9a2e93801994a8b3f866ac95b672fbc00225ca91a", + "sha256:a9ba2a63777027b06b116e1ea8248e66fd1bedc2c644f93124b81a91ddbf6d88", + "sha256:aaa038eafb7186a4abbb311fcf20724be9363645882bbce540bef4797e812a7a", + "sha256:af586e85144023686fb0af09c8cdf672484ea182f352e7ceead3d832de381e1b", + "sha256:b0a0cf39f589e52d801fdef418305562bc030cdf8929217463c8433c65fd5c2f", + "sha256:b1c4874331ab960429caca81acb9d2932170d66d6d6f87e65dc4507a85aca152", + "sha256:b3b5b3cbc3fdf4fcfa292529df2a85b5d9c7053913a739d3069af1e12e12219f", + "sha256:b542d56ed1b8d5cf3bb36326f814bd2fbe8812dfd2582b80a15689ea433c0e35", + "sha256:b6ea08758b6673610b3c5bdf47189286cf9c58b1077558706a2f6f8744922527", + "sha256:b754240daafecd9d5fce426b0fbaaed03f4ebb130745c8a4ae9231fffb8d75e5", + "sha256:b772bab31cbd9cb911e41e1a611ebc9497f9a32a7348e2747c38210f75c00f41", + "sha256:b88d1742159bc93a078733f9789f563cef26f5e370eba810476a71aa98e5fbc2", + "sha256:b8bf42d3b32f586f4c9e37541769993783a534ad35531ce8a4379f6fa664fba9", + "sha256:bc9ac81e73573516070d24ce15da91281922811f385645df32bd3c8a45ab4684", + "sha256:c188db6cf9e14dbbb42f5254292be96f05374a35e7dfa087cc2140f0ff4f10f6", + "sha256:c55782a55f4a013a78ac5b6ee4b8731a192dea7ab09f1b6b3044c96d5128edd4", + "sha256:c5cab230e7cabdae9ff23c12271231283efefb944c1b79bed79a91beb65ba547", + "sha256:cbf8672edeb7b7128c4a939274801f0e32bbf5159987815e3d1eace625264a46", + "sha256:cc2894fe91f31a513860238ede69fe47fada21f9e7ddfe73f7f9fef93a971e41", + "sha256:cda9e628b1315beec8341e8c04aac9a0b910650b05e0751e42e399d5694aeacb", + "sha256:ceae3ab9e11a27aaab42878f1d203600dfd24f0e43678b47298219a0f10c0d30", + "sha256:ced944dcdd561476deef7cb7bfd4987c69fffbfeff6d02ca4d5d4fd592d559b7", + "sha256:d04ca462cb99077e6c059e97c072957caf2918e6e4191e3161c01c439e0193de", + "sha256:d1131562ddc2ea8a446f66c2648d7dabec2b3816fc818528eb978a75a6d23b2e", + "sha256:d1740776b70367277323fafb76bcf09753a5cc9824f5d705bac22a34ff3668ea", + "sha256:d6e11ffd43184d529d6752d6dcb62b994f903038a17ea2168ef1910c96324d26", + "sha256:d73e10772152605f6648ba4410318594f1043bbfe36d2fadee7c4b8912eff7c5", + "sha256:da8288bc4a7807c6715416deed1c57d94d5e03e93537889e002bf985be503f1a", + "sha256:db93608a246da44d728842b8fa9e45aa9782db76955f634a707739a8d53ff544", + "sha256:dcd3d0009fbb6e454d729f8b22d0063bd9171c31a55e0f0271119bd4f2700023", + "sha256:dd1f49f949a658c4e8f81ed73f9aad25fcc7d4f62f767f591e749e30038c4e1d", + "sha256:dd6ff2192f34bd622883c745a56f492b1c9ccd44e14953e8051c33024a2947d5", + "sha256:e018a4921657c2d3f89c720b7b90b9182e277178a04a7e9542cc79d7d787ca51", + "sha256:e2b7670c0c8c6b501464150dd49dd0d6be6cb7f049e064124911cec5514fa19e", + "sha256:e7a33322e08021c37e89cae8ff06327503e8a1719e97c69f32c31cbf6c30d72c", + "sha256:e8a82e35d52ad6f867e88096a1a2b9bdc7ec4d5e65c7b4976a248bf2d1a32a93", + "sha256:e9faf8d4712d5ea301d74abfcf6dafe4b7f4af7936e91f283b0ad7bf69ed3e3a", + "sha256:ec5ca7c0007ce268048bbe0ffc6846ed1616cf3d8628b136e81d5e64ff3f52a2", + "sha256:eee42a1cc06565f6b21caa1f504ec15e07de7ebfd520ab57f8cb3308bc118e22", + "sha256:f2acf9bbcd514e901f82c4ca6926bbd2ae61716728f110b4343eb0a69612d018", + "sha256:f55c1ddcc1f6050b07d468ce594f55dbf6107b459e16f735d26818d7be1e9538", + "sha256:f6977a520bd96e097c8a37a8cbb9faa1ea99d21bf84190195056e25f688af73d", + "sha256:f94c7d22fb36b184734dded7345a04ec5f95130421c775b8b0c65044ef073f34", + "sha256:fa8957e9a1b202cb45e6b839c241cd986c897be1e722b81d2f32e9c6aeee80b0", + "sha256:fd3854148005c808c485c754a184c71116372263709958b42aefbef2e5dd373a", + "sha256:fe5872ce6f9627deac8314bdffd3862624227c3de4c17ef0cc78bbf0402999eb", + "sha256:ffbae429ba9e42d0582d3ac63fdb410338892468a2107d8ff68228ec9a39a0ed" + ], + "index": "pypi", + "version": "==3.12.0" }, "pymysql": { "hashes": [ @@ -2631,7 +2826,6 @@ "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==20.0.1" }, "pyparsing": { @@ -2639,7 +2833,7 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, "pyprof2calltree": { @@ -2676,25 +2870,54 @@ }, "pyrsistent": { "hashes": [ - "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" + "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2", + "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7", + "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea", + "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426", + "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710", + "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1", + "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396", + "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2", + "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680", + "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35", + "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427", + "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b", + "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b", + "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f", + "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef", + "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c", + "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4", + "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d", + "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78", + "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b", + "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72" ], - "markers": "python_version >= '3.5'", - "version": "==0.17.3" + "markers": "python_version >= '3.6'", + "version": "==0.18.0" + }, + "pysocks": { + "hashes": [ + "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", + "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", + "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" + ], + "version": "==1.7.1" }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.2" }, "python-engineio": { "hashes": [ - "sha256:4e97c1189c23923858f5bb6dc47cfcd915005383c3c039ff01c89f2c00d62077", - "sha256:c6c119c2039fcb6f64d260211ca92c0c61b2b888a28678732a961f2aaebcc848" + "sha256:d510329b6d8ed5662547862f58bc73659ae62defa66b66d745ba021de112fa62", + "sha256:f3ef9a2c048d08990f294c5f8991f6f162c3b12ecbd368baa0d90441de907d1c" ], - "version": "==4.2.0" + "markers": "python_version >= '3.6'", + "version": "==4.2.1" }, "python-logstash": { "hashes": [ @@ -2705,10 +2928,11 @@ }, "python-socketio": { "hashes": [ - "sha256:3dcc9785aaeef3a9eeb36c3818095662342744bdcdabd050fe697cdb826a1c2b", - "sha256:d74314fd4241342c8a55c4f66d5cfea8f1a8fffd157af216c67e1c3a649a2444" + "sha256:7ed57f6c024abdfeb9b25c74c0c00ffc18da47d903e8d72deecb87584370d1fc", + "sha256:ca807c9e1f168e96dea412d64dd834fb47c470d27fd83da0504aa4b248ba2544" ], - "version": "==5.3.0" + "markers": "python_version >= '3.6'", + "version": "==5.4.0" }, "pytz": { "hashes": [ @@ -2790,56 +3014,62 @@ }, "pyzmq": { "hashes": [ - "sha256:089b974ec04d663b8685ac90e86bfe0e4da9d911ff3cf52cb765ff22408b102d", - "sha256:0ea7f4237991b0f745a4432c63e888450840bf8cb6c48b93fb7d62864f455529", - "sha256:0f0f27eaab9ba7b92d73d71c51d1a04464a1da6097a252d007922103253d2313", - "sha256:12ffcf33db6ba7c0e5aaf901e65517f5e2b719367b80bcbfad692f546a297c7a", - "sha256:1389b615917d4196962a9b469e947ba862a8ec6f5094a47da5e7a8d404bc07a4", - "sha256:18dd2ca4540c476558099891c129e6f94109971d110b549db2a9775c817cedbd", - "sha256:24fb5bb641f0b2aa25fc3832f4b6fc62430f14a7d328229fe994b2bcdc07c93a", - "sha256:285514956c08c7830da9d94e01f5414661a987831bd9f95e4d89cc8aaae8da10", - "sha256:41049cff5265e9cd75606aa2c90a76b9c80b98d8fe70ee08cf4af3cedb113358", - "sha256:461ed80d741692d9457ab820b1cc057ba9c37c394e67b647b639f623c8b321f6", - "sha256:4b8fb1b3174b56fd020e4b10232b1764e52cf7f3babcfb460c5253bdc48adad0", - "sha256:4c4fe69c7dc0d13d4ae180ad650bb900854367f3349d3c16f0569f6c6447f698", - "sha256:4e9b9a2f6944acdaf57316436c1acdcb30b8df76726bcf570ad9342bc5001654", - "sha256:6355f81947e1fe6e7bb9e123aeb3067264391d3ebe8402709f824ef8673fa6f3", - "sha256:68be16107f41563b9f67d93dff1c9f5587e0f76aa8fd91dc04c83d813bcdab1f", - "sha256:68e2c4505992ab5b89f976f89a9135742b18d60068f761bef994a6805f1cae0c", - "sha256:7040d6dd85ea65703904d023d7f57fab793d7ffee9ba9e14f3b897f34ff2415d", - "sha256:734ea6565c71fc2d03d5b8c7d0d7519c96bb5567e0396da1b563c24a4ac66f0c", - "sha256:9ee48413a2d3cd867fd836737b4c89c24cea1150a37f4856d82d20293fa7519f", - "sha256:a1c77796f395804d6002ff56a6a8168c1f98579896897ad7e35665a9b4a9eec5", - "sha256:b2f707b52e09098a7770503e39294ca6e22ae5138ffa1dd36248b6436d23d78e", - "sha256:bf80b2cec42d96117248b99d3c86e263a00469c840a778e6cb52d916f4fdf82c", - "sha256:c4674004ed64685a38bee222cd75afa769424ec603f9329f0dd4777138337f48", - "sha256:c6a81c9e6754465d09a87e3acd74d9bb1f0039b2d785c6899622f0afdb41d760", - "sha256:c6d0c32532a0519997e1ded767e184ebb8543bdb351f8eff8570bd461e874efc", - "sha256:c8fff75af4c7af92dce9f81fa2a83ed009c3e1f33ee8b5222db2ef80b94e242e", - "sha256:cb9f9fe1305ef69b65794655fd89b2209b11bff3e837de981820a8aa051ef914", - "sha256:d3ecfee2ee8d91ab2e08d2d8e89302c729b244e302bbc39c5b5dde42306ff003", - "sha256:d5e5be93e1714a59a535bbbc086b9e4fd2448c7547c5288548f6fd86353cad9e", - "sha256:de5806be66c9108e4dcdaced084e8ceae14100aa559e2d57b4f0cceb98c462de", - "sha256:f49755684a963731479ff3035d45a8185545b4c9f662d368bd349c419839886d", - "sha256:fc712a90401bcbf3fa25747f189d6dcfccbecc32712701cad25c6355589dac57" - ], - "index": "pypi", - "version": "==22.1.0" + "sha256:021e22a8c58ab294bd4b96448a2ca4e716e1d76600192ff84c33d71edb1fbd37", + "sha256:0471d634c7fe48ff7d3849798da6c16afc71676dd890b5ae08eb1efe735c6fec", + "sha256:0d17bac19e934e9f547a8811b7c2a32651a7840f38086b924e2e3dcb2fae5c3a", + "sha256:200ac096cee5499964c90687306a7244b79ef891f773ed4cf15019fd1f3df330", + "sha256:240b83b3a8175b2f616f80092cbb019fcd5c18598f78ffc6aa0ae9034b300f14", + "sha256:246f27b88722cfa729bb04881e94484e40b085720d728c1b05133b3f331b0b7b", + "sha256:2534a036b777f957bd6b89b55fb2136775ca2659fb0f1c85036ba78d17d86fd5", + "sha256:262f470e7acde18b7217aac78d19d2e29ced91a5afbeb7d98521ebf26461aa7e", + "sha256:2dd3896b3c952cf6c8013deda53c1df16bf962f355b5503d23521e0f6403ae3d", + "sha256:31c5dfb6df5148789835128768c01bf6402eb753d06f524f12f6786caf96fb44", + "sha256:4842a8263cbaba6fce401bbe4e2b125321c401a01714e42624dabc554bfc2629", + "sha256:50d007d5702171bc810c1e74498fa2c7bc5b50f9750697f7fd2a3e71a25aad91", + "sha256:5933d1f4087de6e52906f72d92e1e4dcc630d371860b92c55d7f7a4b815a664c", + "sha256:620b0abb813958cb3ecb5144c177e26cde92fee6f43c4b9de6b329515532bf27", + "sha256:631f932fb1fa4b76f31adf976f8056519bc6208a3c24c184581c3dd5be15066e", + "sha256:66375a6094af72a6098ed4403b15b4db6bf00013c6febc1baa832e7abda827f4", + "sha256:6a5b4566f66d953601d0d47d4071897f550a265bafd52ebcad5ac7aad3838cbb", + "sha256:6d18c76676771fd891ca8e0e68da0bbfb88e30129835c0ade748016adb3b6242", + "sha256:6e9c030222893afa86881d7485d3e841969760a16004bd23e9a83cca28b42778", + "sha256:89200ab6ef9081c72a04ed84c52a50b60dcb0655375aeedb40689bc7c934715e", + "sha256:93705cb90baa9d6f75e8448861a1efd3329006f79095ab18846bd1eaa342f7c3", + "sha256:a649065413ba4eab92a783a7caa4de8ce14cf46ba8a2a09951426143f1298adb", + "sha256:ac4497e4b7d134ee53ce5532d9cc3b640d6e71806a55062984e0c99a2f88f465", + "sha256:b2c16d20bd0aef8e57bc9505fdd80ea0d6008020c3740accd96acf1b3d1b5347", + "sha256:b3f57bee62e36be5c97712de32237c5589caee0d1154c2ad01a888accfae20bc", + "sha256:b4428302c389fffc0c9c07a78cad5376636b9d096f332acfe66b321ae9ff2c63", + "sha256:b4a51c7d906dc263a0cc5590761e53e0a68f2c2fefe549cbef21c9ee5d2d98a4", + "sha256:b921758f8b5098faa85f341bbdd5e36d5339de5e9032ca2b07d8c8e7bec5069b", + "sha256:c1b6619ceb33a8907f1cb82ff8afc8a133e7a5f16df29528e919734718600426", + "sha256:c9cb0bd3a3cb7ccad3caa1d7b0d18ba71ed3a4a3610028e506a4084371d4d223", + "sha256:d60a407663b7c2af781ab7f49d94a3d379dd148bb69ea8d9dd5bc69adf18097c", + "sha256:da7f7f3bb08bcf59a6b60b4e53dd8f08bb00c9e61045319d825a906dbb3c8fb7", + "sha256:e66025b64c4724ba683d6d4a4e5ee23de12fe9ae683908f0c7f0f91b4a2fd94e", + "sha256:ed67df4eaa99a20d162d76655bda23160abdf8abf82a17f41dfd3962e608dbcc", + "sha256:f520e9fee5d7a2e09b051d924f85b977c6b4e224e56c0551c3c241bbeeb0ad8d", + "sha256:f5c84c5de9a773bbf8b22c51e28380999ea72e5e85b4db8edf5e69a7a0d4d9f9", + "sha256:ff345d48940c834168f81fa1d4724675099f148f1ab6369748c4d712ed71bf7c" + ], + "index": "pypi", + "version": "==22.2.1" }, "qtconsole": { "hashes": [ - "sha256:12c734494901658787339dea9bbd82f3dc0d5e394071377a1c77b4a0954d7d8b", - "sha256:3a2adecc43ff201a08972fb2179df22e7b3a08d71b9ed680f46ad1bfd4fb9132" + "sha256:73994105b0369bb99f4164df4a131010f3c7b33a7b5169c37366358d8744675b", + "sha256:bbc34bca14f65535afcb401bc74b752bac955e5313001ba640383f7e5857dc49" ], "markers": "python_version >= '3.6'", - "version": "==5.1.0" + "version": "==5.1.1" }, "qtpy": { "hashes": [ - "sha256:2db72c44b55d0fe1407be8fba35c838ad0d6d3bb81f23007886dc1fc0f459c8d", - "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea" + "sha256:3d20f010caa3b2c04835d6a2f66f8873b041bdaf7a76085c2a0d7890cdd65ea9", + "sha256:f683ce6cd825ba8248a798bf1dfa1a07aca387c88ae44fa5479537490aace7be" ], - "version": "==1.9.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.10.0" }, "redis": { "hashes": [ @@ -2851,11 +3081,11 @@ }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], "index": "pypi", - "version": "==2.25.1" + "version": "==2.26.0" }, "reverse-geocoder": { "hashes": [ @@ -2874,72 +3104,76 @@ }, "s3transfer": { "hashes": [ - "sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc", - "sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2" + "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c", + "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803" ], - "version": "==0.4.2" + "markers": "python_version >= '3.6'", + "version": "==0.5.0" }, "scikit-image": { "hashes": [ - "sha256:1256017c513e8e1b8b9da73e5fd1e605d0077bbbc8e5c8d6c2cab36400131c6c", - "sha256:1cd05c882ffb2a271a1f20b4afe937d63d55b8753c3d652f11495883a7800ebe", - "sha256:23f9178b21c752bfb4e4ea3a3fa0ff79bc5a401bc75ddb4661f2cebd1c2b0e24", - "sha256:2c058770c6ad6e0fe6c30f59970c9c65fa740ff014d121d8c341664cd792cf49", - "sha256:2eea42706a25ae6e0cebaf1914e2ab1c04061b1f3c9966d76025d58a2e9188fc", - "sha256:30447af3f5b7c9491f2d3db5bc275493d1b91bf1dd16b67e2fd79a6bb95d8ee9", - "sha256:3515b890e771f99bbe1051a0dcfe0fc477da961da933c34f89808a0f1eeb7dc2", - "sha256:5f602779258807d03e72c0a439cfb221f647e628be166fb3594397435f13c76b", - "sha256:76446e2402e64d7dba78eeae8aa86e92a0cafe5b1c9e6235bd8d067471ed2788", - "sha256:ae6659b3a8bd4bba7e9dcbfd0064e443b32c7054bf09174749db896730fcf42e", - "sha256:c700336a7f96109c74154090c5e693693a8e3fa09ed6156a5996cdc9a3bb1534", - "sha256:d5ad4a9b4c9797d4c4c48f45fa224c5ebff22b9b0af636c3ecb8addbb66c21e6", - "sha256:d746540cafe7776c6d05a0b40ec744bb8d33d1ddc51faba601d26c02593d8bcc", - "sha256:e972c628ad9ba52c298b032368e29af9bd5eeb81ce33bc2d9b039a81661c99c5", - "sha256:ec25e4110951d3a280421bb10dd510a082ba83d86e20d706294faf7899cdb3d5", - "sha256:fbb618ca911867bce45574c1639618cdfb5d94e207432b19bc19563d80d2f171" - ], - "index": "pypi", - "version": "==0.18.1" + "sha256:05b430b1f8e25f7ba4a55afc6bf592af00f0ec809ab1d80bdede8893e7c6af57", + "sha256:088bf793696a3d5f56cce27c75d415fa795d1db9336b7e8257a1764dc03c7c52", + "sha256:0bf23d3d182ba8fe4ef8a0935e843be1f6c99e7eebeb492ac07c305e8cbb1dcd", + "sha256:0bf3cdadc15db90f875bf59bdd0db080337e6353bb3d165c281f9af456d9d3f2", + "sha256:142d070a41f9dfed0c3661e0dd9ce3cdb59a20a5b5ab071f529577d6d3e1fb81", + "sha256:2f24eb3df859ba5b3fb66947fe2d7240653b38f307d574e25f1ae29cc2a212ee", + "sha256:3068af85682e90fda021070969dd2fce667f89a868c6aacb2fffbc5aa002e39e", + "sha256:3f3aa984638a6868171d176d26d6bd17b7b16a9fd505eaa97482f00a4310e3ff", + "sha256:7994866857a1bb388cf3ede4ca7a8fba0b89ef980d5d802ec25e30124a2a34db", + "sha256:7f27357adae9225df10fd152224d4c43978ae222f44bad7fedbfc2b81b985f9d", + "sha256:8394ad148685ed6ea8d84eb9c41e70cef1adda6c6d9a0ff8476c3126818a9340", + "sha256:9b60fe0bc6e770c126c625f8c2d8af3b20fea53dac845abdf474bef1bd526490", + "sha256:b29982f07231f60d6170f4c2c6f2fe88051a7b4194d775aefd81bfee107452b9", + "sha256:bfa6eb04dc0b8773043f9994eccd8c517d713cd0f9e960dcb6754e19c1abceb1", + "sha256:e2148846fae22e12b7a20d11d951adae57213dd097af5960407eb5c4421c0ab3", + "sha256:ec242ff35bd4bc531aaf00c6edb9f0f64ff36ff353bd6ecd8f1c77886ddc0a7a", + "sha256:ecae99f93f4c5e9b1bf34959f4dc596c41f2f6b2fc407d9d9ddf85aebd3137ca", + "sha256:ef92f42d8a0794c47df1eeb1937119b6686b523dc663ecc5ffdf3c91645719ac", + "sha256:f698fc715202eeccabb371190c19c2d6713696de4d07609a0fa0cae3acb0b3dd" + ], + "index": "pypi", + "version": "==0.18.3" }, "scipy": { "hashes": [ - "sha256:0572256c10ddd058e3d315c555538671ddb2737f27eb56189bfbc3483391403f", - "sha256:2e685fdbfa5b989af4338b29c408b9157ea6addec15d661104c437980c292be5", - "sha256:3595c8b64970c9e5a3f137fa1a9eb64da417e78fb7991d0b098b18a00b776d88", - "sha256:3e7df79b42c3015058a5554bfeab6fd4c9906c46560c9ddebb5c652840f3e182", - "sha256:4ef3d4df8af40cb6f4d4eaf7b02780109ebabeec334cda26a7899ec9d8de9176", - "sha256:53116abd5060a5b4a58489cf689bee259b779e6b7ecd4ce366e7147aa7c9626e", - "sha256:5a983d3cebc27294897951a494cebd78af2eae37facf75d9e4ad4f1f62229860", - "sha256:5eb8f054eebb351af7490bbb57465ba9662c4e16e1786655c6c7ed530eb9a74e", - "sha256:6130e22bf6ee506f7cddde7e0515296d97eb6c6c94f7ef5103c2b77aec5833a7", - "sha256:7f4b89c223bd09460b52b669e2e642cab73c28855b540e6ed029692546a86f8d", - "sha256:80df8af7039bce92fb4cd1ceb056258631b11b3c627384e2d29bb48d44c0cae7", - "sha256:821e75f5c16cd7b0ab0ffe7eb9917e5af7b48c25306b4777287de8d792a5f7f3", - "sha256:97ca4552ace1c313707058e774609af59644321e278c3a539322fab2fb09b943", - "sha256:998c5e6ea649489302de2c0bc026ed34284f531df89d2bdc8df3a0d44d165739", - "sha256:aef6e922aea6f2e6bbb539b413c85210a9ee32757535b84204ebd22723e69704", - "sha256:b77ee5e3a9507622e7f98b16122242a3903397f98d1fe3bc269d904a9025e2bc", - "sha256:bd4399d4388ca0239a4825e312b3e61b60f743dd6daf49e5870837716502a92a", - "sha256:c5d012cb82cc1dcfa72609abaabb4a4ed8113e3e8ac43464508a418c146be57d", - "sha256:e7b733d4d98e604109715e11f2ab9340eb45d53f803634ed730039070fc3bc11" - ], - "index": "pypi", - "version": "==1.7.0" + "sha256:2a0eeaab01258e0870c4022a6cd329aef3b7c6c2b606bd7cf7bb2ba9820ae561", + "sha256:3304bd5bc32e00954ac4b3f4cc382ca8824719bf348aacbec6347337d6b125fe", + "sha256:3f52470e0548cdb74fb8ddf06773ffdcca7c97550f903b1c51312ec19243a7f7", + "sha256:4729b41a4cdaf4cd011aeac816b532f990bdf97710cef59149d3e293115cf467", + "sha256:4ee952f39a4a4c7ba775a32b664b1f4b74818548b65f765987adc14bb78f5802", + "sha256:611f9cb459d0707dd8e4de0c96f86e93f61aac7475fcb225e9ec71fecdc5cebf", + "sha256:6b47d5fa7ea651054362561a28b1ccc8da9368a39514c1bbf6c0977a1c376764", + "sha256:71cfc96297617eab911e22216e8a8597703202e95636d9406df9af5c2ac99a2b", + "sha256:787749110a23502031fb1643c55a2236c99c6b989cca703ea2114d65e21728ef", + "sha256:90c07ba5f34f33299a428b0d4fa24c30d2ceba44d63f8385b2b05be460819fcb", + "sha256:a496b42dbcd04ea9924f5e92be63af3d8e0f43a274b769bfaca0a297327d54ee", + "sha256:bc61e3e5ff92d2f32bb263621d54a9cff5e3f7c420af3d1fa122ce2529de2bd9", + "sha256:c9951e3746b68974125e5e3445008a4163dd6d20ae0bbdae22b38cb8951dc11b", + "sha256:d1388fbac9dd591ea630da75c455f4cc637a7ca5ecb31a6b6cef430914749cde", + "sha256:d13f31457f2216e5705304d9f28e2826edf75487410a57aa99263fa4ffd792c2", + "sha256:d648aa85dd5074b1ed83008ae987c3fbb53d68af619fce1dee231f4d8bd40e2f", + "sha256:da9c6b336e540def0b7fd65603da8abeb306c5fc9a5f4238665cbbb5ff95cf58", + "sha256:e101bceeb9e65a90dadbc5ca31283403a2d4667b9c178db29109750568e8d112", + "sha256:efdd3825d54c58df2cc394366ca4b9166cf940a0ebddeb87b6c10053deb625ea" + ], + "index": "pypi", + "version": "==1.7.1" }, "seaborn": { "hashes": [ - "sha256:44e78eaed937c5a87fc7a892c329a7cc091060b67ebd1d0d306b446a74ba01ad", - "sha256:4e1cce9489449a1c6ff3c567f2113cdb41122f727e27a984950d004a88ef3c5c" + "sha256:85a6baa9b55f81a0623abddc4a26b334653ff4c6b18c418361de19dbba0ef283", + "sha256:cf45e9286d40826864be0e3c066f98536982baf701a7caa386511792d61ff4f6" ], "index": "pypi", - "version": "==0.11.1" + "version": "==0.11.2" }, "send2trash": { "hashes": [ - "sha256:17730aa0a33ab82ed6ca76be3bb25f0433d0014f1ccf63c979bab13a5b9db2b2", - "sha256:c20fee8c09378231b3907df9c215ec9766a84ee20053d99fbad854fe8bd42159" + "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d", + "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08" ], - "version": "==1.7.1" + "version": "==1.8.0" }, "shapely": { "hashes": [ @@ -2972,54 +3206,55 @@ }, "simplejson": { "hashes": [ - "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667", - "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3", - "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043", - "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb", - "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0", - "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d", - "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8", - "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f", - "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf", - "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748", - "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278", - "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4", - "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a", - "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8", - "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d", - "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971", - "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841", - "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f", - "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b", - "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45", - "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9", - "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6", - "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc", - "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956", - "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d", - "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746", - "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a", - "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0", - "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25", - "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625", - "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995", - "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46", - "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f", - "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a", - "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139", - "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f", - "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da", - "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34", - "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b", - "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94", - "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04", - "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b", - "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396", - "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06", - "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb" - ], - "index": "pypi", - "version": "==3.17.2" + "sha256:065230b9659ac38c8021fa512802562d122afb0cf8d4b89e257014dcddb5730a", + "sha256:07707ba69324eaf58f0c6f59d289acc3e0ed9ec528dae5b0d4219c0d6da27dc5", + "sha256:10defa88dd10a0a4763f16c1b5504e96ae6dc68953cfe5fc572b4a8fcaf9409b", + "sha256:140eb58809f24d843736edb8080b220417e22c82ac07a3dfa473f57e78216b5f", + "sha256:188f2c78a8ac1eb7a70a4b2b7b9ad11f52181044957bf981fb3e399c719e30ee", + "sha256:1c2688365743b0f190392e674af5e313ebe9d621813d15f9332e874b7c1f2d04", + "sha256:24e413bd845bd17d4d72063d64e053898543fb7abc81afeae13e5c43cef9c171", + "sha256:2b59acd09b02da97728d0bae8ff48876d7efcbbb08e569c55e2d0c2e018324f5", + "sha256:2df15814529a4625ea6f7b354a083609b3944c269b954ece0d0e7455872e1b2a", + "sha256:352c11582aa1e49a2f0f7f7d8fd5ec5311da890d1354287e83c63ab6af857cf5", + "sha256:36b08b886027eac67e7a0e822e3a5bf419429efad7612e69501669d6252a21f2", + "sha256:376023f51edaf7290332dacfb055bc00ce864cb013c0338d0dea48731f37e42f", + "sha256:3ba82f8b421886f4a2311c43fb98faaf36c581976192349fef2a89ed0fcdbdef", + "sha256:3d72aa9e73134dacd049a2d6f9bd219f7be9c004d03d52395831611d66cedb71", + "sha256:40ece8fa730d1a947bff792bcc7824bd02d3ce6105432798e9a04a360c8c07b0", + "sha256:417b7e119d66085dc45bdd563dcb2c575ee10a3b1c492dd3502a029448d4be1c", + "sha256:42b7c7264229860fe879be961877f7466d9f7173bd6427b3ba98144a031d49fb", + "sha256:457d9cfe7ece1571770381edccdad7fc255b12cd7b5b813219441146d4f47595", + "sha256:4a6943816e10028eeed512ea03be52b54ea83108b408d1049b999f58a760089b", + "sha256:5b94df70bd34a3b946c0eb272022fb0f8a9eb27cad76e7f313fedbee2ebe4317", + "sha256:5f5051a13e7d53430a990604b532c9124253c5f348857e2d5106d45fc8533860", + "sha256:5f7f53b1edd4b23fb112b89208377480c0bcee45d43a03ffacf30f3290e0ed85", + "sha256:5fe8c6dcb9e6f7066bdc07d3c410a2fca78c0d0b4e0e72510ffd20a60a20eb8e", + "sha256:71a54815ec0212b0cba23adc1b2a731bdd2df7b9e4432718b2ed20e8aaf7f01a", + "sha256:7332f7b06d42153255f7bfeb10266141c08d48cc1a022a35473c95238ff2aebc", + "sha256:78c6f0ed72b440ebe1892d273c1e5f91e55e6861bea611d3b904e673152a7a4c", + "sha256:7c9b30a2524ae6983b708f12741a31fbc2fb8d6fecd0b6c8584a62fd59f59e09", + "sha256:86fcffc06f1125cb443e2bed812805739d64ceb78597ac3c1b2d439471a09717", + "sha256:87572213965fd8a4fb7a97f837221e01d8fddcfb558363c671b8aa93477fb6a2", + "sha256:8e595de17178dd3bbeb2c5b8ea97536341c63b7278639cb8ee2681a84c0ef037", + "sha256:917f01db71d5e720b731effa3ff4a2c702a1b6dacad9bcdc580d86a018dfc3ca", + "sha256:91cfb43fb91ff6d1e4258be04eee84b51a4ef40a28d899679b9ea2556322fb50", + "sha256:aa86cfdeb118795875855589934013e32895715ec2d9e8eb7a59be3e7e07a7e1", + "sha256:ade09aa3c284d11f39640aebdcbb748e1996f0c60504f8c4a0c5a9fec821e67a", + "sha256:b2a5688606dffbe95e1347a05b77eb90489fe337edde888e23bbb7fd81b0d93b", + "sha256:b92fbc2bc549c5045c8233d954f3260ccf99e0f3ec9edfd2372b74b350917752", + "sha256:c2d5334d935af711f6d6dfeec2d34e071cdf73ec0df8e8bd35ac435b26d8da97", + "sha256:cb0afc3bad49eb89a579103616574a54b523856d20fc539a4f7a513a0a8ba4b2", + "sha256:ce66f730031b9b3683b2fc6ad4160a18db86557c004c3d490a29bf8d450d7ab9", + "sha256:e29b9cea4216ec130df85d8c36efb9985fda1c9039e4706fb30e0fb6a67602ff", + "sha256:e2cc4b68e59319e3de778325e34fbff487bfdb2225530e89995402989898d681", + "sha256:e90d2e219c3dce1500dda95f5b893c293c4d53c4e330c968afbd4e7a90ff4a5b", + "sha256:f13c48cc4363829bdfecc0c181b6ddf28008931de54908a492dc8ccd0066cd60", + "sha256:f550730d18edec4ff9d4252784b62adfe885d4542946b6d5a54c8a6521b56afd", + "sha256:fa843ee0d34c7193f5a816e79df8142faff851549cab31e84b526f04878ac778", + "sha256:fe1c33f78d2060719d52ea9459d97d7ae3a5b707ec02548575c4fbed1d1d345b" + ], + "index": "pypi", + "version": "==3.17.5" }, "six": { "hashes": [ @@ -3038,39 +3273,39 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0653d444d52f2b9a0cba1ea5cd0fc64e616ee3838ee86c1863781b2a8670fc0c", - "sha256:146af9e67d0f821b28779d602372e65d019db01532d8f7101e91202d447c14ec", - "sha256:2129d33b54da4d4771868a3639a07f461adc5887dbd9e0a80dbf560272245525", - "sha256:284b6df04bc30e886998e0fdbd700ef9ffb83bcb484ffc54d4084959240dce91", - "sha256:3690fc0fc671419debdae9b33df1434ac9253155fd76d0f66a01f7b459d56ee6", - "sha256:3a6afb7a55374329601c8fcad277f0a47793386255764431c8f6a231a6947ee9", - "sha256:45bbb935b305e381bcb542bf4d952232282ba76881e3458105e4733ba0976060", - "sha256:495cce8174c670f1d885e2259d710b0120888db2169ea14fc32d1f72e7950642", - "sha256:4cdc91bb3ee5b10e24ec59303131b791f3f82caa4dd8b36064d1918b0f4d0de4", - "sha256:4f375c52fed5f2ecd06be18756f121b3167a1fdc4543d877961fba04b1713214", - "sha256:56958dd833145f1aa75f8987dfe0cf6f149e93aa31967b7004d4eb9cb579fefc", - "sha256:5b827d3d1d982b38d2bab551edf9893c4734b5db9b852b28d3bc809ea7e179f6", - "sha256:5c62fff70348e3f8e4392540d31f3b8c251dc8eb830173692e5d61896d4309d6", - "sha256:5d4b2c23d20acf631456e645227cef014e7f84a111118d530cfa1d6053fd05a9", - "sha256:60cfe1fb59a34569816907cb25bb256c9490824679c46777377bcc01f6813a81", - "sha256:664c6cc84a5d2bad2a4a3984d146b6201b850ba0a7125b2fcd29ca06cddac4b1", - "sha256:70674f2ff315a74061da7af1225770578d23f4f6f74dd2e1964493abd8d804bc", - "sha256:77549e5ae996de50ad9f69f863c91daf04842b14233e133335b900b152bffb07", - "sha256:8924d552decf1a50d57dca4984ebd0778a55ca2cb1c0ef16df8c1fed405ff290", - "sha256:93394d68f02ecbf8c0a4355b6452793000ce0ee7aef79d2c85b491da25a88af7", - "sha256:9a62b06ad450386a2e671d0bcc5cd430690b77a5cd41c54ede4e4bf46d7a4978", - "sha256:c824d14b52000597dfcced0a4e480fd8664b09fed606e746a2c67fe5fbe8dfd9", - "sha256:cc474d0c40cef94d9b68980155d686d5ad43a9ca0834a8729052d3585f289d57", - "sha256:d25210f5f1a6b7b6b357d8fa199fc1d5be828c67cc1af517600c02e5b2727e4c", - "sha256:d76abceeb6f7c564fdbc304b1ce17ec59664ca7ed0fe6dbc6fc6a960c91370e3", - "sha256:e2aa39fdf5bff1c325a8648ac1957a0320c66763a3fa5f0f4a02457b2afcf372", - "sha256:eba098a4962e1ab0d446c814ae67e30da82c446b382cf718306cc90d4e2ad85f", - "sha256:ee3428f6100ff2b07e7ecec6357d865a4d604c801760094883587ecdbf8a3533", - "sha256:f3357948fa439eb5c7241a8856738605d7ab9d9f276ca5c5cc3220455a5f8e6c", - "sha256:ffb18eb56546aa66640fef831e5d0fe1a8dfbf11cdf5b00803826a01dbbbf3b1" - ], - "index": "pypi", - "version": "==1.4.18" + "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d", + "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090", + "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9", + "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f", + "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff", + "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0", + "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f", + "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389", + "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af", + "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a", + "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81", + "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0", + "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6", + "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f", + "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd", + "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4", + "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583", + "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787", + "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90", + "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262", + "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8", + "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00", + "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67", + "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4", + "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9", + "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1", + "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5", + "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7", + "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194", + "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29" + ], + "index": "pypi", + "version": "==1.4.23" }, "subprocess32": { "hashes": [ @@ -3090,19 +3325,19 @@ }, "tenacity": { "hashes": [ - "sha256:5bd16ef5d3b985647fe28dfa6f695d343aa26479a04e8792b9d3c8f49e361ae1", - "sha256:a0ce48587271515db7d3a5e700df9ae69cce98c4b57c23a4886da15243603dd8" + "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f", + "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a" ], "index": "pypi", - "version": "==7.0.0" + "version": "==8.0.1" }, "terminado": { "hashes": [ - "sha256:89d5dac2f4e2b39758a0ff9a3b643707c95a020a6df36e70583b88297cd59cbe", - "sha256:c89ace5bffd0e7268bdcf22526830eb787fd146ff9d78691a0528386f92b9ae3" + "sha256:962b402edbb480718054dc37027bada293972ecadfb587b89f01e2b8660a2132", + "sha256:9e0457334863be3e6060c487ad60e0995fa1df54f109c67b24ff49a4f2f34df5" ], "markers": "python_version >= '3.6'", - "version": "==0.10.1" + "version": "==0.11.1" }, "testpath": { "hashes": [ @@ -3114,18 +3349,18 @@ }, "tifffile": { "hashes": [ - "sha256:3201f5ba297b94328954724bd48dbf1b36ec14c4ee4cd5a2ec1aa3f83c486200", - "sha256:a2f83d82800a8d83cbd04340f9d65a6873a970874947a6b823b1b1238e84cba6" + "sha256:524f9f3a96ca91d12e5b5ddce80209d2b07769c1764ceecf505613668143f63c", + "sha256:8760e61e30106ea0dab9ec42a238d70a3ff55dde9c54456e7b748fe717cb782d" ], "markers": "python_version >= '3.7'", - "version": "==2021.6.14" + "version": "==2021.8.30" }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tornado": { @@ -3177,35 +3412,35 @@ }, "traitlets": { "hashes": [ - "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", - "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" + "sha256:03f172516916220b58c9f19d7f854734136dd9528103d04e9bf139a92c9f54c4", + "sha256:bd382d7ea181fbbcce157c133db9a829ce06edffe097bcf3ab945b435452b46d" ], "markers": "python_version >= '3.7'", - "version": "==5.0.5" + "version": "==5.1.0" }, "typing-extensions": { "hashes": [ - "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", - "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", - "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" ], - "version": "==3.10.0.0" + "version": "==3.10.0.2" }, "urllib3": { "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], "index": "pypi", - "version": "==1.26.5" + "version": "==1.26.6" }, "virtualenv": { "hashes": [ - "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467", - "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76" + "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0", + "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.4.7" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==20.7.2" }, "wcwidth": { "hashes": [ diff --git a/cereal b/cereal index 95f9fa186fc482..774d4e357ede4d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 95f9fa186fc48277acd4c2d7202765485a9fb1a8 +Subproject commit 774d4e357ede4de7e2f3d0dda3e1ddeb25271a7b diff --git a/installer/custom/install_gfortran.sh b/installer/custom/install_gfortran.sh new file mode 100644 index 00000000000000..297947627eb90a --- /dev/null +++ b/installer/custom/install_gfortran.sh @@ -0,0 +1,34 @@ +#!/data/data/com.termux/files/usr/bin/sh +# Get some needed tools. coreutils for mkdir command, gnugp for the signing key, and apt-transport-https to actually connect to the repo +apt-get update +apt-get --assume-yes upgrade +apt-get --assume-yes install coreutils gnupg + +# Make the sources.list.d directory +mkdir -p $PREFIX/etc/apt/sources.list.d + +# Write the needed source file +echo "deb https://its-pointless.github.io/files/24 termux extras" > $PREFIX/etc/apt/sources.list.d/pointless.list + +# Add signing key from https://its-pointless.github.io/pointless.gpg +curl -sL https://its-pointless.github.io/pointless.gpg | apt-key add - + +# Update apt +apt update + +# install gfortran +apt install gcc-11 -y +setupclang-gfort-11 + +# Elf cleaner is needed to remove a DT_ENTRY warning that prints out when gfortran -v is called to get +# its version number and this breaks the pip installation script when fortran is used. + +# Build elf cleaner +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +ELFCLEANERPATH=$SCRIPTPATH/termux-elf-cleaner/ +cd $ELFCLEANERPATH +make + +# Perform elf cleaner on gfortran +./termux-elf-cleaner $(which gfortran) + diff --git a/installer/custom/termux-elf-cleaner/COPYING b/installer/custom/termux-elf-cleaner/COPYING new file mode 100644 index 00000000000000..9cecc1d4669ee8 --- /dev/null +++ b/installer/custom/termux-elf-cleaner/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/installer/custom/termux-elf-cleaner/Makefile b/installer/custom/termux-elf-cleaner/Makefile new file mode 100644 index 00000000000000..71138bee18282c --- /dev/null +++ b/installer/custom/termux-elf-cleaner/Makefile @@ -0,0 +1,16 @@ +CXXFLAGS += -std=c++11 -Wall -Wextra -pedantic +PREFIX ?= /usr/local + +termux-elf-cleaner: termux-elf-cleaner.cpp + +clean: + rm -f termux-elf-cleaner + +install: termux-elf-cleaner + mkdir -p $(PREFIX)/bin + install termux-elf-cleaner $(PREFIX)/bin/termux-elf-cleaner + +uninstall: + rm -f $(PREFIX)/bin/termux-elf-cleaner + +.PHONY: clean install uninstall diff --git a/installer/custom/termux-elf-cleaner/README.md b/installer/custom/termux-elf-cleaner/README.md new file mode 100644 index 00000000000000..f72d056dca82cc --- /dev/null +++ b/installer/custom/termux-elf-cleaner/README.md @@ -0,0 +1,39 @@ +# termux-elf-cleaner +Utility for Android ELF files to remove unused parts that the linker warns about. + +## Description +When loading ELF files, the Android linker warns about unsupported dynamic section entries with warnings such as: + + WARNING: linker: /data/data/org.kost.nmap.android.networkmapper/bin/nmap: unused DT entry: type 0x6ffffffe arg 0x8a7d4 + WARNING: linker: /data/data/org.kost.nmap.android.networkmapper/bin/nmap: unused DT entry: type 0x6fffffff arg 0x3 + +This utility strips away the following dynamic section entries: + +- `DT_RPATH` - not supported in any Android version. +- `DT_RUNPATH` - supported from Android 7.0. +- `DT_VERDEF` - supported from Android 6.0. +- `DT_VERDEFNUM` - supported from Android 6.0. +- `DT_VERNEEDED` - supported from Android 6.0. +- `DT_VERNEEDNUM` - supported from Android 6.0. +- `DT_VERSYM` - supported from Android 6.0. + +It also removes the three ELF sections of type: + +- `SHT_GNU_verdef` +- `SHT_GNU_verneed` +- `SHT_GNU_versym` + +## Usage +```sh +usage: termux-elf-cleaner + +Processes ELF files to remove unsupported section types and +dynamic section entries which the Android linker warns about. +``` + +## Author +Fredrik Fornwall ([@fornwall](https://github.com/fornwall)). + +## License + +SPDX-License-Identifier: [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) diff --git a/installer/custom/termux-elf-cleaner/elf.h b/installer/custom/termux-elf-cleaner/elf.h new file mode 100644 index 00000000000000..447616ae29b2b8 --- /dev/null +++ b/installer/custom/termux-elf-cleaner/elf.h @@ -0,0 +1,211 @@ +#ifndef ELF_H_INCLUDED +#define ELF_H_INCLUDED + +#include + +/* Type for a 16-bit quantity. */ +typedef uint16_t Elf32_Half; +typedef uint16_t Elf64_Half; + +/* Types for signed and unsigned 32-bit quantities. */ +typedef uint32_t Elf32_Word; +typedef int32_t Elf32_Sword; +typedef uint32_t Elf64_Word; +typedef int32_t Elf64_Sword; + +/* Types for signed and unsigned 64-bit quantities. */ +typedef uint64_t Elf32_Xword; +typedef int64_t Elf32_Sxword; +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; + +/* Type of addresses. */ +typedef uint32_t Elf32_Addr; +typedef uint64_t Elf64_Addr; + +/* Type of file offsets. */ +typedef uint32_t Elf32_Off; +typedef uint64_t Elf64_Off; + +/* Type for section indices, which are 16-bit quantities. */ +typedef uint16_t Elf32_Section; +typedef uint16_t Elf64_Section; + +/* Type for version symbol information. */ +typedef Elf32_Half Elf32_Versym; +typedef Elf64_Half Elf64_Versym; + + +/* The ELF file header. This appears at the start of every ELF file. */ +typedef struct { + unsigned char e_ident[16]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table (usually follows elf header directly) file offset */ + Elf32_Off e_shoff; /* Section header table (at end of file) file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Elf32_Ehdr; +typedef struct { + unsigned char e_ident[16]; /* Magic number and other info */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Architecture */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size in bytes */ + Elf64_Half e_phentsize; /* Program header table entry size */ + Elf64_Half e_phnum; /* Program header table entry count */ + Elf64_Half e_shentsize; /* Section header table entry size */ + Elf64_Half e_shnum; /* Section header table entry count */ + Elf64_Half e_shstrndx; /* Section header string table index */ +} Elf64_Ehdr; + +/* Section header entry. The number of section entries in the file are determined by the "e_shnum" field of the ELF header.*/ +typedef struct { + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +} Elf32_Shdr; +typedef struct { + Elf64_Word sh_name; /* Section name (string tbl index) */ + Elf64_Word sh_type; /* Section type */ + Elf64_Xword sh_flags; /* Section flags */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Section size in bytes */ + Elf64_Word sh_link; /* Link to another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +} Elf64_Shdr; + +/* Legal values for sh_type (section type). */ +#define SHT_NULL 0 /* Section header table entry unused */ +#define SHT_PROGBITS 1 /* Program data */ +#define SHT_SYMTAB 2 /* Symbol table */ +#define SHT_STRTAB 3 /* String table */ +#define SHT_RELA 4 /* Relocation entries with addends */ +#define SHT_HASH 5 /* Symbol hash table */ +#define SHT_DYNAMIC 6 /* Dynamic linking information. Contains Elf32_Dyn/Elf64_Dyn entries. */ +#define SHT_NOTE 7 /* Notes */ +#define SHT_NOBITS 8 /* Program space with no data (bss) */ +#define SHT_REL 9 /* Relocation entries, no addends */ +#define SHT_SHLIB 10 /* Reserved */ +#define SHT_DYNSYM 11 /* Dynamic linker symbol table */ +#define SHT_INIT_ARRAY 14 /* Array of constructors */ +#define SHT_FINI_ARRAY 15 /* Array of destructors */ +#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ +#define SHT_GROUP 17 /* Section group */ +#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ +#define SHT_NUM 19 /* Number of defined types. */ +#define SHT_LOOS 0x60000000 /* Start OS-specific. */ +#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */ +#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */ +#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */ +#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */ +#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */ +#define SHT_SUNW_move 0x6ffffffa +#define SHT_SUNW_COMDAT 0x6ffffffb +#define SHT_SUNW_syminfo 0x6ffffffc +#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */ +#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */ +#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */ +#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */ +#define SHT_HIOS 0x6fffffff /* End OS-specific type */ +#define SHT_LOPROC 0x70000000 /* Start of processor-specific */ +#define SHT_HIPROC 0x7fffffff /* End of processor-specific */ +#define SHT_LOUSER 0x80000000 /* Start of application-specific */ +#define SHT_HIUSER 0x8fffffff /* End of application-specific */ + +/* Dynamic section entry. */ +typedef struct { + Elf32_Sword d_tag; /* Dynamic entry type */ + union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; /* Integer or address value */ +} Elf32_Dyn; +typedef struct { + Elf64_Sxword d_tag; /* Dynamic entry type */ + union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; /* Integer or address value */ +} Elf64_Dyn; + +/* Legal values for d_tag (dynamic entry type). */ +#define DT_NULL 0 /* Marks end of dynamic section */ +#define DT_NEEDED 1 /* Name of needed library */ +#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */ +#define DT_PLTGOT 3 /* Processor defined value */ +#define DT_HASH 4 /* Address of symbol hash table */ +#define DT_STRTAB 5 /* Address of string table */ +#define DT_SYMTAB 6 /* Address of symbol table */ +#define DT_RELA 7 /* Address of Rela relocs */ +#define DT_RELASZ 8 /* Total size of Rela relocs */ +#define DT_RELAENT 9 /* Size of one Rela reloc */ +#define DT_STRSZ 10 /* Size of string table */ +#define DT_SYMENT 11 /* Size of one symbol table entry */ +#define DT_INIT 12 /* Address of init function */ +#define DT_FINI 13 /* Address of termination function */ +#define DT_SONAME 14 /* Name of shared object */ +#define DT_RPATH 15 /* Library search path (deprecated) */ +#define DT_SYMBOLIC 16 /* Start symbol search here */ +#define DT_REL 17 /* Address of Rel relocs */ +#define DT_RELSZ 18 /* Total size of Rel relocs */ +#define DT_RELENT 19 /* Size of one Rel reloc */ +#define DT_PLTREL 20 /* Type of reloc in PLT */ +#define DT_DEBUG 21 /* For debugging; unspecified */ +#define DT_TEXTREL 22 /* Reloc might modify .text */ +#define DT_JMPREL 23 /* Address of PLT relocs */ +#define DT_BIND_NOW 24 /* Process relocations of object */ +#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ +#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ +#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ +#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ +#define DT_RUNPATH 29 /* Library search path */ +#define DT_FLAGS 30 /* Flags for the object being loaded */ +#define DT_ENCODING 32 /* Start of encoded range */ +#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ +#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ +#define DT_NUM 34 /* Number used */ +#define DT_LOOS 0x6000000d /* Start of OS-specific */ +#define DT_HIOS 0x6ffff000 /* End of OS-specific */ +#define DT_VERDEF 0x6ffffffc +#define DT_VERDEFNUM 0x6ffffffd +#define DT_LOPROC 0x70000000 /* Start of processor-specific */ +#define DT_HIPROC 0x7fffffff /* End of processor-specific */ + + +/* Symbol table entry. */ +typedef struct { + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +} Elf32_Sym; + +typedef struct { + Elf64_Word st_name; /* Symbol name (string tbl index) */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf64_Section st_shndx; /* Section index */ + Elf64_Addr st_value; /* Symbol value */ + Elf64_Xword st_size; /* Symbol size */ +} Elf64_Sym; + + +#endif diff --git a/installer/custom/termux-elf-cleaner/termux-elf-cleaner.cpp b/installer/custom/termux-elf-cleaner/termux-elf-cleaner.cpp new file mode 100644 index 00000000000000..97742768e9dc34 --- /dev/null +++ b/installer/custom/termux-elf-cleaner/termux-elf-cleaner.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __ANDROID_API__ +#define __ANDROID_API__ 21 +#endif + +// Include a local elf.h copy as not all platforms have it. +#include "elf.h" + +#define DT_GNU_HASH 0x6ffffef5 +#define DT_VERSYM 0x6ffffff0 +#define DT_FLAGS_1 0x6ffffffb +#define DT_VERNEEDED 0x6ffffffe +#define DT_VERNEEDNUM 0x6fffffff + +#define DF_1_NOW 0x00000001 /* Set RTLD_NOW for this object. */ +#define DF_1_GLOBAL 0x00000002 /* Set RTLD_GLOBAL for this object. */ +#define DF_1_NODELETE 0x00000008 /* Set RTLD_NODELETE for this object.*/ + +#if __ANDROID_API__ < 23 +#define SUPPORTED_DT_FLAGS_1 (DF_1_NOW | DF_1_GLOBAL) +#else +// The supported DT_FLAGS_1 values as of Android 6.0. +#define SUPPORTED_DT_FLAGS_1 (DF_1_NOW | DF_1_GLOBAL | DF_1_NODELETE) +#endif + +template +bool process_elf(uint8_t* bytes, size_t elf_file_size, char const* file_name) +{ + if (sizeof(ElfSectionHeaderType) > elf_file_size) { + fprintf(stderr, "termux-elf-cleaner: Elf header for '%s' would end at %zu but file size only %zu\n", file_name, sizeof(ElfSectionHeaderType), elf_file_size); + return false; + } + ElfHeaderType* elf_hdr = reinterpret_cast(bytes); + + size_t last_section_header_byte = elf_hdr->e_shoff + sizeof(ElfSectionHeaderType) * elf_hdr->e_shnum; + if (last_section_header_byte > elf_file_size) { + fprintf(stderr, "termux-elf-cleaner: Section header for '%s' would end at %zu but file size only %zu\n", file_name, last_section_header_byte, elf_file_size); + return false; + } + ElfSectionHeaderType* section_header_table = reinterpret_cast(bytes + elf_hdr->e_shoff); + + for (unsigned int i = 1; i < elf_hdr->e_shnum; i++) { + ElfSectionHeaderType* section_header_entry = section_header_table + i; + if (section_header_entry->sh_type == SHT_DYNAMIC) { + size_t const last_dynamic_section_byte = section_header_entry->sh_offset + section_header_entry->sh_size; + if (last_dynamic_section_byte > elf_file_size) { + fprintf(stderr, "termux-elf-cleaner: Dynamic section for '%s' would end at %zu but file size only %zu\n", file_name, last_dynamic_section_byte, elf_file_size); + return false; + } + + size_t const dynamic_section_entries = section_header_entry->sh_size / sizeof(ElfDynamicSectionEntryType); + ElfDynamicSectionEntryType* const dynamic_section = + reinterpret_cast(bytes + section_header_entry->sh_offset); + + unsigned int last_nonnull_entry_idx = 0; + for (unsigned int j = dynamic_section_entries - 1; j > 0; j--) { + ElfDynamicSectionEntryType* dynamic_section_entry = dynamic_section + j; + if (dynamic_section_entry->d_tag != DT_NULL) { + last_nonnull_entry_idx = j; + break; + } + } + + for (unsigned int j = 0; j < dynamic_section_entries; j++) { + ElfDynamicSectionEntryType* dynamic_section_entry = dynamic_section + j; + char const* removed_name = nullptr; + switch (dynamic_section_entry->d_tag) { +#if __ANDROID_API__ <= 21 + case DT_GNU_HASH: removed_name = "DT_GNU_HASH"; break; +#endif +#if __ANDROID_API__ < 23 + case DT_VERSYM: removed_name = "DT_VERSYM"; break; + case DT_VERNEEDED: removed_name = "DT_VERNEEDED"; break; + case DT_VERNEEDNUM: removed_name = "DT_VERNEEDNUM"; break; + case DT_VERDEF: removed_name = "DT_VERDEF"; break; + case DT_VERDEFNUM: removed_name = "DT_VERDEFNUM"; break; +#endif + case DT_RPATH: removed_name = "DT_RPATH"; break; +#if __ANDROID_API__ < 24 + case DT_RUNPATH: removed_name = "DT_RUNPATH"; break; +#endif + } + if (removed_name != nullptr) { + printf("termux-elf-cleaner: Removing the %s dynamic section entry from '%s'\n", removed_name, file_name); + // Tag the entry with DT_NULL and put it last: + dynamic_section_entry->d_tag = DT_NULL; + // Decrease j to process new entry index: + std::swap(dynamic_section[j--], dynamic_section[last_nonnull_entry_idx--]); + } else if (dynamic_section_entry->d_tag == DT_FLAGS_1) { + // Remove unsupported DF_1_* flags to avoid linker warnings. + decltype(dynamic_section_entry->d_un.d_val) orig_d_val = + dynamic_section_entry->d_un.d_val; + decltype(dynamic_section_entry->d_un.d_val) new_d_val = + (orig_d_val & SUPPORTED_DT_FLAGS_1); + if (new_d_val != orig_d_val) { + printf("termux-elf-cleaner: Replacing unsupported DF_1_* flags %llu with %llu in '%s'\n", + (unsigned long long) orig_d_val, + (unsigned long long) new_d_val, + file_name); + dynamic_section_entry->d_un.d_val = new_d_val; + } + } + } + } +#if __ANDROID_API__ < 23 + else if (section_header_entry->sh_type == SHT_GNU_verdef || + section_header_entry->sh_type == SHT_GNU_verneed || + section_header_entry->sh_type == SHT_GNU_versym) { + printf("termux-elf-cleaner: Removing version section from '%s'\n", file_name); + section_header_entry->sh_type = SHT_NULL; + } +#endif + } + return true; +} + + +int main(int argc, char const** argv) +{ + if (argc < 2 || (argc == 2 && strcmp(argv[1], "-h")==0)) { + fprintf(stderr, "usage: %s \n", argv[0]); + fprintf(stderr, "\nProcesses ELF files to remove unsupported section types\n" + "and dynamic section entries which the Android linker (API %d)\nwarns about.\n", + __ANDROID_API__); + return 1; + } + + for (int i = 1; i < argc; i++) { + char const* file_name = argv[i]; + int fd = open(file_name, O_RDWR); + if (fd < 0) { + char* error_message; + if (asprintf(&error_message, "open(\"%s\")", file_name) == -1) error_message = (char*) "open()"; + perror(error_message); + return 1; + } + + struct stat st; + if (fstat(fd, &st) < 0) { perror("fstat()"); return 1; } + + if (st.st_size < (long long) sizeof(Elf32_Ehdr)) { + close(fd); + continue; + } + + void* mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) { perror("mmap()"); return 1; } + + uint8_t* bytes = reinterpret_cast(mem); + if (!(bytes[0] == 0x7F && bytes[1] == 'E' && bytes[2] == 'L' && bytes[3] == 'F')) { + // Not the ELF magic number. + munmap(mem, st.st_size); + close(fd); + continue; + } + + if (bytes[/*EI_DATA*/5] != 1) { + fprintf(stderr, "termux-elf-cleaner: Not little endianness in '%s'\n", file_name); + munmap(mem, st.st_size); + close(fd); + continue; + } + + uint8_t const bit_value = bytes[/*EI_CLASS*/4]; + if (bit_value == 1) { + if (!process_elf(bytes, st.st_size, file_name)) return 1; + } else if (bit_value == 2) { + if (!process_elf(bytes, st.st_size, file_name)) return 1; + } else { + printf("termux-elf-cleaner: Incorrect bit value %d in '%s'\n", bit_value, file_name); + return 1; + } + + if (msync(mem, st.st_size, MS_SYNC) < 0) { perror("msync()"); return 1; } + + munmap(mem, st.st_size); + close(fd); + } + return 0; +} diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 730b383a528503..a66e1e740bbc1d 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -198,7 +198,7 @@ function launch { # start manager cd selfdrive/manager - ./build.py && ./manager.py + ./custom_dep.py && ./build.py && ./manager.py # if broken, keep on screen error while true; do sleep 1; done diff --git a/selfdrive/assets/img_hands_on_wheel.png b/selfdrive/assets/img_hands_on_wheel.png new file mode 100644 index 00000000000000..0b06b4c6207fb0 Binary files /dev/null and b/selfdrive/assets/img_hands_on_wheel.png differ diff --git a/selfdrive/assets/img_turn_left_icon.png b/selfdrive/assets/img_turn_left_icon.png new file mode 100644 index 00000000000000..1a5ef61d7bdff8 Binary files /dev/null and b/selfdrive/assets/img_turn_left_icon.png differ diff --git a/selfdrive/assets/img_turn_right_icon.png b/selfdrive/assets/img_turn_right_icon.png new file mode 100644 index 00000000000000..d53e78a4c7a7e3 Binary files /dev/null and b/selfdrive/assets/img_turn_right_icon.png differ diff --git a/selfdrive/assets/img_world_icon.png b/selfdrive/assets/img_world_icon.png new file mode 100644 index 00000000000000..fcfc9d95d97632 Binary files /dev/null and b/selfdrive/assets/img_world_icon.png differ diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 3f09ec94930c35..0e4eb360c7c808 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -88,7 +88,7 @@ void safety_setter_thread(Panda *panda) { cereal::CarParams::Reader car_params = cmsg.getRoot(); cereal::CarParams::SafetyModel safety_model = car_params.getSafetyModel(); - panda->set_unsafe_mode(0); // see safety_declarations.h for allowed values + panda->set_unsafe_mode(p.getBool("DisableDisengageOnGas") ? 1 : 0); // see safety_declarations.h for allowed values auto safety_param = car_params.getSafetyParam(); LOGW("setting safety model: %d with param %d", (int)safety_model, safety_param); diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index f74fe06e03420d..919c1bd39bfc93 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -5,6 +5,7 @@ from cereal import car from common.kalman.simple_kalman import KF1D from common.realtime import DT_CTRL +from common.params import Params from selfdrive.car import gen_empty_fingerprint from selfdrive.config import Conversions as CV from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX @@ -14,6 +15,8 @@ GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName +DISENGAGE_ON_GAS = not Params().get_bool("DisableDisengageOnGas") + # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS # 135 + 4 = 86 mph @@ -142,7 +145,7 @@ def create_common_events(self, cs_out, extra_gears=None, gas_resume_speed=-1, pc # Disable on rising edge of gas or brake. Also disable on brake when speed > 0. # Optionally allow to press gas at zero speed to resume. # e.g. Chrysler does not spam the resume button yet, so resuming with gas is handy. FIXME! - if (cs_out.gasPressed and (not self.CS.out.gasPressed) and cs_out.vEgo > gas_resume_speed) or \ + if (DISENGAGE_ON_GAS and cs_out.gasPressed and (not self.CS.out.gasPressed) and cs_out.vEgo > gas_resume_speed) or \ (cs_out.brakePressed and (not self.CS.out.brakePressed or not cs_out.standstill)): events.add(EventName.pedalPressed) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index a7790bdf978f7b..5aad81b5ffce9d 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -8,6 +8,13 @@ from selfdrive.config import Conversions as CV from selfdrive.car.toyota.values import CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR +_TRAFFIC_SINGAL_MAP = { + 1: "kph", + 36: "mph", + 65: "No overtake", + 66: "No overtake" +} + class CarState(CarStateBase): def __init__(self, CP): @@ -21,6 +28,7 @@ def __init__(self, CP): self.needs_angle_offset = True self.accurate_steer_angle_seen = False self.angle_offset = FirstOrderFilter(None, 60.0, DT_CTRL, initialized=False) + self._init_traffic_signals() self.low_speed_lockout = False self.acc_type = 1 @@ -117,8 +125,89 @@ def update(self, cp, cp_cam): ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSM"]["R_ADJACENT"] == 1) or (cp.vl["BSM"]["R_APPROACHING"] == 1) + self._update_traffic_signals(cp_cam) + ret.cruiseState.speedLimit = self._calculate_speed_limit() + return ret + def _init_traffic_signals(self): + self._tsgn1 = None + self._spdval1 = None + self._splsgn1 = None + self._tsgn2 = None + self._splsgn2 = None + self._tsgn3 = None + self._splsgn3 = None + self._tsgn4 = None + self._splsgn4 = None + + def _update_traffic_signals(self, cp_cam): + # Print out car signals for traffic signal detection + tsgn1 = cp_cam.vl["RSA1"]['TSGN1'] + spdval1 = cp_cam.vl["RSA1"]['SPDVAL1'] + splsgn1 = cp_cam.vl["RSA1"]['SPLSGN1'] + tsgn2 = cp_cam.vl["RSA1"]['TSGN2'] + splsgn2 = cp_cam.vl["RSA1"]['SPLSGN2'] + tsgn3 = cp_cam.vl["RSA2"]['TSGN3'] + splsgn3 = cp_cam.vl["RSA2"]['SPLSGN3'] + tsgn4 = cp_cam.vl["RSA2"]['TSGN4'] + splsgn4 = cp_cam.vl["RSA2"]['SPLSGN4'] + + has_changed = tsgn1 != self._tsgn1 \ + or spdval1 != self._spdval1 \ + or splsgn1 != self._splsgn1 \ + or tsgn2 != self._tsgn2 \ + or splsgn2 != self._splsgn2 \ + or tsgn3 != self._tsgn3 \ + or splsgn3 != self._splsgn3 \ + or tsgn4 != self._tsgn4 \ + or splsgn4 != self._splsgn4 + + self._tsgn1 = tsgn1 + self._spdval1 = spdval1 + self._splsgn1 = splsgn1 + self._tsgn2 = tsgn2 + self._splsgn2 = splsgn2 + self._tsgn3 = tsgn3 + self._splsgn3 = splsgn3 + self._tsgn4 = tsgn4 + self._splsgn4 = splsgn4 + + if not has_changed: + return + + print('---- TRAFFIC SIGNAL UPDATE -----') + if tsgn1 is not None and tsgn1 != 0: + print(f'TSGN1: {self._traffic_signal_description(tsgn1)}') + if spdval1 is not None and spdval1 != 0: + print(f'SPDVAL1: {spdval1}') + if splsgn1 is not None and splsgn1 != 0: + print(f'SPLSGN1: {splsgn1}') + if tsgn2 is not None and tsgn2 != 0: + print(f'TSGN2: {self._traffic_signal_description(tsgn2)}') + if splsgn2 is not None and splsgn2 != 0: + print(f'SPLSGN2: {splsgn2}') + if tsgn3 is not None and tsgn3 != 0: + print(f'TSGN3: {self._traffic_signal_description(tsgn3)}') + if splsgn3 is not None and splsgn3 != 0: + print(f'SPLSGN3: {splsgn3}') + if tsgn4 is not None and tsgn4 != 0: + print(f'TSGN4: {self._traffic_signal_description(tsgn4)}') + if splsgn4 is not None and splsgn4 != 0: + print(f'SPLSGN4: {splsgn4}') + print('------------------------') + + def _traffic_signal_description(self, tsgn): + desc = _TRAFFIC_SINGAL_MAP.get(int(tsgn)) + return f'{tsgn}: {desc}' if desc is not None else f'{tsgn}' + + def _calculate_speed_limit(self): + if self._tsgn1 == 1: + return self._spdval1 * CV.KPH_TO_MS + if self._tsgn1 == 36: + return self._spdval1 * CV.MPH_TO_MS + return 0 + @staticmethod def get_can_parser(CP): @@ -203,9 +292,24 @@ def get_cam_can_parser(CP): ("PRECOLLISION_ACTIVE", "PRE_COLLISION", 0), ] + # Include traffic singal signals. + signals += [ + ("TSGN1", "RSA1", 0), + ("SPDVAL1", "RSA1", 0), + ("SPLSGN1", "RSA1", 0), + ("TSGN2", "RSA1", 0), + ("SPLSGN2", "RSA1", 0), + ("TSGN3", "RSA2", 0), + ("SPLSGN3", "RSA2", 0), + ("TSGN4", "RSA2", 0), + ("SPLSGN4", "RSA2", 0), + ] + # use steering message to check if panda is connected to frc checks = [ ("STEERING_LKA", 42), + ("RSA1", 0), + ("RSA2", 0), ("PRE_COLLISION", 0), # TODO: figure out why freq is inconsistent ] diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index b261855b4e4d35..951c3a1bd72b43 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -137,10 +137,12 @@ std::unordered_map keys = { {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_PANDA_DISCONNECT | CLEAR_ON_IGNITION_ON}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisableRadar", PERSISTENT}, // WARNING: THIS DISABLES AEB + {"EnableDebugSnapshot", PERSISTENT}, {"EndToEndToggle", PERSISTENT}, {"CompletedTrainingVersion", PERSISTENT}, {"DisablePowerDown", PERSISTENT}, {"DisableUpdates", PERSISTENT}, + {"DisableDisengageOnGas", PERSISTENT}, {"EnableWideCamera", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, {"DongleId", PERSISTENT}, @@ -151,6 +153,7 @@ std::unordered_map keys = { {"GithubSshKeys", PERSISTENT}, {"GithubUsername", PERSISTENT}, {"GsmRoaming", PERSISTENT}, + {"HandsOnWheelMonitoring", PERSISTENT}, {"HardwareSerial", PERSISTENT}, {"HasAcceptedTerms", PERSISTENT}, {"IsDriverViewEnabled", CLEAR_ON_MANAGER_START}, @@ -182,11 +185,16 @@ std::unordered_map keys = { {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"ReleaseNotes", PERSISTENT}, {"ShouldDoUpdate", CLEAR_ON_MANAGER_START}, + {"ShowDebugUI", PERSISTENT}, + {"SpeedLimitControl", PERSISTENT}, + {"SpeedLimitPercOffset", PERSISTENT}, {"SubscriberInfo", PERSISTENT}, {"SshEnabled", PERSISTENT}, {"TermsVersion", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, + {"TurnSpeedControl", PERSISTENT}, + {"TurnVisionControl", PERSISTENT}, {"UpdateAvailable", CLEAR_ON_MANAGER_START}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"Version", PERSISTENT}, diff --git a/selfdrive/config.py b/selfdrive/config.py index 511f6126c4f778..7382cd9fba3d3d 100644 --- a/selfdrive/config.py +++ b/selfdrive/config.py @@ -15,6 +15,8 @@ class Conversions: RAD_TO_DEG = 1. / DEG_TO_RAD #Mass LB_TO_KG = 0.453592 + #Distance + MT_TO_FT = 3.28084 RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index e2737ee053daaf..95289bef54ba48 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -180,6 +180,7 @@ def update_events(self, CS): self.events.clear() self.events.add_from_msg(CS.events) self.events.add_from_msg(self.sm['driverMonitoringState'].events) + self.events.add_from_msg(self.sm['longitudinalPlan'].eventsDEPRECATED) # Handle startup event if self.startup_event is not None: diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 9f554c88b499cc..5159b198245ba3 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -19,6 +19,15 @@ MAX_CURVATURE_RATES = [0.03762194918267951, 0.003441203371932992] MAX_CURVATURE_RATE_SPEEDS = [0, 35] +# Constants for Limit controllers. +LIMIT_ADAPT_ACC = -1. # m/s^2 Ideal acceleration for the adapting (braking) phase when approaching speed limits. +LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide. +LIMIT_MAX_ACC = 1.0 # m/s^2 Maximum acelration allowed for limit controllers to provide while active. +LIMIT_MIN_SPEED = 8.33 # m/s, Minimum speed limit to provide as solution on limit controllers. +LIMIT_SPEED_OFFSET_TH = -1. # m/s Maximum offset between speed limit and current speed for adapting state. +LIMIT_MAX_MAP_DATA_AGE = 10. # s Maximum time to hold to map data, then consider it invalid inside limits controllers. + + class MPC_COST_LAT: PATH = 1.0 HEADING = 1.0 diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index f08803212ef627..224aa61dc1805a 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -236,6 +236,15 @@ def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> AlertStatus.normal, AlertSize.mid, Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .1) +def speed_limit_adjust_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: + speedLimit = sm['longitudinalPlan'].speedLimit + speed = round(speedLimit * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)) + message = f'Adjusting to {speed} {"km/h" if metric else "mph"} speed limit' + return Alert( + message, + "", + AlertStatus.normal, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., 4.) EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = { # ********** events with no alerts ********** @@ -459,6 +468,26 @@ def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), }, + EventName.preKeepHandsOnWheel: { + ET.WARNING: Alert( + "No hands on steering wheel detected", + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), + }, + + EventName.promptKeepHandsOnWheel: { + ET.WARNING: Alert( + "HANDS OFF STEERING WHEEL", + "Place hands on steering wheel", + AlertStatus.critical, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, .1, .1, .1, alert_rate=0.75), + }, + + EventName.keepHandsOnWheel: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Driver kept hands off sterring wheel"), + }, + EventName.manualRestart: { ET.WARNING: Alert( "TAKE CONTROL", @@ -541,6 +570,18 @@ def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Contact Support"), }, + EventName.speedLimitActive: { + ET.WARNING: Alert( + "Cruise set to speed limit", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., 0., 2.), + }, + + EventName.speedLimitValueChange: { + ET.WARNING: speed_limit_adjust_alert, + }, + # ********** events that affect controls state transitions ********** EventName.pcmEnable: { diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 0dab9503b92a36..0ef283c54a399b 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -61,6 +61,7 @@ def __init__(self, CP, use_lanelines=True, wide_camera=False): self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) + self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3)) def setup_mpc(self): self.libmpc = libmpc_py.libmpc @@ -169,8 +170,9 @@ def update(self, sm, CP): if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft: self.LP.lll_prob *= self.lane_change_ll_prob self.LP.rll_prob *= self.lane_change_ll_prob + self.d_path_w_lines_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) if self.use_lanelines: - d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) + d_path_xyz = self.d_path_w_lines_xyz self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) else: d_path_xyz = self.path_xyz @@ -233,4 +235,7 @@ def publish(self, sm, pm): plan_send.lateralPlan.laneChangeState = self.lane_change_state plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction + plan_send.lateralPlan.dPathWLinesX = [float(x) for x in self.d_path_w_lines_xyz[:, 0]] + plan_send.lateralPlan.dPathWLinesY = [float(y) for y in self.d_path_w_lines_xyz[:, 1]] + pm.send('lateralPlan', plan_send) diff --git a/selfdrive/controls/lib/lead_mpc.py b/selfdrive/controls/lib/lead_mpc.py index ee8ea98e7b82be..7a9968cd14e924 100644 --- a/selfdrive/controls/lib/lead_mpc.py +++ b/selfdrive/controls/lib/lead_mpc.py @@ -46,7 +46,7 @@ def set_cur_state(self, v, a): self.cur_state[0].v_ego = v_safe self.cur_state[0].a_ego = a_safe - def update(self, CS, radarstate, v_cruise): + def update(self, CS, radarstate, v_cruise, a_target, active): v_ego = CS.vEgo if self.lead_id == 0: lead = radarstate.leadOne diff --git a/selfdrive/controls/lib/limits_long_mpc.py b/selfdrive/controls/lib/limits_long_mpc.py new file mode 100644 index 00000000000000..51ac8c1193106c --- /dev/null +++ b/selfdrive/controls/lib/limits_long_mpc.py @@ -0,0 +1,79 @@ +import numpy as np +import math + +from selfdrive.swaglog import cloudlog +from common.realtime import sec_since_boot +from selfdrive.controls.lib.longitudinal_mpc_lib import libmpc_py +from selfdrive.controls.lib.drive_helpers import LON_MPC_N +from selfdrive.modeld.constants import T_IDXS + + +class LimitsLongitudinalMpc(): + def __init__(self): + self.reset_mpc() + self.last_cloudlog_t = 0.0 + self.ts = list(range(10)) + self.status = True + self.min_a = -1.2 + self.max_a = 1.2 + + def reset_mpc(self): + self.libmpc = libmpc_py.libmpc + self.libmpc.init(0.0, 10.0, 0.0, 50.0, 10000.0) + + self.mpc_solution = libmpc_py.ffi.new("log_t *") + self.cur_state = libmpc_py.ffi.new("state_t *") + + self.cur_state[0].x_ego = 0 + self.cur_state[0].v_ego = 0 + self.cur_state[0].a_ego = 0 + + self.v_solution = [0.0 for i in range(len(T_IDXS))] + self.a_solution = [0.0 for i in range(len(T_IDXS))] + self.j_solution = [0.0 for i in range(len(T_IDXS) - 1)] + + def set_accel_limits(self, min_a, max_a): + self.min_a = min_a + self.max_a = max_a + + def set_cur_state(self, v, a): + v_safe = max(v, 1e-2) + a_safe = min(a, self.max_a - 1e-2) + a_safe = max(a_safe, self.min_a + 1e-2) + self.cur_state[0].x_ego = 0.0 + self.cur_state[0].v_ego = v_safe + self.cur_state[0].a_ego = a_safe + + def update(self, carstate, model, v_cruise, a_target, active): + t = np.array(T_IDXS[:LON_MPC_N + 1]) + v_ego = self.cur_state[0].v_ego + + # If active, provide targets for a constant acceleration following a_target + # otherwise just target cruising at current speed + if active: + poss = v_ego * t + a_target * t**2 / 2. + speeds = v_ego + a_target * t + accels = a_target + np.ones(LON_MPC_N + 1) + else: + poss = v_ego * t + speeds = v_ego * np.ones(LON_MPC_N + 1) + accels = np.zeros(LON_MPC_N + 1) + + # Calculate mpc + self.libmpc.run_mpc(self.cur_state, self.mpc_solution, + list(poss), list(speeds), list(accels), + self.min_a, self.max_a) + + self.v_solution = list(self.mpc_solution.v_ego) + self.a_solution = list(self.mpc_solution.a_ego) + self.j_solution = list(self.mpc_solution.j_ego) + + # Reset if NaN or goes through lead car + nans = any(math.isnan(x) for x in self.mpc_solution[0].v_ego) + + t = sec_since_boot() + if nans: + if t > self.last_cloudlog_t + 5.0: + self.last_cloudlog_t = t + cloudlog.warning("Longitudinal model mpc reset - nans") + self.reset_mpc() diff --git a/selfdrive/controls/lib/long_mpc.py b/selfdrive/controls/lib/long_mpc.py index 4d6f3846d05271..e65583f86bde99 100644 --- a/selfdrive/controls/lib/long_mpc.py +++ b/selfdrive/controls/lib/long_mpc.py @@ -45,7 +45,7 @@ def set_cur_state(self, v, a): self.cur_state[0].v_ego = v_safe self.cur_state[0].a_ego = a_safe - def update(self, carstate, radarstate, v_cruise): + def update(self, carstate, radarstate, v_cruise, a_target, active): v_cruise_clipped = np.clip(v_cruise, self.cur_state[0].v_ego - 10., self.cur_state[0].v_ego + 10.0) poss = v_cruise_clipped * np.array(T_IDXS[:LON_MPC_N+1]) speeds = v_cruise_clipped * np.ones(LON_MPC_N+1) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index cab3e20aa3b0d4..69f194a0eb3328 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -13,7 +13,12 @@ from selfdrive.controls.lib.longcontrol import LongCtrlState from selfdrive.controls.lib.lead_mpc import LeadMpc from selfdrive.controls.lib.long_mpc import LongitudinalMpc +from selfdrive.controls.lib.limits_long_mpc import LimitsLongitudinalMpc from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N +from selfdrive.controls.lib.vision_turn_controller import VisionTurnController +from selfdrive.controls.lib.speed_limit_controller import SpeedLimitController, SpeedLimitResolver +from selfdrive.controls.lib.turn_speed_controller import TurnSpeedController +from selfdrive.controls.lib.events import Events from selfdrive.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s @@ -51,6 +56,7 @@ def __init__(self, CP): self.mpcs['lead0'] = LeadMpc(0) self.mpcs['lead1'] = LeadMpc(1) self.mpcs['cruise'] = LongitudinalMpc() + self.mpcs['custom'] = LimitsLongitudinalMpc() self.fcw = False self.fcw_checker = FCWChecker() @@ -65,6 +71,10 @@ def __init__(self, CP): self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) + self.vision_turn_controller = VisionTurnController(CP) + self.speed_limit_controller = SpeedLimitController() + self.events = Events() + self.turn_speed_controller = TurnSpeedController() def update(self, sm, CP): cur_time = sec_since_boot() @@ -90,23 +100,32 @@ def update(self, sm, CP): self.v_desired = self.alpha * self.v_desired + (1 - self.alpha) * v_ego self.v_desired = max(0.0, self.v_desired) + # Get acceleration and active solutions for custom long mpc. + a_mpc, active_mpc, c_source = self.mpc_solutions(enabled, self.v_desired, self.a_desired, v_cruise, sm) + accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) if force_slow_decel: # if required so, force a smooth deceleration accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL) accel_limits_turns[0] = min(accel_limits_turns[0], accel_limits_turns[1]) + # clip limits, cannot init MPC outside of bounds accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired) self.mpcs['cruise'].set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) + # ensure lower accel limit (for braking) is lower than target acc for custom controllers. + accel_limits = [min(accel_limits_turns[0], a_mpc['custom']), accel_limits_turns[1]] + self.mpcs['custom'].set_accel_limits(accel_limits[0], accel_limits[1]) + next_a = np.inf for key in self.mpcs: self.mpcs[key].set_cur_state(self.v_desired, self.a_desired) - self.mpcs[key].update(sm['carState'], sm['radarState'], v_cruise) - if self.mpcs[key].status and self.mpcs[key].a_solution[5] < next_a: # picks slowest solution from accel in ~0.2 seconds - self.longitudinalPlanSource = key + self.mpcs[key].update(sm['carState'], sm['radarState'], v_cruise, a_mpc[key], active_mpc[key]) + # picks slowest solution from accel in ~0.2 seconds + if self.mpcs[key].status and active_mpc[key] and self.mpcs[key].a_solution[5] < next_a: + self.longitudinalPlanSource = c_source if key == 'custom' else key self.v_desired_trajectory = self.mpcs[key].v_solution[:CONTROL_N] self.a_desired_trajectory = self.mpcs[key].a_solution[:CONTROL_N] self.j_desired_trajectory = self.mpcs[key].j_solution[:CONTROL_N] @@ -147,4 +166,56 @@ def publish(self, sm, pm): longitudinalPlan.longitudinalPlanSource = self.longitudinalPlanSource longitudinalPlan.fcw = self.fcw + longitudinalPlan.visionTurnControllerState = self.vision_turn_controller.state + longitudinalPlan.visionTurnSpeed = float(self.vision_turn_controller.v_turn) + + longitudinalPlan.speedLimitControlState = self.speed_limit_controller.state + longitudinalPlan.speedLimit = float(self.speed_limit_controller.speed_limit) + longitudinalPlan.speedLimitOffset = float(self.speed_limit_controller.speed_limit_offset) + longitudinalPlan.distToSpeedLimit = float(self.speed_limit_controller.distance) + longitudinalPlan.isMapSpeedLimit = bool(self.speed_limit_controller.source == SpeedLimitResolver.Source.map_data) + longitudinalPlan.eventsDEPRECATED = self.events.to_msg() + + longitudinalPlan.turnSpeedControlState = self.turn_speed_controller.state + longitudinalPlan.turnSpeed = float(self.turn_speed_controller.speed_limit) + longitudinalPlan.distToTurn = float(self.turn_speed_controller.distance) + longitudinalPlan.turnSign = int(self.turn_speed_controller.turn_sign) + pm.send('longitudinalPlan', plan_send) + + def mpc_solutions(self, enabled, v_ego, a_ego, v_cruise, sm): + # Update controllers + self.vision_turn_controller.update(enabled, v_ego, a_ego, v_cruise, sm) + self.events = Events() + self.speed_limit_controller.update(enabled, v_ego, a_ego, sm, v_cruise, self.events) + self.turn_speed_controller.update(enabled, v_ego, a_ego, sm) + + # Pick solution with lowest acceleration target. + a_solutions = {None: float("inf")} + + if self.vision_turn_controller.is_active: + a_solutions['turn'] = self.vision_turn_controller.a_target + + if self.speed_limit_controller.is_active: + a_solutions['limit'] = self.speed_limit_controller.a_target + + if self.turn_speed_controller.is_active: + a_solutions['turnlimit'] = self.turn_speed_controller.a_target + + source = min(a_solutions, key=a_solutions.get) + + a_sol = { + 'cruise': a_ego, # Irrelevant + 'lead0': a_ego, # Irrelevant + 'lead1': a_ego, # Irrelevant + 'custom': 0. if source is None else a_solutions[source], + } + + active_sol = { + 'cruise': True, # Irrelevant + 'lead0': True, # Irrelevant + 'lead1': True, # Irrelevant + 'custom': source is not None, + } + + return a_sol, active_sol, source diff --git a/selfdrive/controls/lib/speed_limit_controller.py b/selfdrive/controls/lib/speed_limit_controller.py new file mode 100644 index 00000000000000..679f5a7ae6f259 --- /dev/null +++ b/selfdrive/controls/lib/speed_limit_controller.py @@ -0,0 +1,377 @@ +import numpy as np +import time +from common.numpy_fast import interp +from enum import IntEnum +from cereal import log, car +from common.params import Params +from common.realtime import sec_since_boot +from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_ACC, LIMIT_MAX_ACC, LIMIT_SPEED_OFFSET_TH, \ + LIMIT_MAX_MAP_DATA_AGE, CONTROL_N +from selfdrive.controls.lib.events import Events +from selfdrive.modeld.constants import T_IDXS + + +_PARAMS_UPDATE_PERIOD = 2. # secs. Time between parameter updates. +_TEMP_INACTIVE_GUARD_PERIOD = 1. # secs. Time to wait after activation before considering temp deactivation signal. + +# Lookup table for speed limit percent offset depending on speed. +_LIMIT_PERC_OFFSET_V = [0.1, 0.05, 0.038] # 55, 105, 135 km/h +_LIMIT_PERC_OFFSET_BP = [13.9, 27.8, 36.1] # 50, 100, 130 km/h + +SpeedLimitControlState = log.LongitudinalPlan.SpeedLimitControlState +EventName = car.CarEvent.EventName + +_DEBUG = False + + +def _debug(msg): + if not _DEBUG: + return + print(msg) + + +def _description_for_state(speed_limit_control_state): + if speed_limit_control_state == SpeedLimitControlState.inactive: + return 'INACTIVE' + if speed_limit_control_state == SpeedLimitControlState.tempInactive: + return 'TEMP_INACTIVE' + if speed_limit_control_state == SpeedLimitControlState.adapting: + return 'ADAPTING' + if speed_limit_control_state == SpeedLimitControlState.active: + return 'ACTIVE' + + +class SpeedLimitResolver(): + class Source(IntEnum): + none = 0 + car_state = 1 + map_data = 2 + + class Policy(IntEnum): + car_state_only = 0 + map_data_only = 1 + car_state_priority = 2 + map_data_priority = 3 + combined = 4 + + def __init__(self, policy=Policy.map_data_priority): + self._limit_solutions = {} # Store for speed limit solutions from different sources + self._distance_solutions = {} # Store for distance to current speed limit start for different sources + self._v_ego = 0. + self._current_speed_limit = 0. + self._policy = policy + self._next_speed_limit_prev = 0. + self.speed_limit = 0. + self.distance = 0. + self.source = SpeedLimitResolver.Source.none + + def resolve(self, v_ego, current_speed_limit, sm): + self._v_ego = v_ego + self._current_speed_limit = current_speed_limit + self._sm = sm + + self._get_from_car_state() + self._get_from_map_data() + self._consolidate() + + return self.speed_limit, self.distance, self.source + + def _get_from_car_state(self): + self._limit_solutions[SpeedLimitResolver.Source.car_state] = self._sm['carState'].cruiseState.speedLimit + self._distance_solutions[SpeedLimitResolver.Source.car_state] = 0. + + def _get_from_map_data(self): + # Ignore if no live map data + sock = 'liveMapData' + if self._sm.logMonoTime[sock] is None: + self._limit_solutions[SpeedLimitResolver.Source.map_data] = 0. + self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0. + _debug('SL: No map data for speed limit') + return + + # Load limits from map_data + map_data = self._sm[sock] + speed_limit = map_data.speedLimit if map_data.speedLimitValid else 0. + next_speed_limit = map_data.speedLimitAhead if map_data.speedLimitAheadValid else 0. + + # Calculate the age of the gps fix. Ignore if too old. + gps_fix_age = time.time() - map_data.lastGpsTimestamp * 1e-3 + if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE: + self._limit_solutions[SpeedLimitResolver.Source.map_data] = 0. + self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0. + _debug(f'SL: Ignoring map data as is too old. Age: {gps_fix_age}') + return + + # When we have no ahead speed limit to consider or it is greater than current speed limit + # or car has stopped, then provide current value and reset tracking. + if next_speed_limit == 0. or self._v_ego <= 0. or next_speed_limit > self._current_speed_limit: + self._limit_solutions[SpeedLimitResolver.Source.map_data] = speed_limit + self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0. + self._next_speed_limit_prev = 0. + return + + # Calculate the actual distance to the speed limit ahead corrected by gps_fix_age + distance_since_fix = self._v_ego * gps_fix_age + distance_to_speed_limit_ahead = max(0., map_data.speedLimitAheadDistance - distance_since_fix) + + # When we have a next_speed_limit value that has not changed from a provided next speed limit value + # in previous resolutions, we keep providing it. + if next_speed_limit == self._next_speed_limit_prev: + self._limit_solutions[SpeedLimitResolver.Source.map_data] = next_speed_limit + self._distance_solutions[SpeedLimitResolver.Source.map_data] = distance_to_speed_limit_ahead + return + + # Reset tracking + self._next_speed_limit_prev = 0. + + # Calculated the time needed to adapt to the new limit and the corresponding distance. + adapt_time = (next_speed_limit - self._v_ego) / LIMIT_ADAPT_ACC + adapt_distance = self._v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time**2 + + # When we detect we are close enough, we provide the next limit value and track it. + if distance_to_speed_limit_ahead <= adapt_distance: + self._limit_solutions[SpeedLimitResolver.Source.map_data] = next_speed_limit + self._distance_solutions[SpeedLimitResolver.Source.map_data] = distance_to_speed_limit_ahead + self._next_speed_limit_prev = next_speed_limit + return + + # Otherwise we just provide the map data speed limit. + self.distance_to_map_speed_limit = 0. + self._limit_solutions[SpeedLimitResolver.Source.map_data] = speed_limit + self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0. + + def _consolidate(self): + limits = np.array([], dtype=float) + distances = np.array([], dtype=float) + sources = np.array([], dtype=int) + + if self._policy == SpeedLimitResolver.Policy.car_state_only or \ + self._policy == SpeedLimitResolver.Policy.car_state_priority or \ + self._policy == SpeedLimitResolver.Policy.combined: + limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.car_state]) + distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.car_state]) + sources = np.append(sources, SpeedLimitResolver.Source.car_state.value) + + if self._policy == SpeedLimitResolver.Policy.map_data_only or \ + self._policy == SpeedLimitResolver.Policy.map_data_priority or \ + self._policy == SpeedLimitResolver.Policy.combined: + limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.map_data]) + distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.map_data]) + sources = np.append(sources, SpeedLimitResolver.Source.map_data.value) + + if np.amax(limits) == 0.: + if self._policy == SpeedLimitResolver.Policy.car_state_priority: + limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.map_data]) + distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.map_data]) + sources = np.append(sources, SpeedLimitResolver.Source.map_data.value) + + elif self._policy == SpeedLimitResolver.Policy.map_data_priority: + limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.car_state]) + distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.car_state]) + sources = np.append(sources, SpeedLimitResolver.Source.car_state.value) + + # Get all non-zero values and set the minimum if any, otherwise 0. + mask = limits > 0. + limits = limits[mask] + distances = distances[mask] + sources = sources[mask] + + if len(limits) > 0: + min_idx = np.argmin(limits) + self.speed_limit = limits[min_idx] + self.distance = distances[min_idx] + self.source = SpeedLimitResolver.Source(sources[min_idx]) + else: + self.speed_limit = 0. + self.distance = 0. + self.source = SpeedLimitResolver.Source.none + + _debug(f'SL: *** Speed Limit set: {self.speed_limit}, distance: {self.distance}, source: {self.source}') + + +class SpeedLimitController(): + def __init__(self): + self._params = Params() + self._resolver = SpeedLimitResolver() + self._last_params_update = 0.0 + self._last_op_enabled_time = 0.0 + self._is_metric = self._params.get_bool("IsMetric") + self._is_enabled = self._params.get_bool("SpeedLimitControl") + self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset") + self._op_enabled = False + self._op_enabled_prev = False + self._v_ego = 0. + self._a_ego = 0. + self._v_offset = 0. + self._v_cruise_setpoint = 0. + self._v_cruise_setpoint_prev = 0. + self._v_cruise_setpoint_changed = False + self._speed_limit = 0. + self._speed_limit_prev = 0. + self._speed_limit_changed = False + self._distance = 0. + self._source = SpeedLimitResolver.Source.none + self._state = SpeedLimitControlState.inactive + self._state_prev = SpeedLimitControlState.inactive + self._gas_pressed = False + self._a_target = 0. + + @property + def a_target(self): + return self._a_target if self.is_active else self._a_ego + + @property + def state(self): + return self._state + + @state.setter + def state(self, value): + if value != self._state: + _debug(f'Speed Limit Controller state: {_description_for_state(value)}') + + if value == SpeedLimitControlState.tempInactive: + # Reset previous speed limit to current value as to prevent going out of tempInactive in + # a single cycle when the speed limit changes at the same time the user has temporarily deactivate it. + self._speed_limit_prev = self._speed_limit + + self._state = value + + @property + def is_active(self): + return self.state > SpeedLimitControlState.tempInactive + + @property + def speed_limit_offseted(self): + return self._speed_limit + self.speed_limit_offset + + @property + def speed_limit_offset(self): + if self._offset_enabled: + return interp(self._speed_limit, _LIMIT_PERC_OFFSET_BP, _LIMIT_PERC_OFFSET_V) * self._speed_limit + return 0. + + @property + def speed_limit(self): + return self._speed_limit + + @property + def distance(self): + return self._distance + + @property + def source(self): + return self._source + + def _update_params(self): + time = sec_since_boot() + if time > self._last_params_update + _PARAMS_UPDATE_PERIOD: + self._is_enabled = self._params.get_bool("SpeedLimitControl") + self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset") + _debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_enabled}') + self._last_params_update = time + + def _update_calculations(self): + # Update current velocity offset (error) + self._v_offset = self.speed_limit_offseted - self._v_ego + + # Track the time op becomes active to prevent going to tempInactive right away after + # op enabling since controlsd will change the cruise speed every time on enabling and this will + # cause a temp inactive transition if the controller is updated before controlsd sets actual cruise + # speed. + if not self._op_enabled_prev and self._op_enabled: + self._last_op_enabled_time = sec_since_boot() + + # Update change tracking variables + self._speed_limit_changed = self._speed_limit != self._speed_limit_prev + self._v_cruise_setpoint_changed = self._v_cruise_setpoint != self._v_cruise_setpoint_prev + self._speed_limit_prev = self._speed_limit + self._v_cruise_setpoint_prev = self._v_cruise_setpoint + self._op_enabled_prev = self._op_enabled + + def _state_transition(self): + self._state_prev = self._state + + # In any case, if op is disabled, or speed limit control is disabled + # or the reported speed limit is 0 or gas is pressed, deactivate. + if not self._op_enabled or not self._is_enabled or self._speed_limit == 0 or self._gas_pressed: + self.state = SpeedLimitControlState.inactive + return + + # In any case, we deactivate the speed limit controller temporarily if the user changes the cruise speed. + # Ignore if a minimum ammount of time has not passed since activation. This is to prevent temp inactivations + # due to controlsd logic changing cruise setpoint when going active. + if self._v_cruise_setpoint_changed and \ + sec_since_boot() > (self._last_op_enabled_time + _TEMP_INACTIVE_GUARD_PERIOD): + self.state = SpeedLimitControlState.tempInactive + return + + # inactive + if self.state == SpeedLimitControlState.inactive: + # If the limit speed offset is negative (i.e. reduce speed) and lower than threshold + # we go to adapting state to quickly reduce speed, otherwise we go directly to active + if self._v_offset < LIMIT_SPEED_OFFSET_TH: + self.state = SpeedLimitControlState.adapting + else: + self.state = SpeedLimitControlState.active + # tempInactive + elif self.state == SpeedLimitControlState.tempInactive: + # if speed limit changes, transition to inactive, + # proper active state will be set on next iteration. + if self._speed_limit_changed: + self.state = SpeedLimitControlState.inactive + # adapting + elif self.state == SpeedLimitControlState.adapting: + # Go to active once the speed offset is over threshold. + if self._v_offset >= LIMIT_SPEED_OFFSET_TH: + self.state = SpeedLimitControlState.active + # active + elif self.state == SpeedLimitControlState.active: + # Go to adapting if the speed offset goes below threshold. + if self._v_offset < LIMIT_SPEED_OFFSET_TH: + self.state = SpeedLimitControlState.adapting + + def _update_solution(self): + # inactive or tempInactive state + if self.state <= SpeedLimitControlState.tempInactive: + # Preserve current values + a_target = self._a_ego + # adapting + elif self.state == SpeedLimitControlState.adapting: + # When adapting we target to achieve the speed limit on the distance if not there yet, + # otherwise try to keep the speed constant around the control time horizon. + if self.distance > 0: + a_target = (self.speed_limit_offseted**2 - self._v_ego**2) / (2. * self.distance) + else: + a_target = self._v_offset / T_IDXS[CONTROL_N] + # active + elif self.state == SpeedLimitControlState.active: + # When active we are trying to keep the speed constant around the control time horizon. + a_target = self._v_offset / T_IDXS[CONTROL_N] + + # Keep solution limited. + self._a_target = np.clip(a_target, LIMIT_MIN_ACC, LIMIT_MAX_ACC) + + def _update_events(self, events): + if not self.is_active: + # no event while inactive + return + + if self._state_prev <= SpeedLimitControlState.tempInactive: + events.add(EventName.speedLimitActive) + elif self._speed_limit_changed != 0: + events.add(EventName.speedLimitValueChange) + + def update(self, enabled, v_ego, a_ego, sm, v_cruise_setpoint, events=Events()): + self._op_enabled = enabled + self._v_ego = v_ego + self._a_ego = a_ego + self._v_cruise_setpoint = v_cruise_setpoint + self._gas_pressed = sm['carState'].gasPressed + + self._speed_limit, self._distance, self._source = self._resolver.resolve(v_ego, self.speed_limit, sm) + + self._update_params() + self._update_calculations() + self._state_transition() + self._update_solution() + self._update_events(events) diff --git a/selfdrive/controls/lib/turn_speed_controller.py b/selfdrive/controls/lib/turn_speed_controller.py new file mode 100644 index 00000000000000..bff671a4f273ac --- /dev/null +++ b/selfdrive/controls/lib/turn_speed_controller.py @@ -0,0 +1,244 @@ +import numpy as np +import time +from common.params import Params +from cereal import log +from common.realtime import sec_since_boot +from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_SPEED, LIMIT_MAX_MAP_DATA_AGE, \ + LIMIT_SPEED_OFFSET_TH, CONTROL_N, LIMIT_MIN_ACC, LIMIT_MAX_ACC +from selfdrive.modeld.constants import T_IDXS + + +_ACTIVE_LIMIT_MIN_ACC = -0.5 # m/s^2 Maximum deceleration allowed while active. +_ACTIVE_LIMIT_MAX_ACC = 0.5 # m/s^2 Maximum acelration allowed while active. + + +_DEBUG = False + +TurnSpeedControlState = log.LongitudinalPlan.SpeedLimitControlState + + +def _debug(msg): + if not _DEBUG: + return + print(msg) + + +def _description_for_state(turn_speed_control_state): + if turn_speed_control_state == TurnSpeedControlState.inactive: + return 'INACTIVE' + if turn_speed_control_state == TurnSpeedControlState.tempInactive: + return 'TEMP INACTIVE' + if turn_speed_control_state == TurnSpeedControlState.adapting: + return 'ADAPTING' + if turn_speed_control_state == TurnSpeedControlState.active: + return 'ACTIVE' + + +class TurnSpeedController(): + def __init__(self): + self._params = Params() + self._last_params_update = 0. + self._is_enabled = self._params.get_bool("TurnSpeedControl") + self._op_enabled = False + self._v_ego = 0. + self._a_ego = 0. + self._v_cruise_setpoint = 0. + + self._v_offset = 0. + self._speed_limit = 0. + self._speed_limit_temp_inactive = 0. + self._distance = 0. + self._turn_sign = 0 + self._state = TurnSpeedControlState.inactive + + self._next_speed_limit_prev = 0. + + self._a_target = 0. + + @property + def a_target(self): + return self._a_target if self.is_active else self._a_ego + + @property + def state(self): + return self._state + + @state.setter + def state(self, value): + if value != self._state: + _debug(f'Turn Speed Controller state: {_description_for_state(value)}') + + if value == TurnSpeedControlState.adapting: + _debug('TSC: Enteriing Adapting as speed offset is below threshold') + _debug(f'_v_offset: {self._v_offset * 3.6}\nspeed_limit: {self.speed_limit * 3.6}') + _debug(f'_v_ego: {self._v_ego * 3.6}\ndistance: {self.distance}') + + if value == TurnSpeedControlState.tempInactive: + # Track the speed limit value when controller was set to temp inactive. + self._speed_limit_temp_inactive = self._speed_limit + + self._state = value + + @property + def is_active(self): + return self.state > TurnSpeedControlState.tempInactive + + @property + def speed_limit(self): + return max(self._speed_limit, LIMIT_MIN_SPEED) if self._speed_limit > 0. else 0. + + @property + def distance(self): + return max(self._distance, 0.) + + @property + def turn_sign(self): + return self._turn_sign + + def _get_limit_from_map_data(self, sm): + """Provides the speed limit, distance and turn sign to it for turns based on map data. + """ + # Ignore if no live map data + sock = 'liveMapData' + if sm.logMonoTime[sock] is None: + _debug('TS: No map data for turn speed limit') + return 0., 0., 0 + + # Load map_data and initialize + map_data = sm[sock] + speed_limit = 0. + + # Calculate the age of the gps fix. Ignore if too old. + gps_fix_age = time.time() - map_data.lastGpsTimestamp * 1e-3 + if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE: + _debug(f'TS: Ignoring map data as is too old. Age: {gps_fix_age}') + return 0., 0., 0 + + # Load turn ahead sections info from map_data with distances corrected by gps_fix_age + distance_since_fix = self._v_ego * gps_fix_age + distances_to_sections_ahead = np.maximum(0., np.array(map_data.turnSpeedLimitsAheadDistances) - distance_since_fix) + speed_limit_in_sections_ahead = map_data.turnSpeedLimitsAhead + turn_signs_in_sections_ahead = map_data.turnSpeedLimitsAheadSigns + + # Ensure current speed limit is considered only if we are inside the section. + if map_data.turnSpeedLimitValid and self._v_ego > 0.: + speed_limit_end_time = (map_data.turnSpeedLimitEndDistance / self._v_ego) - gps_fix_age + if speed_limit_end_time > 0.: + speed_limit = map_data.turnSpeedLimit + + # When we have no ahead speed limit to consider or all are greater than current speed limit + # or car has stopped, then provide current value and reset tracking. + turn_sign = map_data.turnSpeedLimitSign if map_data.turnSpeedLimitValid else 0 + if len(speed_limit_in_sections_ahead) == 0 or self._v_ego <= 0. or \ + (speed_limit > 0 and np.amin(speed_limit_in_sections_ahead) > speed_limit): + self._next_speed_limit_prev = 0. + return speed_limit, 0., turn_sign + + # Calculated the time needed to adapt to the limits ahead and the corresponding distances. + adapt_times = (np.maximum(speed_limit_in_sections_ahead, LIMIT_MIN_SPEED) - self._v_ego) / LIMIT_ADAPT_ACC + adapt_distances = self._v_ego * adapt_times + 0.5 * LIMIT_ADAPT_ACC * adapt_times**2 + distance_gaps = distances_to_sections_ahead - adapt_distances + + # We select as next speed limit, the one that have the lowest distance gap. + next_idx = np.argmin(distance_gaps) + next_speed_limit = speed_limit_in_sections_ahead[next_idx] + distance_to_section_ahead = distances_to_sections_ahead[next_idx] + next_turn_sign = turn_signs_in_sections_ahead[next_idx] + distance_gap = distance_gaps[next_idx] + + # When we have a next_speed_limit value that has not changed from a provided next speed limit value + # in previous resolutions, we keep providing it along with the udpated distance to it. + if next_speed_limit == self._next_speed_limit_prev: + return next_speed_limit, distance_to_section_ahead, next_turn_sign + + # Reset tracking + self._next_speed_limit_prev = 0. + + # When we detect we are close enough, we provide the next limit value and track it. + if distance_gap <= 0.: + self._next_speed_limit_prev = next_speed_limit + return next_speed_limit, distance_to_section_ahead, next_turn_sign + + # Otherwise we just provide the calculated speed_limit + return speed_limit, 0., turn_sign + + def _update_params(self): + time = sec_since_boot() + if time > self._last_params_update + 5.0: + self._is_enabled = self._params.get_bool("TurnSpeedControl") + self._last_params_update = time + + def _update_calculations(self): + # Update current velocity offset (error) + self._v_offset = self.speed_limit - self._v_ego + + def _state_transition(self, sm): + # In any case, if op is disabled, or turn speed limit control is disabled + # or the reported speed limit is 0, deactivate. + if not self._op_enabled or not self._is_enabled or self.speed_limit == 0.: + self.state = TurnSpeedControlState.inactive + return + + # In any case, we deactivate the speed limit controller temporarily + # if gas is pressed (to support gas override implementations). + if sm['carState'].gasPressed: + self.state = TurnSpeedControlState.tempInactive + return + + # inactive + if self.state == TurnSpeedControlState.inactive: + # If the limit speed offset is negative (i.e. reduce speed) and lower than threshold and distanct to turn limit + # is positive (not in turn yet) we go to adapting state to reduce speed, otherwise we go directly to active + if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.: + self.state = TurnSpeedControlState.adapting + else: + self.state = TurnSpeedControlState.active + # tempInactive + elif self.state == TurnSpeedControlState.tempInactive: + # if the speed limit recorded when going to temp Inactive changes + # then set to inactive, activation will happen on next cycle + if self._speed_limit != self._speed_limit_temp_inactive: + self.state = TurnSpeedControlState.inactive + # adapting + elif self.state == TurnSpeedControlState.adapting: + # Go to active once the speed offset is over threshold or the distance to turn is now 0. + if self._v_offset >= LIMIT_SPEED_OFFSET_TH or self.distance == 0.: + self.state = TurnSpeedControlState.active + # active + elif self.state == TurnSpeedControlState.active: + # Go to adapting if the speed offset goes below threshold as long as the distance to turn is still positive. + if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.: + self.state = TurnSpeedControlState.adapting + + def _update_solution(self): + # inactive or tempInactive state + if self.state <= TurnSpeedControlState.tempInactive: + # Preserve current values + a_target = self._a_ego + # adapting + elif self.state == TurnSpeedControlState.adapting: + # When adapting we target to achieve the speed limit on the distance. + a_target = (self.speed_limit**2 - self._v_ego**2) / (2. * self.distance) + a_target = np.clip(a_target, LIMIT_MIN_ACC, LIMIT_MAX_ACC) + # active + elif self.state == TurnSpeedControlState.active: + # When active we are trying to keep the speed constant around the control time horizon. + # but under constrained acceleration limits since we are in a turn. + a_target = self._v_offset / T_IDXS[CONTROL_N] + a_target = np.clip(a_target, _ACTIVE_LIMIT_MIN_ACC, _ACTIVE_LIMIT_MAX_ACC) + + # update solution values. + self._a_target = a_target + + def update(self, enabled, v_ego, a_ego, sm): + self._op_enabled = enabled + self._v_ego = v_ego + self._a_ego = a_ego + + # Get the speed limit from Map Data + self._speed_limit, self._distance, self._turn_sign = self._get_limit_from_map_data(sm) + + self._update_params() + self._update_calculations() + self._state_transition(sm) + self._update_solution() diff --git a/selfdrive/controls/lib/vision_turn_controller.py b/selfdrive/controls/lib/vision_turn_controller.py new file mode 100644 index 00000000000000..f547a458a9ac71 --- /dev/null +++ b/selfdrive/controls/lib/vision_turn_controller.py @@ -0,0 +1,287 @@ +import numpy as np +import math +from cereal import log +from common.numpy_fast import interp +from common.params import Params +from common.realtime import sec_since_boot +from selfdrive.config import Conversions as CV +from selfdrive.controls.lib.lane_planner import TRAJECTORY_SIZE +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX + + +_MIN_V = 5.6 # Do not operate under 20km/h + +_ENTERING_PRED_LAT_ACC_TH = 1.3 # Predicted Lat Acc threshold to trigger entering turn state. +_ABORT_ENTERING_PRED_LAT_ACC_TH = 1.1 # Predicted Lat Acc threshold to abort entering state if speed drops. + +_TURNING_LAT_ACC_TH = 1.6 # Lat Acc threshold to trigger turning turn state. + +_LEAVING_LAT_ACC_TH = 1.3 # Lat Acc threshold to trigger leaving turn state. +_FINISH_LAT_ACC_TH = 1.1 # Lat Acc threshold to trigger end of turn cycle. + +_EVAL_STEP = 5. # mts. Resolution of the curvature evaluation. +_EVAL_START = 20. # mts. Distance ahead where to start evaluating vision curvature. +_EVAL_LENGHT = 150. # mts. Distance ahead where to stop evaluating vision curvature. +_EVAL_RANGE = np.arange(_EVAL_START, _EVAL_LENGHT, _EVAL_STEP) + +_A_LAT_REG_MAX = 2. # Maximum lateral acceleration + +# Lookup table for the minimum smooth deceleration during the ENTERING state +# depending on the actual maximum absolute lateral acceleration predicted on the turn ahead. +_ENTERING_SMOOTH_DECEL_V = [-0.2, -1.] # min decel value allowed on ENTERING state +_ENTERING_SMOOTH_DECEL_BP = [1.3, 3.] # absolute value of lat acc ahead + +# Lookup table for the acceleration for the TURNING state +# depending on the current lateral acceleration of the vehicle. +_TURNING_ACC_V = [0.5, 0., -0.4] # acc value +_TURNING_ACC_BP = [1.5, 2.3, 3.] # absolute value of current lat acc + +_LEAVING_ACC = 0.5 # Confortble acceleration to regain speed while leaving a turn. + +_MIN_LANE_PROB = 0.6 # Minimum lanes probability to allow curvature prediction based on lanes. + +_DEBUG = False + + +def _debug(msg): + if not _DEBUG: + return + print(msg) + + +VisionTurnControllerState = log.LongitudinalPlan.VisionTurnControllerState + + +def eval_curvature(poly, x_vals): + """ + This function returns a vector with the curvature based on path defined by `poly` + evaluated on distance vector `x_vals` + """ + # https://en.wikipedia.org/wiki/Curvature# Local_expressions + def curvature(x): + a = abs(2 * poly[1] + 6 * poly[0] * x) / (1 + (3 * poly[0] * x**2 + 2 * poly[1] * x + poly[2])**2)**(1.5) + return a + + return np.vectorize(curvature)(x_vals) + + +def eval_lat_acc(v_ego, x_curv): + """ + This function returns a vector with the lateral acceleration based + for the provided speed `v_ego` evaluated over curvature vector `x_curv` + """ + + def lat_acc(curv): + a = v_ego**2 * curv + return a + + return np.vectorize(lat_acc)(x_curv) + + +def _description_for_state(turn_controller_state): + if turn_controller_state == VisionTurnControllerState.disabled: + return 'DISABLED' + if turn_controller_state == VisionTurnControllerState.entering: + return 'ENTERING' + if turn_controller_state == VisionTurnControllerState.turning: + return 'TURNING' + if turn_controller_state == VisionTurnControllerState.leaving: + return 'LEAVING' + + +class VisionTurnController(): + def __init__(self, CP): + self._params = Params() + self._CP = CP + self._op_enabled = False + self._gas_pressed = False + self._is_enabled = self._params.get_bool("TurnVisionControl") + self._last_params_update = 0. + self._v_cruise_setpoint = 0. + self._v_ego = 0. + self._a_ego = 0. + self._a_target = 0. + self._v_overshoot = 0. + self._state = VisionTurnControllerState.disabled + + self._reset() + + @property + def state(self): + return self._state + + @state.setter + def state(self, value): + if value != self._state: + _debug(f'TVC: TurnVisionController state: {_description_for_state(value)}') + if value == VisionTurnControllerState.disabled: + self._reset() + self._state = value + + @property + def a_target(self): + return self._a_target if self.is_active else self._a_ego + + @property + def v_turn(self): + return self._v_overshoot if self.is_active and self._lat_acc_overshoot_ahead else self._v_ego + + @property + def is_active(self): + return self._state != VisionTurnControllerState.disabled + + def _reset(self): + self._current_lat_acc = 0. + self._max_v_for_current_curvature = 0. + self._max_pred_lat_acc = 0. + self._v_overshoot_distance = 200. + self._lat_acc_overshoot_ahead = False + + def _update_params(self): + time = sec_since_boot() + if time > self._last_params_update + 5.0: + self._is_enabled = self._params.get_bool("TurnVisionControl") + self._last_params_update = time + + def _update_calculations(self, sm): + # Get path polynomial aproximation for curvature estimation from model data. + path_poly = None + model_data = sm['modelV2'] if sm.valid.get('modelV2', False) else None + lat_planner_data = sm['lateralPlan'] if sm.valid.get('lateralPlan', False) else None + + # 1. When the probability of lanes is good enough, compute polynomial from lanes as they are way more stable + # on current mode than drving path. + if model_data is not None and len(model_data.laneLines) == 4 and len(model_data.laneLines[0].t) == TRAJECTORY_SIZE: + ll_x = model_data.laneLines[1].x # left and right ll x is the same + lll_y = np.array(model_data.laneLines[1].y) + rll_y = np.array(model_data.laneLines[2].y) + l_prob = model_data.laneLineProbs[1] + r_prob = model_data.laneLineProbs[2] + lll_std = model_data.laneLineStds[1] + rll_std = model_data.laneLineStds[2] + + # Reduce reliance on lanelines that are too far apart or will be in a few seconds + width_pts = rll_y - lll_y + prob_mods = [] + for t_check in [0.0, 1.5, 3.0]: + width_at_t = interp(t_check * (self._v_ego + 7), ll_x, width_pts) + prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0])) + mod = min(prob_mods) + l_prob *= mod + r_prob *= mod + + # Reduce reliance on uncertain lanelines + l_std_mod = interp(lll_std, [.15, .3], [1.0, 0.0]) + r_std_mod = interp(rll_std, [.15, .3], [1.0, 0.0]) + l_prob *= l_std_mod + r_prob *= r_std_mod + + # Find path from lanes as the average center lane only if min probability on both lanes is above threshold. + if l_prob > _MIN_LANE_PROB and r_prob > _MIN_LANE_PROB: + c_y = width_pts / 2 + lll_y + path_poly = np.polyfit(ll_x, c_y, 3) + + # 2. If not polynomial derived from lanes, then derive it from compensated driving path with lanes as + # provided by `lateralPlanner`. + if path_poly is None and lat_planner_data is not None and len(lat_planner_data.dPathWLinesX) > 0 \ + and lat_planner_data.dPathWLinesX[0] > 0: + path_poly = np.polyfit(lat_planner_data.dPathWLinesX, lat_planner_data.dPathWLinesY, 3) + + # 3. If no polynomial derived from lanes or driving path, then provide a straight line poly. + if path_poly is None: + path_poly = np.array([0., 0., 0., 0.]) + + current_curvature = abs( + sm['carState'].steeringAngleDeg * CV.DEG_TO_RAD / (self._CP.steerRatio * self._CP.wheelbase)) + self._current_lat_acc = current_curvature * self._v_ego**2 + self._max_v_for_current_curvature = math.sqrt(_A_LAT_REG_MAX / current_curvature) if current_curvature > 0 \ + else V_CRUISE_MAX * CV.KPH_TO_MS + + pred_curvatures = eval_curvature(path_poly, _EVAL_RANGE) + max_pred_curvature = np.amax(pred_curvatures) + self._max_pred_lat_acc = self._v_ego**2 * max_pred_curvature + + max_curvature_for_vego = _A_LAT_REG_MAX / max(self._v_ego, 0.1)**2 + lat_acc_overshoot_idxs = np.nonzero(pred_curvatures >= max_curvature_for_vego)[0] + self._lat_acc_overshoot_ahead = len(lat_acc_overshoot_idxs) > 0 + + if self._lat_acc_overshoot_ahead: + self._v_overshoot = min(math.sqrt(_A_LAT_REG_MAX / max_pred_curvature), self._v_cruise_setpoint) + self._v_overshoot_distance = max(lat_acc_overshoot_idxs[0] * _EVAL_STEP + _EVAL_START, _EVAL_STEP) + _debug(f'TVC: High LatAcc. Dist: {self._v_overshoot_distance:.2f}, v: {self._v_overshoot * CV.MS_TO_KPH:.2f}') + + def _state_transition(self): + # In any case, if system is disabled or the feature is disabeld or gas is pressed, disable. + if not self._op_enabled or not self._is_enabled or self._gas_pressed: + self.state = VisionTurnControllerState.disabled + return + + # DISABLED + if self.state == VisionTurnControllerState.disabled: + # Do not enter a turn control cycle if speed is low. + if self._v_ego <= _MIN_V: + pass + # If substantial lateral acceleration is predicted ahead, then move to Entering turn state. + elif self._max_pred_lat_acc >= _ENTERING_PRED_LAT_ACC_TH: + self.state = VisionTurnControllerState.entering + # ENTERING + elif self.state == VisionTurnControllerState.entering: + # Transition to Turning if current lateral acceleration is over the threshold. + if self._current_lat_acc >= _TURNING_LAT_ACC_TH: + self.state = VisionTurnControllerState.turning + # Abort if the predicted lateral acceleration drops + elif self._max_pred_lat_acc < _ABORT_ENTERING_PRED_LAT_ACC_TH: + self.state = VisionTurnControllerState.disabled + # TURNING + elif self.state == VisionTurnControllerState.turning: + # Transition to Leaving if current lateral acceleration drops drops below threshold. + if self._current_lat_acc <= _LEAVING_LAT_ACC_TH: + self.state = VisionTurnControllerState.leaving + # LEAVING + elif self.state == VisionTurnControllerState.leaving: + # Transition back to Turning if current lateral acceleration goes back over the threshold. + if self._current_lat_acc >= _TURNING_LAT_ACC_TH: + self.state = VisionTurnControllerState.turning + # Finish if current lateral acceleration goes below threshold. + elif self._current_lat_acc < _FINISH_LAT_ACC_TH: + self.state = VisionTurnControllerState.disabled + + def _update_solution(self): + # DISABLED + if self.state == VisionTurnControllerState.disabled: + # when not overshooting, calculate v_turn as the speed at the prediction horizon when following + # the smooth deceleration. + a_target = self._a_ego + # ENTERING + elif self.state == VisionTurnControllerState.entering: + # when not overshooting, target a smooth deceleration in preparation for a sharp turn to come. + a_target = interp(self._max_pred_lat_acc, _ENTERING_SMOOTH_DECEL_BP, _ENTERING_SMOOTH_DECEL_V) + if self._lat_acc_overshoot_ahead: + # when overshooting, target the acceleration needed to achieve the overshoot speed at + # the required distance + a_target = min((self._v_overshoot**2 - self._v_ego**2) / (2 * self._v_overshoot_distance), a_target) + _debug(f'TVC Entering: Overshooting: {self._lat_acc_overshoot_ahead}') + _debug(f' Decel: {a_target:.2f}, target v: {self.v_turn * CV.MS_TO_KPH}') + # TURNING + elif self.state == VisionTurnControllerState.turning: + # When turning we provide a target acceleration that is confortable for the lateral accelearation felt. + a_target = interp(self._current_lat_acc, _TURNING_ACC_BP, _TURNING_ACC_V) + # LEAVING + elif self.state == VisionTurnControllerState.leaving: + # When leaving we provide a confortable acceleration to regain speed. + a_target = _LEAVING_ACC + + # update solution values. + self._a_target = a_target + + def update(self, enabled, v_ego, a_ego, v_cruise_setpoint, sm): + self._op_enabled = enabled + self._gas_pressed = sm['carState'].gasPressed + self._v_ego = v_ego + self._a_ego = a_ego + self._v_cruise_setpoint = v_cruise_setpoint + + self._update_params() + self._update_calculations(sm) + self._state_transition() + self._update_solution() diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 235bafc41b67b6..6f1a22a3e54d45 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -26,7 +26,7 @@ def plannerd_thread(sm=None, pm=None): lateral_planner = LateralPlanner(CP, use_lanelines=use_lanelines, wide_camera=wide_camera) if sm is None: - sm = messaging.SubMaster(['carState', 'controlsState', 'radarState', 'modelV2'], + sm = messaging.SubMaster(['carState', 'controlsState', 'radarState', 'modelV2', 'lateralPlan', 'liveMapData'], poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) if pm is None: diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index aeaabcf0247e4d..0f72e5238e33f0 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -53,8 +53,8 @@ def run_following_distance_simulation(v_lead, t_end=200.0): mpc.set_cur_state(v_ego, a_ego) if first: # Make sure MPC is converged on first timestep for _ in range(20): - mpc.update(CS.carState, radarstate.radarState, 0) - mpc.update(CS.carState, radarstate.radarState, 0) + mpc.update(CS.carState, radarstate.radarState, 0, 0., True) + mpc.update(CS.carState, radarstate.radarState, 0, 0., True) # Choose slowest of two solutions v_ego, a_ego = mpc.mpc_solution.v_ego[5], mpc.mpc_solution.a_ego[5] diff --git a/selfdrive/debug/internal/hands_on_wheel_moniotr.py b/selfdrive/debug/internal/hands_on_wheel_moniotr.py new file mode 100644 index 00000000000000..014648bba9aba7 --- /dev/null +++ b/selfdrive/debug/internal/hands_on_wheel_moniotr.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# type: ignore + +import os +import argparse +import signal +import sys + +import cereal.messaging as messaging +from cereal import log +from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus +from selfdrive.controls.lib.events import Events + +HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState + + +def sigint_handler(signal, frame): + print("handler!") + exit(0) + + +signal.signal(signal.SIGINT, sigint_handler) + + +def status_monitor(): + # use driverState socker to drive timing. + driverState = messaging.sub_sock('driverState', addr=args.addr, conflate=True) + sm = messaging.SubMaster(['carState', 'dMonitoringState'], addr=args.addr) + steering_status = HandsOnWheelStatus() + v_cruise_last = 0 + + while messaging.recv_one(driverState): + try: + sm.update() + + v_cruise = sm['carState'].cruiseState.speed + steering_wheel_engaged = len(sm['carState'].buttonEvents) > 0 or \ + v_cruise != v_cruise_last or sm['carState'].steeringPressed + v_cruise_last = v_cruise + + # Get status from our own instance of SteeringStatus + steering_status.update(Events(), steering_wheel_engaged, sm['carState'].cruiseState.enabled, sm['carState'].vEgo) + steering_state = steering_status.hands_on_wheel_state + state_name = "Unknown " + if steering_state == HandsOnWheelState.none: + state_name = "Not Active " + elif steering_state == HandsOnWheelState.ok: + state_name = "Hands On Wheel " + elif steering_state == HandsOnWheelState.minor: + state_name = "Hands Off Wheel - Minor " + elif steering_state == HandsOnWheelState.warning: + state_name = "Hands Off Wheel - Warning " + elif steering_state == HandsOnWheelState.critical: + state_name = "Hands Off Wheel - Critical" + elif steering_state == HandsOnWheelState.terminal: + state_name = "Hands Off Wheel - Terminal" + + # Get events from `dMonitoringState` + events = sm['dMonitoringState'].events + event_name = events[0].name if len(events) else "None" + event_name = "{:<30}".format(event_name[:30]) + + # Print output + sys.stdout.write(f'\rSteering State: {state_name} | event: {event_name}') + sys.stdout.flush() + + except Exception as e: + print(e) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Sniff a communcation socket') + parser.add_argument('--addr', default='127.0.0.1') + args = parser.parse_args() + + if args.addr != "127.0.0.1": + os.environ["ZMQ"] = "1" + messaging.context = messaging.Context() + + status_monitor() diff --git a/selfdrive/manager/custom_dep.py b/selfdrive/manager/custom_dep.py new file mode 100755 index 00000000000000..579cf520ccfb69 --- /dev/null +++ b/selfdrive/manager/custom_dep.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +import os +import sys +import errno +import shutil +from urllib.request import urlopen +from glob import glob +import subprocess +import importlib.util + +# NOTE: Do NOT import anything here that needs be built (e.g. params) +from common.basedir import BASEDIR +from common.spinner import Spinner +from selfdrive.hardware import TICI + + +OPSPLINE_SPEC = importlib.util.find_spec('opspline') or importlib.util.find_spec('scipy') +OVERPY_SPEC = importlib.util.find_spec('overpy') +MAX_BUILD_PROGRESS = 100 +TMP_DIR = '/data/tmp' +PYEXTRA_DIR = '/data/openpilot/pyextra' + + +def wait_for_internet_connection(): + while True: + try: + _ = urlopen('https://www.google.com/', timeout=10) + return + except Exception: + pass + + +def install_dep(spinner): + wait_for_internet_connection() + + if TICI: + TOTAL_PIP_STEPS = 2986 + + try: + os.makedirs(TMP_DIR) + except OSError as e: + if e.errno != errno.EEXIST: + raise + my_env = os.environ.copy() + my_env['TMPDIR'] = TMP_DIR + + pip_target = [f'--target={PYEXTRA_DIR}'] + packages = [] + if OPSPLINE_SPEC is None: + packages.append('scipy==1.7.1') + if OVERPY_SPEC is None: + packages.append('overpy==0.6') + + pip = subprocess.Popen([sys.executable, "-m", "pip", "install", "-v"] + pip_target + packages, + stdout=subprocess.PIPE, env=my_env) + else: + TOTAL_PIP_STEPS = 24 + # mount system rw so apt and pip can do its thing + subprocess.check_call(['mount', '-o', 'rw,remount', '/system']) + + # Run preparation script for pip installation + subprocess.check_call(['sh', './install_gfortran.sh'], cwd=os.path.join(BASEDIR, 'installer/custom/')) + + # install pip from git + package = 'git+https://github.com/move-fast/opspline.git@master' + pip = subprocess.Popen([sys.executable, "-m", "pip", "install", "-v", package], stdout=subprocess.PIPE) + + # Read progress from pip and update spinner + steps = 0 + while True: + output = pip.stdout.readline() + if pip.poll() is not None: + break + if output: + steps += 1 + spinner.update_progress(MAX_BUILD_PROGRESS * min(1., steps / TOTAL_PIP_STEPS), 100.) + print(output.decode('utf8', 'replace')) + if TICI: + shutil.rmtree(TMP_DIR) + os.unsetenv('TMPDIR') + + # remove numpy installed to PYEXTRA_DIR since numpy is already present in the AGNOS image + if OPSPLINE_SPEC is None: + for directory in glob(f'{PYEXTRA_DIR}/numpy*'): + shutil.rmtree(directory) + shutil.rmtree(f'{PYEXTRA_DIR}/bin') + + +if __name__ == "__main__" and (OPSPLINE_SPEC is None or OVERPY_SPEC is None): + spinner = Spinner() + spinner.update_progress(0, 100) + install_dep(spinner) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index bcbd74f9919326..d00f39a4416eec 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -35,7 +35,13 @@ def manager_init(): default_params = [ ("CompletedTrainingVersion", "0"), ("HasAcceptedTerms", "0"), + ("HandsOnWheelMonitoring", "0"), ("OpenpilotEnabledToggle", "1"), + ("ShowDebugUI", "1"), + ("SpeedLimitControl", "1"), + ("SpeedLimitPercOffset", "1"), + ("TurnSpeedControl", "1"), + ("TurnVisionControl", "1"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) @@ -48,6 +54,10 @@ def manager_init(): if params.get(k) is None: params.put(k, v) + # parameters set by Enviroment Varables + if os.getenv("HANDSMONITORING") is not None: + params.put_bool("HandsOnWheelMonitoring", bool(int(os.getenv("HANDSMONITORING")))) + # is this dashcam? if os.getenv("PASSIVE") is not None: params.put_bool("Passive", bool(int(os.getenv("PASSIVE")))) diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 1284505402f80e..7fc59d610fc139 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -35,6 +35,7 @@ PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, persistent=True), PythonProcess("updated", "selfdrive.updated", enabled=not PC, persistent=True), PythonProcess("uploader", "selfdrive.loggerd.uploader", persistent=True), + PythonProcess("mapd", "selfdrive.mapd.mapd"), # EON only PythonProcess("rtshield", "selfdrive.rtshield", enabled=EON), diff --git a/selfdrive/mapd/config.py b/selfdrive/mapd/config.py new file mode 100644 index 00000000000000..5d122422cad743 --- /dev/null +++ b/selfdrive/mapd/config.py @@ -0,0 +1,7 @@ +# Map query config + +QUERY_RADIUS = 3000 # mts. Radius to use on OSM data queries. +MIN_DISTANCE_FOR_NEW_QUERY = 1000 # mts. Minimum distance to query area edge before issuing a new query. +FULL_STOP_MAX_SPEED = 1.39 # m/s Max speed for considering car is stopped. +LOOK_AHEAD_HORIZON_TIME = 15. # s. Time horizon for look ahead of turn speed sections to provide on liveMapData msg. +LANE_WIDTH = 3.7 # Lane width estimate. Used for detecting departures from way. diff --git a/selfdrive/mapd/lib/NodesData.py b/selfdrive/mapd/lib/NodesData.py new file mode 100644 index 00000000000000..5dc41848f9dad4 --- /dev/null +++ b/selfdrive/mapd/lib/NodesData.py @@ -0,0 +1,397 @@ +import numpy as np +from enum import Enum +from selfdrive.mapd.lib.geo import DIRECTION, R, vectors + +from selfdrive.hardware import EON + +if EON: + from opspline import splev, splprep # pylint: disable=E0401 +else: + from scipy.interpolate import splev, splprep + + +_TURN_CURVATURE_THRESHOLD = 0.002 # 1/mts. A curvature over this value will generate a speed limit section. +_MAX_LAT_ACC = 2.3 # Maximum lateral acceleration in turns. +_SPLINE_EVAL_STEP = 5 # mts for spline evaluation for curvature calculation +_MIN_SPEED_SECTION_LENGTH = 100. # mts. Sections below this value will not be split in smaller sections. +_MAX_CURV_DEVIATION_FOR_SPLIT = 2. # Split a speed section if the max curvature deviates from mean by this factor. +_MAX_CURV_SPLIT_ARC_ANGLE = 90. # degrees. Arc section to split into new speed section around max curvature. +_MIN_NODE_DISTANCE = 50. # mts. Minimum distance between nodes for spline evaluation. Data is enhanced if not met. +_ADDED_NODES_DIST = 15. # mts. Distance between added nodes when data is enhanced for spline evaluation. +_DIVERTION_SEARCH_RANGE = [-200., 50.] # mt. Range of distance to current location for divertion search. + + +def nodes_raw_data_array_for_wr(wr, drop_last=False): + """Provides an array of raw node data (id, lat, lon, speed_limit) for all nodes in way relation + """ + sl = wr.speed_limit + data = np.array([(n.id, n.lat, n.lon, sl) for n in wr.way.nodes], dtype=float) + + # reverse the order if way direction is backwards + if wr.direction == DIRECTION.BACKWARD: + data = np.flip(data, axis=0) + + # drop last if requested + return data[:-1] if drop_last else data + + +def node_calculations(points): + """Provides node calculations based on an array of (lat, lon) points in radians. + points is a (N x 1) array where N >= 3 + """ + if len(points) < 3: + raise(IndexError) + + # Get the vector representation of node points in cartesian plane. + # (N-1, 2) array. Not including (0., 0.) + v = vectors(points) * R + + # Calculate the vector magnitudes (or distance) + # (N-1, 1) array. No distance for v[-1] + d = np.linalg.norm(v, axis=1) + + # Calculate the bearing (from true north clockwise) for every node. + # (N-1, 1) array. No bearing for v[-1] + b = np.arctan2(v[:, 0], v[:, 1]) + + # Add origin to vector space. (i.e first node in list) + v = np.concatenate(([[0., 0.]], v)) + + # Provide distance to previous node and distance to next node + dp = np.concatenate(([0.], d)) + dn = np.concatenate((d, [0.])) + + # Provide cumulative distance on route + dr = np.cumsum(dp, axis=0) + + # Bearing of last node should keep bearing from previous. + b = np.concatenate((b, [b[-1]])) + + return v, dp, dn, dr, b + + +def spline_curvature_calculations(vect, dist_prev): + """Provides an array of curvatures and its distances by applying a spline interpolation + to the path described by the nodes data. + """ + # We need to artificially enhance the data before applying spline interpolation to avoid getting + # inexistent curvature values close to irregularities on the road when the resolution of nodes data + # approaching the irregularity is low. + + # - Find indexes where dist_prev is greater than threshold + too_far_idxs = np.nonzero(dist_prev >= _MIN_NODE_DISTANCE)[0] + + # - Traversing in reverse order, enhance data by adding points at the found indexes. + for idx in too_far_idxs[::-1]: + dp = dist_prev[idx] # distance of vector that needs to be replaced by higher resolution vectors. + n = int(np.ceil(dp / _ADDED_NODES_DIST)) # number of vectors that need to be added. + new_v = vect[idx, :] / n # new relative vector to insert. + vect = np.delete(vect, idx, axis=0) # remove the relative vector to be replaced by the insertion of new vectors. + vect = np.insert(vect, [idx] * n, [new_v] * n, axis=0) # insert n new relative vectors + + # Data is now enhanced, we can proceed with curvature evaluation. + # - Create cumulative arrays for distance traveled and vector (x, y) + ds = np.cumsum(dist_prev, axis=0) + vs = np.cumsum(vect, axis=0) + + # - spline interpolation + tck, u = splprep([vs[:, 0], vs[:, 1]]) + + # - evaluate every _SPLINE_EVAL_STEP mts. + n = max(int(ds[-1] / _SPLINE_EVAL_STEP), len(u)) + unew = np.arange(0, n + 1) / n + + # - get derivatives + d1 = splev(unew, tck, der=1) + d2 = splev(unew, tck, der=2) + + # - calculate curvatures + num = d1[0] * d2[1] - d1[1] * d2[0] + den = (d1[0]**2 + d1[1]**2)**(1.5) + curv = num / den + curv_ds = unew * ds[-1] + + return curv, curv_ds + + +def speed_section(curv_sec): + """Map curvature section data into turn speed sections data. + Returns: [section start distance, section end distance, speed limit based on max curvature, sing of curvature] + """ + max_curv_idx = np.argmax(curv_sec[:, 0]) + start = np.amin(curv_sec[:, 2]) + end = np.amax(curv_sec[:, 2]) + + return np.array([start, end, np.sqrt(_MAX_LAT_ACC / curv_sec[max_curv_idx, 0]), curv_sec[max_curv_idx, 1]]) + + +def split_speed_section_by_sign(curv_sec): + """Will split the given curvature section in subsections if there is a change of sign on the curvature value + in the section. + """ + # Find the indexes where the curvatures change signs (if any). + c_idx = np.nonzero(np.diff(curv_sec[:, 1]))[0] + 1 + + # Split section base on change of sign. + return np.split(curv_sec, c_idx) + + +def split_speed_section_by_curv_degree(curv_sec): + """Will split the given curvature section in subsections as to isolate peaks of turn with substantially + higher curvature values. This will aid on preventing having very long turn sections with low speed limit + that is only really necessary for a small region of the section. + """ + # Only consider spliting a section if long enough. + length = curv_sec[-1, 2] - curv_sec[0, 2] + if length <= _MIN_SPEED_SECTION_LENGTH: + return [curv_sec] + + # Only split if max curvature deviates substantially from mean curvature. + max_curv_idx = np.argmax(curv_sec[:, 0]) + max_curv = curv_sec[max_curv_idx, 0] + mean_curv = np.mean(curv_sec[:, 0]) + if max_curv / mean_curv <= _MAX_CURV_DEVIATION_FOR_SPLIT: + return [curv_sec] + + # Calcualate where to split as to isolate a curve section around the max curvature peak. + arc_side = (np.radians(_MAX_CURV_SPLIT_ARC_ANGLE) / max_curv) / 2. + arc_side_idx_length = int(np.ceil(arc_side / _SPLINE_EVAL_STEP)) + split_idxs = [max_curv_idx - arc_side_idx_length, max_curv_idx + arc_side_idx_length] + split_idxs = list(filter(lambda idx: idx > 0 and idx < len(curv_sec) - 1, split_idxs)) + + # If the arc section to split extendes outside the section, then no need to split. + if len(split_idxs) == 0: + return [curv_sec] + + # Create the splits and split the resulting sections recursevly. + splits = [split_speed_section_by_curv_degree(cs) for cs in np.split(curv_sec, split_idxs)] + + # Flatten the results and return the new list of curvature sections. + curv_secs = [cs for split in splits for cs in split] + return curv_secs + + +def speed_limits_for_curvatures_data(curv, dist): + """Provides the calculations for the speed limits from the curvatures array and distances, + by providing distances to curvature sections and correspoinding speed limit values as well as + curvature direction/sign. + """ + # Prepare a data array for processing with absolute curvature values, curvature sign and distances. + curv_abs = np.abs(curv) + data = np.column_stack((curv_abs, np.sign(curv), dist)) + + # Find where curvatures overshoot turn curvature threshold and define as section + is_section = curv_abs >= _TURN_CURVATURE_THRESHOLD + + # Find the indexes where the sections start and end. i.e. change indexes. + c_idx = np.nonzero(np.diff(is_section))[0] + 1 + + # Create independent arrays for each split section base on change indexes. + splits = np.array(np.split(data, c_idx), dtype=object) + + # Filter the splits to keep only the curvature section arrays by getting the odd or even split arrays depending + # on whether the first split is a curvature split or not. + curv_sec_idxs = np.arange(0 if is_section[0] else 1, len(splits), 2, dtype=int) + curv_secs = splits[curv_sec_idxs] + + # Further split the curv sections by sign change + sub_secs = [split_speed_section_by_sign(cs) for cs in curv_secs] + curv_secs = [cs for sub_sec in sub_secs for cs in sub_sec] + + # Further split the curv sections by degree of curvature + sub_secs = [split_speed_section_by_curv_degree(cs) for cs in curv_secs] + curv_secs = [cs for sub_sec in sub_secs for cs in sub_sec] + + # Return an array where each row represents a turn speed limit section. + # [start, end, speed_limit, curvature_sign] + return np.array([speed_section(cs) for cs in curv_secs]) + +def is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids): + """ + Evaluates if the way relation `wr` is a valid divertion from node with id `node_id`. + A valid divertion is a way relation with an edge node with the given `node_id` that is not already included + in the list of way relations in the route (`wr_ids`) and that can be travaled in the direction as if starting + from node with id `node_id` + """ + if wr.id in wr_ids: + return False + wr.update_direction_from_starting_node(node_id) + return not wr.is_prohibited + + +class SpeedLimitSection(): + """And object representing a speed limited road section ahead. + provides the start and end distance and the speed limit value + """ + def __init__(self, start, end, value): + self.start = start + self.end = end + self.value = value + + def __repr__(self): + return f'from: {self.start}, to: {self.end}, limit: {self.value}' + + +class TurnSpeedLimitSection(SpeedLimitSection): + def __init__(self, start, end, value, sign): + super().__init__(start, end, value) + self.curv_sign = sign + + def __repr__(self): + return f'{super().__repr__()}, sign: {self.curv_sign}' + + +class NodeDataIdx(Enum): + """Column index for data elements on NodesData underlying data store. + """ + node_id = 0 + lat = 1 + lon = 2 + speed_limit = 3 + x = 4 # x value of cartesian vector representing the section between last node and this node. + y = 5 # y value of cartesian vector representing the section between last node and this node. + dist_prev = 6 # distance to previous node. + dist_next = 7 # distance to next node + dist_route = 8 # cumulative distance on route + bearing = 9 # bearing of the vector departing from this node. + + +class NodesData: + """Container for the list of node data from a ordered list of way relations to be used in a Route + """ + def __init__(self, way_relations, wr_index): + self._nodes_data = np.array([]) + self._divertions = [[]] + self._curvature_speed_sections_data = np.array([]) + + way_count = len(way_relations) + if way_count == 0: + return + + # We want all the nodes from the last way section + nodes_data = nodes_raw_data_array_for_wr(way_relations[-1]) + + # For the ways before the last in the route we want all the nodes but the last, as that one is the first on + # the next section. Collect them, append last way node data and concatenate the numpy arrays. + if way_count > 1: + wrs_data = tuple([nodes_raw_data_array_for_wr(wr, drop_last=True) for wr in way_relations[:-1]]) + wrs_data += (nodes_data,) + nodes_data = np.concatenate(wrs_data) + + # Get a subarray with lat, lon to compute the remaining node values. + lat_lon_array = nodes_data[:, [1, 2]] + points = np.radians(lat_lon_array) + # Ensure we have more than 3 points, if not calculations are not possible. + if len(points) <= 3: + return + vect, dist_prev, dist_next, dist_route, bearing = node_calculations(points) + + # append calculations to nodes_data + # nodes_data structure: [id, lat, lon, speed_limit, x, y, dist_prev, dist_next, dist_route, bearing] + self._nodes_data = np.column_stack((nodes_data, vect, dist_prev, dist_next, dist_route, bearing)) + + # Build route divertion options data from the wr_index. + wr_ids = [wr.id for wr in way_relations] + self._divertions = [[wr for wr in wr_index.way_relations_with_edge_node_id(node_id) + if is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids)] + for node_id in nodes_data[:, 0]] + + # Store calculcations for curvature sections speed limits. We need more than 3 points to be able to process. + # _curvature_speed_sections_data structure: [dist_start, dist_stop, speed_limits, curv_sign] + if len(vect) > 3: + curv, curv_ds = spline_curvature_calculations(vect, dist_prev) + self._curvature_speed_sections_data = speed_limits_for_curvatures_data(curv, curv_ds) + + @property + def count(self): + return len(self._nodes_data) + + def get(self, node_data_idx): + """Returns the array containing all the elements of a specific NodeDataIdx type. + """ + if len(self._nodes_data) == 0 or node_data_idx.value >= self._nodes_data.shape[1]: + return np.array([]) + + return self._nodes_data[:, node_data_idx.value] + + def speed_limits_ahead(self, ahead_idx, distance_to_node_ahead): + """Returns and array of SpeedLimitSection objects for the actual route ahead of current location + """ + if len(self._nodes_data) == 0 or ahead_idx is None: + return [] + + # Find the cumulative distances where speed limit changes. Build Speed limit sections for those. + dist = np.concatenate(([distance_to_node_ahead], self.get(NodeDataIdx.dist_next)[ahead_idx:])) + dist = np.cumsum(dist, axis=0) + sl = self.get(NodeDataIdx.speed_limit)[ahead_idx - 1:] + sl_next = np.concatenate((sl[1:], [0.])) + + # Create a boolean mask where speed limit changes and filter values + sl_change = sl != sl_next + distances = dist[sl_change] + speed_limits = sl[sl_change] + + # Create speed limits sections combining all continious nodes that have same speed limit value. + start = 0. + limits_ahead = [] + for idx, end in enumerate(distances): + limits_ahead.append(SpeedLimitSection(start, end, speed_limits[idx])) + start = end + + return limits_ahead + + def distance_to_end(self, ahead_idx, distance_to_node_ahead): + if len(self._nodes_data) == 0 or ahead_idx is None: + return None + + return np.sum(np.concatenate(([distance_to_node_ahead], self.get(NodeDataIdx.dist_next)[ahead_idx:]))) + + def curvatures_speed_limit_sections_ahead(self, ahead_idx, distance_to_node_ahead): + """Returns and array of TurnSpeedLimitSection objects for the actual route ahead of current location for + speed limit sections due to curvatures in the road. + """ + if len(self._curvature_speed_sections_data) == 0 or ahead_idx is None: + return [] + + # Find the current distance traveled so far on the route. + dist_curr = self.get(NodeDataIdx.dist_route)[ahead_idx] - distance_to_node_ahead + + # Filter the sections to get only those where the stop distance is ahead of current. + sec_filter = self._curvature_speed_sections_data[:, 1] > dist_curr + data = self._curvature_speed_sections_data[sec_filter] + + # Offset distances to current distance. + data[:, [0, 1]] -= dist_curr + + # Create speed limits sections + limits_ahead = [TurnSpeedLimitSection(max(0., d[0]), d[1], d[2], d[3]) for d in data] + + return limits_ahead + + def possible_divertions(self, ahead_idx, distance_to_node_ahead): + """ Returns and array with the way relations the route could possible divert to by finding + the alternative way divertions on the nodes in the vicinity of the current location. + """ + if len(self._nodes_data) == 0 or ahead_idx is None: + return [] + + dist_route = self.get(NodeDataIdx.dist_route) + rel_dist = dist_route - dist_route[ahead_idx] + distance_to_node_ahead + valid_idxs = np.nonzero(np.logical_and(rel_dist >= _DIVERTION_SEARCH_RANGE[0], + rel_dist <= _DIVERTION_SEARCH_RANGE[1]))[0] + valid_divertions = [self._divertions[i] for i in valid_idxs] + + return [wr for wrs in valid_divertions for wr in wrs] # flatten. + + def distance_to_node(self, node_id, ahead_idx, distance_to_node_ahead): + """ + Provides the distance to a specific node in the route identified by `node_id` in reference to the node ahead + (`ahead_idx`) and the distance from current location to the node ahead (`distance_to_node_ahead`). + """ + node_ids = self.get(NodeDataIdx.node_id) + node_idxs = np.nonzero(node_ids == node_id)[0] + if len(self._nodes_data) == 0 or ahead_idx is None or len(node_idxs) == 0: + return None + + return self.get(NodeDataIdx.dist_route)[node_idxs[0]] - self.get(NodeDataIdx.dist_route)[ahead_idx] + \ + distance_to_node_ahead diff --git a/selfdrive/mapd/lib/Route.py b/selfdrive/mapd/lib/Route.py new file mode 100644 index 00000000000000..12c05f669a3375 --- /dev/null +++ b/selfdrive/mapd/lib/Route.py @@ -0,0 +1,337 @@ +from selfdrive.mapd.lib.NodesData import NodesData, NodeDataIdx +from selfdrive.mapd.config import QUERY_RADIUS +from selfdrive.mapd.lib.geo import ref_vectors, R, distance_to_points +from itertools import compress +import numpy as np + + +_ACCEPTABLE_BEARING_DELTA_COSINE = -0.7 # Continuation paths with a bearing of 180 +/- 45 degrees. +_MAX_ALLOWED_BEARING_DELTA_COSINE_AT_EDGE = -0.3420 # bearing delta at route edge must be 180 +/- 70 degrees. +_MAP_DATA_EDGE_DISTANCE = 50 # mts. Consider edge of map data from this distance to edge of query radius. + + +class Route(): + """A set of consecutive way relations forming a default driving route. + """ + def __init__(self, current, wr_index, way_collection_id, query_center): + """Create a Route object from a given `wr_index` (Way relation index) + + Args: + current (WayRelation): The Way Relation that is currently located. It must be active. + wr_index (WayRelationIndex): The indexes of WayRelations by node id. + way_collection_id (UUID): The id of the Way Collection that created this Route. + query_center (Numpy Array): lat, lon] numpy array in radians indicating the center of the data query. + """ + self.way_collection_id = way_collection_id + self._ordered_way_relations = [] + self._nodes_data = None + self._reset() + + # An active current way is needed to be able to build a route + if not current.active: + return + + # Build the route by finding iteratavely the best matching ways continuing after the end of the + # current (last_wr) way. Use the index to find the continuation posibilities on each iteration. + last_wr = current + ordered_way_ids = [] + split_wrs = [] + while True: + # - Append current element to the route list of ordered way relations. + self._ordered_way_relations.append(last_wr) + ordered_way_ids.append(last_wr.id) + + # - Get the id of the node at the end of the way and then fetch the way relations that share the end node id. + last_node_id = last_wr.last_node.id + way_relations = wr_index.way_relations_with_edge_node_id(last_node_id) + + # - Add split way relations when necessary and remove parent way relations. + split_wrs_to_add = [wr for wr in split_wrs if last_node_id in wr.edge_nodes_ids] + way_relations.extend(split_wrs_to_add) + parent_ids = [wr.parent_wr_id for wr in split_wrs_to_add] + way_relations = [wr for wr in way_relations if wr.id not in parent_ids] + + # - If no more way_relations than last_wr, we have to check if we join another wr on an internal node, and + # if we do, we replace such way relation with the split of it and continue. + if len(way_relations) == 1: + way_relations = wr_index.way_relations_with_node_id(last_node_id) + # If no more way_relations than last_wr, we got to the end. + if len(way_relations) == 1: + break + + # If we join a wr on an internal node, then we artificially split the wr in two and pass both wrs as + # candidates to the wr selection code below. + wr_to_split = [wr for wr in way_relations if wr is not last_wr][0] + next_split_way_id = -len(split_wrs) - 1 # Keep split wrs ids unique on Route + new_wrs = wr_to_split.split(last_node_id, [next_split_way_id, next_split_way_id - 1]) + # If it could not be splited, we are done. + if len(new_wrs) != 2: + break + + # Replace the original way relation for the splitted version on way_relations and track splited wrs. + split_wrs.extend(new_wrs) + way_relations.remove(wr_to_split) + way_relations.extend(new_wrs) + + # - Get the coordinates for the edge node and build the array of coordinates for the nodes before the edge node + # on each of the common way relations, then get the vectors in cartesian plane for the end sections of each way. + ref_point = last_wr.last_node_coordinates + points = np.array([wr.node_before_edge_coordinates(last_node_id) for wr in way_relations]) + v = ref_vectors(ref_point, points) * R + + # - Calculate the bearing (from true north clockwise) for every end section of each way. + b = np.arctan2(v[:, 0], v[:, 1]) + + # - Find index of las_wr section and calculate deltas of bearings to the other sections. + last_wr_idx = way_relations.index(last_wr) + b_ref = b[last_wr_idx] + delta = b - b_ref + + # - Update the direction of the possible route continuation ways as starting from last_node_id. + # Make sure to exclude any ways already included in the ordered list as to not modify direction when there + # are looping roads (like roundabouts). A way will never be included twice in a route anyway. + for wr in way_relations: + if wr.id not in ordered_way_ids: + wr.update_direction_from_starting_node(last_node_id) + + # - Filter the possible route continuation way relations: + # - exclude any way already added to the ordered list. + # - exclude all way relations that are prohibited due to traffic direction. + mask = [wr.id not in ordered_way_ids and not wr.is_prohibited for wr in way_relations] + way_relations = list(compress(way_relations, mask)) + delta = delta[mask] + + # if no options left, we got to the end. + if len(way_relations) == 0: + break + + # - The cosine of the bearing delta will aid us in choosing the way that continues. The cosine is + # minimum (-1) for a perfect straight continuation as delta would be pi or -pi. + cos_delta = np.cos(delta) + + def pick_best_idx(cos_delta): + """Selects the best index on `cos_delta` array for a way that continues the route. + In principle we want to choose the way that continues as straight as possible. + Bue we need to make sure that if there are 2 or more ways continuing relatively straight, then we + need to disambiguate, either by matching the `ref` or `name` value of the continuing way with the + last way selected. + This can prevent cases where the chosen route could be for instance an exit ramp of a way due to the fact + that the ramp has a better match on bearing to previous way. We choose to stay on the road with the same `ref` + or `name` value if available. + If there is no ambiguity or there are no `name` or `ref` values to disambiguate, then we pick the one with + the straightest following direction. + """ + # Find the indexes of the cosine of the deltas that are considered straight enough to continue. + idxs = np.nonzero(cos_delta < _ACCEPTABLE_BEARING_DELTA_COSINE)[0] + + # If no amiguity or no way to break it, just return the straightest line. + if len(idxs) <= 1 or (last_wr.ref is None and last_wr.name is None): + # The section with the best continuation is the one with a bearing delta closest to pi. This is equivalent + # to taking the one with the smallest cosine of the bearing delta, as cosine is minimum (-1) on both pi + # and -pi. + return np.argmin(cos_delta) + + wrs = [way_relations[idx] for idx in idxs] + + # If we find a continuation way with the same reference we just choose it. + refs = list(map(lambda wr: wr.ref, wrs)) + if last_wr.ref is not None: + idx = next((idx for idx, ref in enumerate(refs) if ref == last_wr.ref), None) + if idx is not None: + return idxs[idx] + + # If we find a continuation way with the same name we just choose it. + names = list(map(lambda wr: wr.name, wrs)) + if last_wr.name is not None: + idx = next((idx for idx, name in enumerate(names) if name == last_wr.name), None) + if idx is not None: + return idxs[idx] + + # We did not manage to deambiguate, choose straightest path. + return np.argmin(cos_delta) + + # Get the index of the continuation way. + best_idx = pick_best_idx(cos_delta) + + # - Make sure to not select as route continuation a way that turns too much if we are close to the border of + # map data queried. This is to avoid building a route that takes a sharp turn just because we do not have the + # data for the way that actually continues straight. + if cos_delta[best_idx] > _MAX_ALLOWED_BEARING_DELTA_COSINE_AT_EDGE: + dist_to_center = distance_to_points(query_center, np.array([ref_point]))[0] + if dist_to_center > QUERY_RADIUS - _MAP_DATA_EDGE_DISTANCE: + break + + # - Select next way. + last_wr = way_relations[best_idx] + + # Build the node data from the ordered list of way relations + self._nodes_data = NodesData(self._ordered_way_relations, wr_index) + + # Locate where we are in the route node list. + self._locate() + + def __repr__(self): + count = self._nodes_data.count if self._nodes_data is not None else None + return f'Route: {self.way_collection_id}, idx ahead: {self._ahead_idx} of {count}' + + def _reset(self): + self._limits_ahead = None + self._cuvature_limits_ahead = None + self._curvatures_ahead = None + self._ahead_idx = None + self._distance_to_node_ahead = None + + @property + def located(self): + return self._ahead_idx is not None + + def _locate(self): + """Will resolve the index in the nodes_data list for the node ahead of the current location. + It updates as well the distance from the current location to the node ahead. + """ + current = self.current_wr + if current is None: + return + + node_ahead_id = current.node_ahead.id + self._distance_to_node_ahead = current.distance_to_node_ahead + start_idx = self._ahead_idx if self._ahead_idx is not None else 1 + self._ahead_idx = None + + ids = self._nodes_data.get(NodeDataIdx.node_id) + for idx in range(start_idx, len(ids)): + if ids[idx] == node_ahead_id: + self._ahead_idx = idx + break + + @property + def current_wr(self): + return self._ordered_way_relations[0] if len(self._ordered_way_relations) else None + + def update(self, location_rad, bearing_rad, location_stdev): + """Will update the route structure based on the given `location_rad` and `bearing_rad` assuming progress on the + route on the original direction. If direction has changed or active point on the route can not be found, the route + will become invalid. + """ + if len(self._ordered_way_relations) == 0 or location_rad is None or bearing_rad is None: + return + + # Skip if no update on location or bearing. + if np.array_equal(self.current_wr.location_rad, location_rad) and self.current_wr.bearing_rad == bearing_rad: + return + + # Transverse the way relations on the actual order until we find an active one. From there, rebuild the route + # with the way relations remaining ahead. + for idx, wr in enumerate(self._ordered_way_relations): + active_direction = wr.direction + wr.update(location_rad, bearing_rad, location_stdev) + + if not wr.active: + continue + + if wr.direction != active_direction: + # Driving direction on the route has changed. stop. + break + + # We have now the current wr. Repopulate from here till the end and locate + self._ordered_way_relations = self._ordered_way_relations[idx:] + self._reset() + self._locate() + + # If the active way is diverting, check whether there are posibilities to divert from the route in the + # vecinity of the current location. If there are possibilities, then stop here to loose the route as we are + # most likely driving away. If there are no possibilites, then stick to the route as the diversion is probably + # just a matter of GPS accuracy. (It can happen after driving under a bridge) + if wr.diverting and len(self._nodes_data.possible_divertions(self._ahead_idx, self._distance_to_node_ahead)) > 0: + break + + # The current location in route is valid, return. + return + + # if we got here, there is no new active way relation or driving direction has changed. Reset. + self._reset() + + @property + def speed_limits_ahead(self): + """Returns and array of SpeedLimitSection objects for the actual route ahead of current location + """ + if self._limits_ahead is not None: + return self._limits_ahead + + if self._nodes_data is None or self._ahead_idx is None: + return [] + + self._limits_ahead = self._nodes_data.speed_limits_ahead(self._ahead_idx, self._distance_to_node_ahead) + return self._limits_ahead + + @property + def curvature_speed_limits_ahead(self): + """Returns and array of TurnSpeedLimitSection objects for the actual route ahead of current location due + to curvatures + """ + if self._cuvature_limits_ahead is not None: + return self._cuvature_limits_ahead + + if self._nodes_data is None or self._ahead_idx is None: + return [] + + self._cuvature_limits_ahead = self._nodes_data. \ + curvatures_speed_limit_sections_ahead(self._ahead_idx, self._distance_to_node_ahead) + + return self._cuvature_limits_ahead + + @property + def current_speed_limit(self): + if not self.located: + return None + + limits_ahead = self.speed_limits_ahead + if len(limits_ahead) == 0 or limits_ahead[0].start != 0: + return None + + return limits_ahead[0].value + + @property + def current_curvature_speed_limit_section(self): + if not self.located: + return None + + limits_ahead = self.curvature_speed_limits_ahead + if len(limits_ahead) == 0 or limits_ahead[0].start != 0: + return None + + return limits_ahead[0] + + @property + def next_speed_limit_section(self): + if not self.located: + return None + + limits_ahead = self.speed_limits_ahead + if len(limits_ahead) == 0: + return None + + # Find the first section that does not start in 0. i.e. the next section + for section in limits_ahead: + if section.start > 0: + return section + + return None + + def next_curvature_speed_limit_sections(self, horizon_mts): + if not self.located: + return [] + + # Provide the curvature speed sections that start ahead (> 0) and up to horizon + return list(filter(lambda la: la.start > 0 and la.start <= horizon_mts, self.curvature_speed_limits_ahead)) + + @property + def distance_to_end(self): + if not self.located: + return None + + return self._nodes_data.distance_to_end(self._ahead_idx, self._distance_to_node_ahead) + + @property + def current_road_name(self): + return self.current_wr.road_name if self.located else None diff --git a/selfdrive/mapd/lib/WayCollection.py b/selfdrive/mapd/lib/WayCollection.py new file mode 100644 index 00000000000000..56ad0b60dbba17 --- /dev/null +++ b/selfdrive/mapd/lib/WayCollection.py @@ -0,0 +1,85 @@ +from selfdrive.mapd.lib.WayRelation import WayRelation +from selfdrive.mapd.lib.WayRelationIndex import WayRelationIndex +from selfdrive.mapd.lib.Route import Route +from selfdrive.mapd.config import LANE_WIDTH +import uuid + + +_ACCEPTABLE_BEARING_DELTA_IND = 0.7071067811865475 # sin(pi/4) | 45 degrees acceptable bearing delta + + +class WayCollection(): + """A collection of WayRelations to use for maps data analysis. + """ + def __init__(self, ways, query_center): + """Creates a WayCollection with a set of OSM way objects. + + Args: + ways (Array): Collection of Way objects fetched from OSM in a radius around `query_center` + query_center (Numpy Array): [lat, lon] numpy array in radians indicating the center of the data query. + """ + self.id = uuid.uuid4() + self.way_relations = [WayRelation(way) for way in ways] + self.query_center = query_center + + self.wr_index = WayRelationIndex(self.way_relations) + + def get_route(self, location_rad, bearing_rad, location_stdev): + """Provides the best route found in the way collection based on current location and bearing. + """ + if location_rad is None or bearing_rad is None or location_stdev is None: + return None + + # Update all way relations in collection to the provided location and bearing. + for wr in self.way_relations: + wr.update(location_rad, bearing_rad, location_stdev) + + # Get the way relations where a match was found. i.e. those now marked as active as long as the direction of + # travel is valid. + valid_way_relations = [wr for wr in self.way_relations if wr.active and not wr.is_prohibited] + + # If no active, then we could not find a current way to build a route. + if len(valid_way_relations) == 0: + return None + + # If only one valid, then pick it as current. + if len(valid_way_relations) == 1: + current = valid_way_relations[0] + + # If more than one is valid, filter out any valid way relation where the bearing delta indicator is too high. + else: + wr_acceptable_bearing = list(filter(lambda wr: wr.active_bearing_delta <= _ACCEPTABLE_BEARING_DELTA_IND, + valid_way_relations)) + + # If delta bearing indicator is too high for all, then use as current the one that has the shorter one. + if len(wr_acceptable_bearing) == 0: + valid_way_relations.sort(key=lambda wr: wr.active_bearing_delta) + current = valid_way_relations[0] + + # If only one with acceptable bearing, use it. + elif len(wr_acceptable_bearing) == 1: + current = wr_acceptable_bearing[0] + + else: + # If more than one with acceptable bearing, filter the ones with distance to way lower than 2 standard + # deviation from GPS accuracy (95%) + half the road width estimate. + wr_accurate_distance = [wr for wr in wr_acceptable_bearing + if wr.distance_to_way <= 2. * location_stdev + wr.lanes * LANE_WIDTH / 2.] + + # If none with accurate distance to way, then select the closest to the way + if len(wr_accurate_distance) == 0: + wr_acceptable_bearing.sort(key=lambda wr: wr.distance_to_way) + current = wr_acceptable_bearing[0] + + # If only one with distance under accuracy, select this one. + elif len(wr_accurate_distance) == 1: + current = wr_accurate_distance[0] + + # If more than one with distance under accuracy. Then select the one with lowest highway rank. + # i.e. prefere motorways over other roads and so on. This is to prevent selecting a small paralel + # road to a main road when the accuracy is poor. + else: + wr_accurate_distance.sort(key=lambda wr: wr.highway_rank) + current = wr_accurate_distance[0] + + return Route(current, self.wr_index, self.id, self.query_center) diff --git a/selfdrive/mapd/lib/WayRelation.py b/selfdrive/mapd/lib/WayRelation.py new file mode 100644 index 00000000000000..6dd341cb880705 --- /dev/null +++ b/selfdrive/mapd/lib/WayRelation.py @@ -0,0 +1,422 @@ +from selfdrive.mapd.lib.geo import DIRECTION, R, vectors, bearing_to_points, distance_to_points +from selfdrive.mapd.lib.osm import create_way +from selfdrive.config import Conversions as CV +from selfdrive.mapd.config import LANE_WIDTH +from common.basedir import BASEDIR +from datetime import datetime as dt +import numpy as np +import re +import json + + +_WAY_BBOX_PADING = 80. / R # 80 mts of pading to bounding box. (expressed in radians) + + +with open(BASEDIR + "/selfdrive/mapd/lib/default_speeds.json", "rb") as f: + _COUNTRY_LIMITS = json.loads(f.read()) + + +_WD = { + 'Mo': 0, + 'Tu': 1, + 'We': 2, + 'Th': 3, + 'Fr': 4, + 'Sa': 5, + 'Su': 6 +} + +_HIGHWAY_RANK = { + 'motorway': 0, + 'motorway_link': 1, + 'trunk': 10, + 'trunk_link': 11, + 'primary': 20, + 'primary_link': 21, + 'secondary': 30, + 'secondary_link': 31, + 'tertiary': 40, + 'tertiary_link': 41, + 'unclassified': 50, + 'residential': 60, + 'living_street': 61, + None: 100, +} + + +def is_osm_time_condition_active(condition_string): + """ + Will indicate if a time condition for a restriction as described + @ https://wiki.openstreetmap.org/wiki/Conditional_restrictions + is active for the current date and time of day. + """ + now = dt.now().astimezone() + today = now.date() + week_days = [] + + # Look for days of week matched and validate if today matches criteria. + dr = re.findall(r'(Mo|Tu|We|Th|Fr|Sa|Su[-,\s]*?)', condition_string) + + if len(dr) == 1: + week_days = [_WD[dr[0]]] + # If two or more matches condider it a range of days between 1st and 2nd element. + elif len(dr) > 1: + week_days = list(range(_WD[dr[0]], _WD[dr[1]] + 1)) + + # If valid week days list is not empy and today day is not in the list, then the time-date range is not active. + if len(week_days) > 0 and now.weekday() not in week_days: + return False + + # Look for time ranges on the day. No time range, means all day + tr = re.findall(r'([0-9]{1,2}:[0-9]{2})\s*?-\s*?([0-9]{1,2}:[0-9]{2})', condition_string) + + # if no time range but there were week days set, consider it active during the whole day + if len(tr) == 0: + return len(dr) > 0 + + # Search among time ranges matched, one where now time belongs too. If found range is active. + for times_tup in tr: + times = list(map(lambda tt: dt. + combine(today, dt.strptime(tt, '%H:%M').time().replace(tzinfo=now.tzinfo)), times_tup)) + if now >= times[0] and now <= times[1]: + return True + + return False + + +def speed_limit_value_for_limit_string(limit_string): + # Look for matches of speed by default in kph, or in mph when explicitly noted. + v = re.match(r'^\s*([0-9]{1,3})\s*?(mph)?\s*$', limit_string) + if v is None: + return None + conv = CV.MPH_TO_MS if v[2] is not None and v[2] == "mph" else CV.KPH_TO_MS + return conv * float(v[1]) + + +def speed_limit_for_osm_tag_limit_string(limit_string): + # https://wiki.openstreetmap.org/wiki/Key:maxspeed + if limit_string is None: + # When limit is set to 0. is considered not existing. + return 0. + + # Attempt to parse limit as simple numeric value considering units. + limit = speed_limit_value_for_limit_string(limit_string) + if limit is not None: + return limit + + # Look for matches of speed with country implicit values. + v = re.match(r'^\s*([A-Z]{2}):([a-z_]+):?([0-9]{1,3})?(\s+)?(mph)?\s*', limit_string) + if v is None: + return 0. + + if v[2] == "zone" and v[3] is not None: + conv = CV.MPH_TO_MS if v[5] is not None and v[5] == "mph" else CV.KPH_TO_MS + limit = conv * float(v[3]) + elif f'{v[1]}:{v[2]}' in _COUNTRY_LIMITS: + limit = speed_limit_value_for_limit_string(_COUNTRY_LIMITS[f'{v[1]}:{v[2]}']) + + return limit if limit is not None else 0. + + +def conditional_speed_limit_for_osm_tag_limit_string(limit_string): + if limit_string is None: + # When limit is set to 0. is considered not existing. + return 0. + + # Look for matches of the ` @ ()` format + v = re.match(r'^(.*)@\s*\((.*)\).*$', limit_string) + if v is None: + return 0. # No valid format match + + value = speed_limit_for_osm_tag_limit_string(v[1]) + if value == 0.: + return 0. # Invalid speed limit value + + # Look for date-time conditions separated by semicolon + v = re.findall(r'(?:;|^)([^;]*)', v[2]) + for datetime_condition in v: + if is_osm_time_condition_active(datetime_condition): + return value + + # If we get here, no current date-time conditon is active. + return 0. + + +class WayRelation(): + """A class that represent the relationship of an OSM way and a given `location` and `bearing` of a driving vehicle. + """ + def __init__(self, way, parent=None): + self.way = way + self.parent_wr_id = parent.id if parent is not None else None # For WRs created as splits of other WRs + self.reset_location_variables() + self.direction = DIRECTION.NONE + self._speed_limit = None + self._one_way = way.tags.get("oneway") + self.name = way.tags.get('name') + self.ref = way.tags.get('ref') + self.highway_type = way.tags.get("highway") + self.highway_rank = _HIGHWAY_RANK.get(self.highway_type) + try: + self.lanes = int(way.tags.get('lanes')) + except Exception: + self.lanes = 2 + + # Create numpy arrays with nodes data to support calculations. + self._nodes_np = np.radians(np.array([[nd.lat, nd.lon] for nd in way.nodes], dtype=float)) + self._nodes_ids = np.array([nd.id for nd in way .nodes], dtype=int) + + # Get the vectors representation of the segments betwheen consecutive nodes. (N-1, 2) + v = vectors(self._nodes_np) * R + + # Calculate the vector magnitudes (or distance) between nodes. (N-1) + self._way_distances = np.linalg.norm(v, axis=1) + + # Calculate the bearing (from true north clockwise) for every section of the way (vectors between nodes). (N-1) + self._way_bearings = np.arctan2(v[:, 0], v[:, 1]) + + # Define bounding box to ease the process of locating a node in a way. + # [[min_lat, min_lon], [max_lat, max_lon]] + self.bbox = np.row_stack((np.amin(self._nodes_np, 0) - _WAY_BBOX_PADING, + np.amax(self._nodes_np, 0) + _WAY_BBOX_PADING)) + + # Get the edge nodes ids. + self.edge_nodes_ids = [way.nodes[0].id, way.nodes[-1].id] + + def __repr__(self): + return f'(id: {self.id}, between {self.behind_idx} and {self.ahead_idx}, {self.direction}, active: {self.active})' + + def __eq__(self, other): + if isinstance(other, WayRelation): + return self.id == other.id + return False + + def reset_location_variables(self): + self.distance_to_node_ahead = 0. + self.location_rad = None + self.bearing_rad = None + self.active = False + self.diverting = False + self.ahead_idx = None + self.behind_idx = None + self._active_bearing_delta = None + self._distance_to_way = None + + @property + def id(self): + return self.way.id + + @property + def road_name(self): + if self.name is not None: + return self.name + return self.ref + + def update(self, location_rad, bearing_rad, location_stdev): + """Will update and validate the associated way with a given `location_rad` and `bearing_rad`. + Specifically it will find the nodes behind and ahead of the current location and bearing. + If no proper fit to the way geometry, the way relation is marked as invalid. + """ + self.reset_location_variables() + + # Ignore if location not in way bounding box + if not self.is_location_in_bbox(location_rad): + return + + # - Get the distance and bearings from location to all nodes. (N) + bearings = bearing_to_points(location_rad, self._nodes_np) + distances = distance_to_points(location_rad, self._nodes_np) + + # - Get absolute bearing delta to current driving bearing. (N) + delta = np.abs(bearing_rad - bearings) + + # - Nodes are ahead if the cosine of the delta is positive (N) + is_ahead = np.cos(delta) >= 0. + + # - Possible locations on the way are those where adjacent nodes change from ahead to behind or viceversa. + possible_idxs = np.nonzero(np.diff(is_ahead))[0] + + # - when no possible locations found, then the location is not in this way. + if len(possible_idxs) == 0: + return + + # - Find then angle formed between the vectors from the current location to consecutive nodes. This is the + # value of the difference in the bearings of the vectors. + teta = np.diff(bearings) + + # - When two consecutive nodes will be ahead and behind, they will form a triangle with the current location. + # We find the closest distance to the way by solving the area of the triangle and finding the height (h). + # We must use the abolute value of the sin of the angle in the formula, which is equivalent to ensure we + # are considering the smallest of the two angles formed between the two vectors. + # https://www.mathsisfun.com/algebra/trig-area-triangle-without-right-angle.html + h = distances[:-1] * distances[1:] * np.abs(np.sin(teta)) / self._way_distances + + # - Calculate the delta between driving bearing and way bearings. (N-1) + bw_delta = self._way_bearings - bearing_rad + + # - The absolut value of the sin of `bw_delta` indicates how close the bearings match independent of direction. + # We will use this value along the distance to the way to aid on way selection. (N-1) + abs_sin_bw_delta = np.abs(np.sin(bw_delta)) + + # - Get the delta to way bearing indicators and the distance to the way for the possible locations. + abs_sin_bw_delta_possible = abs_sin_bw_delta[possible_idxs] + h_possible = h[possible_idxs] + + # - Get the index where the distance to the way is minimum. That is the chosen location. + min_h_possible_idx = np.argmin(h_possible) + min_delta_idx = possible_idxs[min_h_possible_idx] + + # - If the distance to the way is over 4 standard deviations of the gps accuracy + half the maximum road width + # estimate, then we are way too far to stick to this way (i.e. we are not on this way anymore) + half_road_width_estimate = self.lanes * LANE_WIDTH / 2. + if h_possible[min_h_possible_idx] > 4. * location_stdev + half_road_width_estimate: + return + + # - If the distance to the road is greater than 2 standard deviations of the gps accuracy + half the maximum road + # width estimate then we are most likely diverting from this route. + diverting = h_possible[min_h_possible_idx] > 2. * location_stdev + half_road_width_estimate + + # Populate location variables with result + if is_ahead[min_delta_idx]: + self.direction = DIRECTION.BACKWARD + self.ahead_idx = min_delta_idx + self.behind_idx = min_delta_idx + 1 + else: + self.direction = DIRECTION.FORWARD + self.ahead_idx = min_delta_idx + 1 + self.behind_idx = min_delta_idx + + self._distance_to_way = h[min_delta_idx] + self._active_bearing_delta = abs_sin_bw_delta_possible[min_h_possible_idx] + # TODO: The distance to node ahead currently represent the distance from the GPS fix location. + # It would be perhaps more accurate to use the distance on the projection over the direct line between + # the two nodes. + self.distance_to_node_ahead = distances[self.ahead_idx] + self.active = True + self.diverting = diverting + self.location_rad = location_rad + self.bearing_rad = bearing_rad + self._speed_limit = None + + def update_direction_from_starting_node(self, start_node_id): + self._speed_limit = None + if self.edge_nodes_ids[0] == start_node_id: + self.direction = DIRECTION.FORWARD + elif self.edge_nodes_ids[-1] == start_node_id: + self.direction = DIRECTION.BACKWARD + else: + self.direction = DIRECTION.NONE + + def is_location_in_bbox(self, location_rad): + """Indicates if a given location is contained in the bounding box surrounding the way. + self.bbox = [[min_lat, min_lon], [max_lat, max_lon]] + """ + is_g = np.greater_equal(location_rad, self.bbox[0, :]) + is_l = np.less_equal(location_rad, self.bbox[1, :]) + + return np.all(np.concatenate((is_g, is_l))) + + @property + def speed_limit(self): + if self._speed_limit is not None: + return self._speed_limit + + # Get string from corresponding tag, consider conditional limits first. + limit_string = self.way.tags.get("maxspeed:conditional") + if limit_string is None: + if self.direction == DIRECTION.FORWARD: + limit_string = self.way.tags.get("maxspeed:forward:conditional") + elif self.direction == DIRECTION.BACKWARD: + limit_string = self.way.tags.get("maxspeed:backward:conditional") + + limit = conditional_speed_limit_for_osm_tag_limit_string(limit_string) + + # When no conditional limit set, attempt to get from regular speed limit tags. + if limit == 0.: + limit_string = self.way.tags.get("maxspeed") + if limit_string is None: + if self.direction == DIRECTION.FORWARD: + limit_string = self.way.tags.get("maxspeed:forward") + elif self.direction == DIRECTION.BACKWARD: + limit_string = self.way.tags.get("maxspeed:backward") + + limit = speed_limit_for_osm_tag_limit_string(limit_string) + + self._speed_limit = limit + return self._speed_limit + + @property + def active_bearing_delta(self): + """Returns the sine of the delta between the current location bearing and the exact + bearing of the portion of way we are currentluy located at. + """ + return self._active_bearing_delta + + @property + def is_one_way(self): + return self._one_way in ['yes'] or self.highway_type in ["motorway"] + + @property + def is_prohibited(self): + # Direction must be defined to asses this property. Default to `True` if not. + if self.direction == DIRECTION.NONE: + return True + return self.is_one_way and self.direction == DIRECTION.BACKWARD + + @property + def distance_to_way(self): + """Returns the perpendicular (i.e. minimum) distance between current location and the way + """ + return self._distance_to_way + + @property + def node_ahead(self): + return self.way.nodes[self.ahead_idx] if self.ahead_idx is not None else None + + @property + def last_node(self): + """Returns the last node on the way considering the traveling direction + """ + if self.direction == DIRECTION.FORWARD: + return self.way.nodes[-1] + if self.direction == DIRECTION.BACKWARD: + return self.way.nodes[0] + return None + + @property + def last_node_coordinates(self): + """Returns the coordinates for the last node on the way considering the traveling direction. (in radians) + """ + if self.direction == DIRECTION.FORWARD: + return self._nodes_np[-1] + if self.direction == DIRECTION.BACKWARD: + return self._nodes_np[0] + return None + + def node_before_edge_coordinates(self, node_id): + """Returns the coordinates of the node before the edge node identifeid with `node_id`. (in radians) + """ + if self.edge_nodes_ids[0] == node_id: + return self._nodes_np[1] + + if self.edge_nodes_ids[-1] == node_id: + return self._nodes_np[-2] + + return np.array([0., 0.]) + + def split(self, node_id, way_ids=None): + """ Returns and array with the way relations resulting from spliting the current way relation at node_id + """ + idxs = np.nonzero(self._nodes_ids == node_id)[0] + if len(idxs) == 0: + return [] + + idx = idxs[0] + if idx == 0 or idx == len(self._nodes_ids) - 1: + return [self] + + if not isinstance(way_ids, list): + way_ids = [-1, -2] # Default id values. + + ways = [create_way(way_ids[0], node_ids=self._nodes_ids[:idx + 1], from_way=self.way), + create_way(way_ids[1], node_ids=self._nodes_ids[idx:], from_way=self.way)] + return [WayRelation(way, parent=self) for way in ways] diff --git a/selfdrive/mapd/lib/WayRelationIndex.py b/selfdrive/mapd/lib/WayRelationIndex.py new file mode 100644 index 00000000000000..e941dcd5b7c1fe --- /dev/null +++ b/selfdrive/mapd/lib/WayRelationIndex.py @@ -0,0 +1,34 @@ + + +class WayRelationIndex(): + """ + A class containing an index of WayRelations by node ids of internal nodes and edge nodes. + """ + def __init__(self, way_relations): + self._edge_nodes_index_dict = {} + self._full_nodes_index_dict = {} + + for wr in way_relations: + self.add(wr) + + def add(self, way_relation): + for node in way_relation.way.nodes: + node_id = node.id + self._full_nodes_index_dict[node_id] = self._full_nodes_index_dict.get(node_id, []) + [way_relation] + if node_id in way_relation.edge_nodes_ids: + self._edge_nodes_index_dict[node_id] = self._edge_nodes_index_dict.get(node_id, []) + [way_relation] + + def remove(self, way_relation): + for node in way_relation.way.nodes: + node_id = node.id + self._full_nodes_index_dict[node_id] = [wr for wr in self._full_nodes_index_dict.get(node_id, []) + if wr is not way_relation] + if node_id in way_relation.edge_nodes_ids: + self._edge_nodes_index_dict[node_id] = [wr for wr in self._edge_nodes_index_dict.get(node_id, []) + if wr is not way_relation] + + def way_relations_with_edge_node_id(self, node_id): + return self._edge_nodes_index_dict.get(node_id, []) + + def way_relations_with_node_id(self, node_id): + return self._full_nodes_index_dict.get(node_id, []) diff --git a/selfdrive/mapd/lib/default_speeds.json b/selfdrive/mapd/lib/default_speeds.json new file mode 100644 index 00000000000000..5dbbe941f9ca04 --- /dev/null +++ b/selfdrive/mapd/lib/default_speeds.json @@ -0,0 +1,112 @@ +{ + "_comment": "These speeds are from https://wiki.openstreetmap.org/wiki/Speed_limits Special cases have been stripped", + "AR:urban": "40", + "AR:urban:primary": "60", + "AR:urban:secondary": "60", + "AR:rural": "110", + "AT:urban": "50", + "AT:rural": "100", + "AT:trunk": "100", + "AT:motorway": "130", + "BE:urban": "50", + "BE-VLG:rural": "70", + "BE-WAL:rural": "90", + "BE:trunk": "120", + "BE:motorway": "120", + "CH:urban[1]": "50", + "CH:rural": "80", + "CH:trunk": "100", + "CH:motorway": "120", + "CZ:pedestrian_zone": "20", + "CZ:living_street": "20", + "CZ:urban": "50", + "CZ:urban_trunk": "80", + "CZ:urban_motorway": "80", + "CZ:rural": "90", + "CZ:trunk": "110", + "CZ:motorway": "130", + "DK:urban": "50", + "DK:rural": "80", + "DK:motorway": "130", + "DE:living_street": "7", + "DE:service": "7", + "DE:residential": "30", + "DE:urban": "50", + "DE:rural": "100", + "DE:trunk": "none", + "DE:motorway": "none", + "FI:urban": "50", + "FI:rural": "80", + "FI:trunk": "100", + "FI:motorway": "120", + "FR:urban": "50", + "FR:rural": "80", + "FR:trunk": "110", + "FR:motorway": "130", + "GR:urban": "50", + "GR:rural": "90", + "GR:trunk": "110", + "GR:motorway": "130", + "HU:urban": "50", + "HU:rural": "90", + "HU:trunk": "110", + "HU:motorway": "130", + "IT:urban": "50", + "IT:rural": "90", + "IT:trunk": "110", + "IT:motorway": "130", + "JP:national": "60", + "JP:motorway": "100", + "LT:living_street": "20", + "LT:urban": "50", + "LT:rural": "90", + "LT:trunk": "120", + "LT:motorway": "130", + "PL:living_street": "20", + "PL:urban": "50", + "PL:rural": "90", + "PL:trunk": "100", + "PL:motorway": "140", + "RO:urban": "50", + "RO:rural": "90", + "RO:trunk": "100", + "RO:motorway": "130", + "RU:living_street": "20", + "RU:urban": "60", + "RU:rural": "90", + "RU:motorway": "110", + "SK:urban": "50", + "SK:rural": "90", + "SK:trunk": "90", + "SK:motorway": "90", + "SI:urban": "50", + "SI:rural": "90", + "SI:trunk": "110", + "SI:motorway": "130", + "ES:living_street": "20", + "ES:urban": "50", + "ES:rural": "50", + "ES:trunk": "90", + "ES:motorway": "120", + "SE:urban": "50", + "SE:rural": "70", + "SE:trunk": "90", + "SE:motorway": "110", + "GB:nsl_restricted": "30 mph", + "GB:nsl_single": "60 mph", + "GB:nsl_dual": "70 mph", + "GB:motorway": "70 mph", + "UA:urban": "50", + "UA:rural": "90", + "UA:trunk": "110", + "UA:motorway": "130", + "UZ:living_street": "30", + "UZ:urban": "70", + "UZ:rural": "100", + "UZ:motorway": "110", + "ZA:trunk": "120", + "ZA:residential": "60", + "ZA:rural": "100", + "ZA:urban": "60", + "ZA:motorway": "120" +} diff --git a/selfdrive/mapd/lib/geo.py b/selfdrive/mapd/lib/geo.py new file mode 100644 index 00000000000000..8f6232225f9de7 --- /dev/null +++ b/selfdrive/mapd/lib/geo.py @@ -0,0 +1,66 @@ +from enum import Enum +import numpy as np + + +R = 6373000.0 # approximate radius of earth in mts + + +def vectors(points): + """Provides a array of vectors on cartesian space (x, y). + Each vector represents the path from a point in `points` to the next. + `points` must by a (N, 2) array of [lat, lon] pairs in radians. + """ + latA = points[:-1, 0] + latB = points[1:, 0] + delta = np.diff(points, axis=0) + dlon = delta[:, 1] + + x = np.sin(dlon) * np.cos(latB) + y = np.cos(latA) * np.sin(latB) - (np.sin(latA) * np.cos(latB) * np.cos(dlon)) + + return np.column_stack((x, y)) + + +def ref_vectors(ref, points): + """Provides a array of vectors on cartesian space (x, y). + Each vector represents the path from ref to a point in `points`. + `points` must by a (N, 2) array of [lat, lon] pairs in radians. + """ + latA = ref[0] + latB = points[:, 0] + delta = points - ref + dlon = delta[:, 1] + + x = np.sin(dlon) * np.cos(latB) + y = np.cos(latA) * np.sin(latB) - (np.sin(latA) * np.cos(latB) * np.cos(dlon)) + + return np.column_stack((x, y)) + + +def bearing_to_points(point, points): + """Calculate the bearings (angle from true north clockwise) of the vectors between `point` and each + one of the entries in `points`. Both `point` and `points` elements are 2 element arrays containing a latitud, + longitude pair in radians. + """ + delta = points - point + x = np.sin(delta[:, 1]) * np.cos(points[:, 0]) + y = np.cos(point[0]) * np.sin(points[:, 0]) - (np.sin(point[0]) * np.cos(points[:, 0]) * np.cos(delta[:, 1])) + return np.arctan2(x, y) + + +def distance_to_points(point, points): + """Calculate the distance of the vectors between `point` and each one of the entries in `points`. + Both `point` and `points` elements are 2 element arrays containing a latitud, longitude pair in radians. + """ + delta = points - point + a = np.sin(delta[:, 0] / 2)**2 + np.cos(point[0]) * np.cos(points[:, 0]) * np.sin(delta[:, 1] / 2)**2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + return c * R + + +class DIRECTION(Enum): + NONE = 0 + AHEAD = 1 + BEHIND = 2 + FORWARD = 3 + BACKWARD = 4 diff --git a/selfdrive/mapd/lib/osm.py b/selfdrive/mapd/lib/osm.py new file mode 100644 index 00000000000000..04c8163bc7e560 --- /dev/null +++ b/selfdrive/mapd/lib/osm.py @@ -0,0 +1,37 @@ +import overpy +import numpy as np +from selfdrive.mapd.lib.geo import R + + +def create_way(way_id, node_ids, from_way): + """ + Creates and OSM Way with the given `way_id` and list of `node_ids`, copying attributes and tags from `from_way` + """ + return overpy.Way(way_id, node_ids=node_ids, attributes={}, result=from_way._result, + tags=from_way.tags) + + +class OSM(): + def __init__(self): + self.api = overpy.Overpass() + # self.api = overpy.Overpass(url='http://3.65.170.21/api/interpreter') + + def fetch_road_ways_around_location(self, lat, lon, radius): + # Calculate the bounding box coordinates for the bbox containing the circle around location. + bbox_angle = np.degrees(radius / R) + # fetch all ways and nodes on this ways in bbox + bbox_str = f'{str(lat - bbox_angle)},{str(lon - bbox_angle)},{str(lat + bbox_angle)},{str(lon + bbox_angle)}' + q = """ + way(""" + bbox_str + """) + [highway] + [highway!~"^(footway|path|corridor|bridleway|steps|cycleway|construction|bus_guideway|escape|service|track)$"]; + (._;>;); + out; + """ + try: + ways = self.api.query(q).ways + except Exception as e: + print(f'Exception while querying OSM:\n{e}') + ways = [] + + return ways diff --git a/selfdrive/mapd/mapd.py b/selfdrive/mapd/mapd.py new file mode 100644 index 00000000000000..80776557829312 --- /dev/null +++ b/selfdrive/mapd/mapd.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +import threading +from traceback import print_exception +import numpy as np +from time import strftime, gmtime +import cereal.messaging as messaging +from common.realtime import Ratekeeper +from selfdrive.mapd.lib.osm import OSM +from selfdrive.mapd.lib.geo import distance_to_points +from selfdrive.mapd.lib.WayCollection import WayCollection +from selfdrive.mapd.config import QUERY_RADIUS, MIN_DISTANCE_FOR_NEW_QUERY, FULL_STOP_MAX_SPEED, LOOK_AHEAD_HORIZON_TIME + + +_DEBUG = False + + +def _debug(msg): + if not _DEBUG: + return + print(msg) + + +def excepthook(args): + _debug(f'MapD: Threading exception:\n{args}') + print_exception(args.exc_type, args.exc_value, args.exc_traceback) + + +threading.excepthook = excepthook + + +class MapD(): + def __init__(self): + self.osm = OSM() + self.way_collection = None + self.route = None + self.last_gps_fix_timestamp = 0 + self.last_gps = None + self.location_deg = None # The current location in degrees. + self.location_rad = None # The current location in radians as a Numpy array. + self.bearing_rad = None + self.location_stdev = None # The current location accuracy in mts. 1 standard devitation. + self.gps_speed = 0. + self.last_fetch_location = None + self.last_route_update_fix_timestamp = 0 + self.last_publish_fix_timestamp = 0 + self._op_enabled = False + self._disengaging = False + self._query_thread = None + self._lock = threading.RLock() + + def udpate_state(self, sm): + sock = 'controlsState' + if not sm.updated[sock] or not sm.valid[sock]: + return + + controls_state = sm[sock] + self._disengaging = not controls_state.enabled and self._op_enabled + self._op_enabled = controls_state.enabled + + def update_gps(self, sm): + sock = 'gpsLocationExternal' + if not sm.updated[sock] or not sm.valid[sock]: + return + + log = sm[sock] + self.last_gps = log + + # ignore the message if the fix is invalid + if log.flags % 2 == 0: + return + + self.last_gps_fix_timestamp = log.timestamp # Unix TS. Milliseconds since January 1, 1970. + self.location_rad = np.radians(np.array([log.latitude, log.longitude], dtype=float)) + self.location_deg = (log.latitude, log.longitude) + self.bearing_rad = np.radians(log.bearingDeg, dtype=float) + self.gps_speed = log.speed + self.location_stdev = log.accuracy # log accuracies are presumably 1 standard deviation. + + _debug('Mapd: ********* Got GPS fix' + f'Pos: {self.location_deg} +/- {self.location_stdev * 2.} mts.\n' + f'Bearing: {log.bearingDeg} +/- {log.bearingAccuracyDeg * 2.} deg.\n' + f'timestamp: {strftime("%d-%m-%y %H:%M:%S", gmtime(self.last_gps_fix_timestamp * 1e-3))}' + f'*******') + + def _query_osm_not_blocking(self): + def query(osm, location_deg, location_rad, radius): + _debug(f'Mapd: Start query for OSM map data at {location_deg}') + lat, lon = location_deg + ways = osm.fetch_road_ways_around_location(lat, lon, radius) + _debug(f'Mapd: Query to OSM finished with {len(ways)} ways') + + # Only issue an update if we received some ways. Otherwise it is most likely a conectivity issue. + # Will retry on next loop. + if len(ways) > 0: + new_way_collection = WayCollection(ways, location_rad) + + # Use the lock to update the way_collection as it might be being used to update the route. + _debug('Mapd: Locking to write results from osm.') + with self._lock: + self.way_collection = new_way_collection + self.last_fetch_location = location_rad + _debug(f'Mapd: Updated map data @ {location_deg} - got {len(ways)} ways') + + _debug('Mapd: Releasing Lock to write results from osm') + + # Ignore if we have a query thread already running. + if self._query_thread is not None and self._query_thread.is_alive(): + return + + self._query_thread = threading.Thread(target=query, args=(self.osm, self.location_deg, self.location_rad, + QUERY_RADIUS)) + self._query_thread.start() + + def updated_osm_data(self): + if self.route is not None: + distance_to_end = self.route.distance_to_end + if distance_to_end is not None and distance_to_end >= MIN_DISTANCE_FOR_NEW_QUERY: + # do not query as long as we have a route with enough distance ahead. + return + + if self.location_rad is None: + return + + if self.last_fetch_location is not None: + distance_since_last = distance_to_points(self.last_fetch_location, np.array([self.location_rad]))[0] + if distance_since_last < QUERY_RADIUS - MIN_DISTANCE_FOR_NEW_QUERY: + # do not query if are still not close to the border of previous query area + return + + self._query_osm_not_blocking() + + def update_route(self): + def update_proc(): + # Ensure we clear the route on op disengage, this way we can correct possible incorrect map data due + # to wrongly locating or picking up the wrong route. + if self._disengaging: + self.route = None + _debug('Mapd *****: Clearing Route as system is disengaging. ********') + + if self.way_collection is None or self.location_rad is None or self.bearing_rad is None: + _debug('Mapd *****: Can not update route. Missing WayCollection, location or bearing ********') + return + + if self.route is not None and self.last_route_update_fix_timestamp == self.last_gps_fix_timestamp: + _debug('Mapd *****: Skipping route update. No new fix since last update ********') + return + + self.last_route_update_fix_timestamp = self.last_gps_fix_timestamp + + # Create the route if not existent or if it was generated by an older way collection + if self.route is None or self.route.way_collection_id != self.way_collection.id: + self.route = self.way_collection.get_route(self.location_rad, self.bearing_rad, self.location_stdev) + _debug(f'Mapd *****: Route created: \n{self.route}\n********') + return + + # Do not attempt to update the route if the car is going close to a full stop, as the bearing can start + # jumping and creating unnecesary loosing of the route. Since the route update timestamp has been updated + # a new liveMapData message will be published with the current values (which is desirable) + if self.gps_speed < FULL_STOP_MAX_SPEED: + _debug('Mapd *****: Route Not updated as car has Stopped ********') + return + + self.route.update(self.location_rad, self.bearing_rad, self.location_stdev) + if self.route.located: + _debug(f'Mapd *****: Route updated: \n{self.route}\n********') + return + + # if an old route did not mange to locate, attempt to regenerate form way collection. + self.route = self.way_collection.get_route(self.location_rad, self.bearing_rad, self.location_stdev) + _debug(f'Mapd *****: Failed to update location in route. Regenerated with route: \n{self.route}\n********') + + # We use the lock when updating the route, as it reads `way_collection` which can ben updated by + # a new query result from the _query_thread. + _debug('Mapd: Locking to update route.') + with self._lock: + update_proc() + + _debug('Mapd: Releasing Lock to update route') + + def publish(self, pm, sm): + # Ensure we have a route currently located + if self.route is None or not self.route.located: + return + + # Ensure we have a route update since last publish + if self.last_publish_fix_timestamp == self.last_route_update_fix_timestamp: + return + + self.last_publish_fix_timestamp = self.last_route_update_fix_timestamp + + speed_limit = self.route.current_speed_limit + next_speed_limit_section = self.route.next_speed_limit_section + turn_speed_limit_section = self.route.current_curvature_speed_limit_section + horizon_mts = self.gps_speed * LOOK_AHEAD_HORIZON_TIME + next_turn_speed_limit_sections = self.route.next_curvature_speed_limit_sections(horizon_mts) + current_road_name = self.route.current_road_name + + map_data_msg = messaging.new_message('liveMapData') + map_data_msg.valid = sm.all_alive_and_valid(service_list=['gpsLocationExternal']) + + map_data_msg.liveMapData.lastGpsTimestamp = self.last_gps.timestamp + map_data_msg.liveMapData.lastGpsLatitude = float(self.last_gps.latitude) + map_data_msg.liveMapData.lastGpsLongitude = float(self.last_gps.longitude) + map_data_msg.liveMapData.lastGpsSpeed = float(self.last_gps.speed) + map_data_msg.liveMapData.lastGpsBearingDeg = float(self.last_gps.bearingDeg) + map_data_msg.liveMapData.lastGpsAccuracy = float(self.last_gps.accuracy) + map_data_msg.liveMapData.lastGpsBearingAccuracyDeg = float(self.last_gps.bearingAccuracyDeg) + + map_data_msg.liveMapData.speedLimitValid = bool(speed_limit is not None) + map_data_msg.liveMapData.speedLimit = float(speed_limit if speed_limit is not None else 0.0) + map_data_msg.liveMapData.speedLimitAheadValid = bool(next_speed_limit_section is not None) + map_data_msg.liveMapData.speedLimitAhead = float(next_speed_limit_section.value + if next_speed_limit_section is not None else 0.0) + map_data_msg.liveMapData.speedLimitAheadDistance = float(next_speed_limit_section.start + if next_speed_limit_section is not None else 0.0) + + map_data_msg.liveMapData.turnSpeedLimitValid = bool(turn_speed_limit_section is not None) + map_data_msg.liveMapData.turnSpeedLimit = float(turn_speed_limit_section.value + if turn_speed_limit_section is not None else 0.0) + map_data_msg.liveMapData.turnSpeedLimitSign = int(turn_speed_limit_section.curv_sign + if turn_speed_limit_section is not None else 0) + map_data_msg.liveMapData.turnSpeedLimitEndDistance = float(turn_speed_limit_section.end + if turn_speed_limit_section is not None else 0.0) + map_data_msg.liveMapData.turnSpeedLimitsAhead = [float(s.value) for s in next_turn_speed_limit_sections] + map_data_msg.liveMapData.turnSpeedLimitsAheadDistances = [float(s.start) for s in next_turn_speed_limit_sections] + map_data_msg.liveMapData.turnSpeedLimitsAheadSigns = [float(s.curv_sign) for s in next_turn_speed_limit_sections] + + map_data_msg.liveMapData.currentRoadName = str(current_road_name if current_road_name is not None else "") + + pm.send('liveMapData', map_data_msg) + _debug(f'Mapd *****: Publish: \n{map_data_msg}\n********') + + +# provides live map data information +def mapd_thread(sm=None, pm=None): + mapd = MapD() + rk = Ratekeeper(1., print_delay_threshold=None) # Keeps rate at 1 hz + + # *** setup messaging + if sm is None: + sm = messaging.SubMaster(['gpsLocationExternal', 'controlsState']) + if pm is None: + pm = messaging.PubMaster(['liveMapData']) + + while True: + sm.update() + mapd.udpate_state(sm) + mapd.update_gps(sm) + mapd.updated_osm_data() + mapd.update_route() + mapd.publish(pm, sm) + rk.keep_time() + + +def main(sm=None, pm=None): + mapd_thread(sm, pm) + + +if __name__ == "__main__": + main() diff --git a/selfdrive/mapd/test/__init__.py b/selfdrive/mapd/test/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/selfdrive/mapd/test/mock_data.py b/selfdrive/mapd/test/mock_data.py new file mode 100644 index 00000000000000..e7263724783f42 --- /dev/null +++ b/selfdrive/mapd/test/mock_data.py @@ -0,0 +1,274 @@ +from selfdrive.mapd.lib.WayCollection import WayCollection +from selfdrive.mapd.lib.geo import vectors, R +from selfdrive.mapd.lib.NodesData import _MIN_NODE_DISTANCE, _ADDED_NODES_DIST, _SPLINE_EVAL_STEP, \ + _MIN_SPEED_SECTION_LENGTH, nodes_raw_data_array_for_wr, node_calculations, is_wr_a_valid_divertion_from_node, \ + spline_curvature_calculations, speed_limits_for_curvatures_data +from scipy.interpolate import splev, splprep +import numpy as np +import overpy + + +class MockNodesData(): + def __init__(self, way_coords): + self.degrees = np.array(way_coords) + self.radians = np.radians(self.degrees) + + # ***************** + # Expected code implementation nodes_data + self.v = vectors(self.radians) * R + self.d = np.linalg.norm(self.v, axis=1) + self.b = np.arctan2(self.v[:, 0], self.v[:, 1]) + self.v = np.concatenate(([[0., 0.]], self.v)) + self.dp = np.concatenate(([0.], self.d)) + self.dn = np.concatenate((self.d, [0.])) + self.dr = np.cumsum(self.dp, axis=0) + self.b = np.concatenate((self.b, [self.b[-1]])) + + # Expected code implementation spline_curvature_calculations + vect = self.v + dist_prev = self.dp + too_far_idxs = np.nonzero(self.dp >= _MIN_NODE_DISTANCE)[0] + for idx in too_far_idxs[::-1]: + dp = dist_prev[idx] # distance of vector that needs to be replaced by higher resolution vectors. + n = int(np.ceil(dp / _ADDED_NODES_DIST)) # number of vectors that need to be added. + new_v = vect[idx, :] / n # new relative vector to insert. + vect = np.delete(vect, idx, axis=0) # remove the relative vector to be replaced by the insertion of new vectors. + vect = np.insert(vect, [idx] * n, [new_v] * n, axis=0) # insert n new relative vectors + ds = np.cumsum(dist_prev, axis=0) + vs = np.cumsum(vect, axis=0) + tck, u = splprep([vs[:, 0], vs[:, 1]]) # pylint: disable=W0632 + n = max(int(ds[-1] / _SPLINE_EVAL_STEP), len(u)) + unew = np.arange(0, n + 1) / n + d1 = splev(unew, tck, der=1) + d2 = splev(unew, tck, der=2) + num = d1[0] * d2[1] - d1[1] * d2[0] + den = (d1[0]**2 + d1[1]**2)**(1.5) + self.curv = num / den + self.curv_ds = unew * ds[-1] + # ***************** + + +class MockCurveSection(): + def __init__(self, func, di=0., df=1000., step=10.): + self.di = di + self.df = df + self.n = (df - di) // step + self.u = np.arange(0, self.n + 1) / self.n + self.curv_ds = self.u * (df - di) + di + self.curv = func(self.u) + self.curv_abs = np.abs(self.curv) + self.curv_sec = np.column_stack((self.curv_abs, np.sign(self.curv), self.curv_ds)) + + +class MockOSMQueryResponse(): + def __init__(self, xml_path, query_center): + self.api = overpy.Overpass() + self.query_center = np.radians(np.array(query_center)) + + with open(xml_path, 'r') as f: + overpass_xml = f.read() + self.ways = self.api.parse_xml(overpass_xml).ways + + self.wayCollection = WayCollection(self.ways, self.query_center) + +class MockRouteData(): + def __init__(self, way_ids, way_collection, first_node_id): # way)ids must be in order forming a route. + self.wrs = [next(wr for wr in way_collection.way_relations if wr.id == way_id) for way_id in way_ids] + self.way_collection = way_collection + self.first_node_id = first_node_id + + def reset(self): + way_relations = self.wrs + wr_index = self.way_collection.wr_index + + # Nodes Data processing expects way relations to be updated with direction before running. + for idx, wr in enumerate(way_relations): + if idx == 0: + wr.update_direction_from_starting_node(self.first_node_id) + else: + wr.update_direction_from_starting_node(way_relations[idx - 1].last_node.id) + + # ***** Expected calculations + self._nodes_data = np.array([]) + self._divertions = [[]] + self._curvature_speed_sections_data = np.array([]) + way_count = len(way_relations) + if way_count == 0: + return + # We want all the nodes from the last way section + nodes_data = nodes_raw_data_array_for_wr(way_relations[-1]) + # For the ways before the last in the route we want all the nodes but the last, as that one is the first on + # the next section. Collect them, append last way node data and concatenate the numpy arrays. + if way_count > 1: + wrs_data = tuple([nodes_raw_data_array_for_wr(wr, drop_last=True) for wr in way_relations[:-1]]) + wrs_data += (nodes_data,) + nodes_data = np.concatenate(wrs_data) + # Get a subarray with lat, lon to compute the remaining node values. + lat_lon_array = nodes_data[:, [1, 2]] + points = np.radians(lat_lon_array) + # Ensure we have more than 3 points, if not calculations are not possible. + if len(points) <= 3: + return + vect, dist_prev, dist_next, dist_route, bearing = node_calculations(points) + # append calculations to nodes_data + # nodes_data structure: [id, lat, lon, speed_limit, x, y, dist_prev, dist_next, dist_route, bearing] + self._nodes_data = np.column_stack((nodes_data, vect, dist_prev, dist_next, dist_route, bearing)) + # Build route divertion options data from the wr_index. + wr_ids = [wr.id for wr in way_relations] + self._divertions = [[wr for wr in wr_index.way_relations_with_edge_node_id(node_id) + if is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids)] + for node_id in nodes_data[:, 0]] + # Store calculcations for curvature sections speed limits. We need more than 3 points to be able to process. + # _curvature_speed_sections_data structure: [dist_start, dist_stop, speed_limits, curv_sign] + if len(vect) > 3: + self._curv, self._curv_ds = spline_curvature_calculations(vect, dist_prev) + self._curvature_speed_sections_data = speed_limits_for_curvatures_data(self._curv, self._curv_ds) + # ***** + + +# Test data in degrees from this road: +# https://www.google.de/maps/@52.209263,13.8723137,13z +_WAY_NODES_COORDS_01 = [ + [52.1933703, 13.8723799], + [52.1939477, 13.8711273], + [52.1942004, 13.8705818], + [52.1945408, 13.8698496], + [52.1948447, 13.8691873], + [52.1950772, 13.8685726], + [52.1951168, 13.8684641], + [52.1956681, 13.8670323], + [52.1958716, 13.8664936], + [52.1964366, 13.8649875], + [52.1969283, 13.8636040], + [52.1970203, 13.8634430], + [52.1975486, 13.8626307], + [52.1976354, 13.8624971], + [52.1977827, 13.8621795], + [52.1978564, 13.8619220], + [52.1981843, 13.8604497], + [52.1982614, 13.8602140], + [52.1983351, 13.8600595], + [52.1992768, 13.8579824], + [52.1995107, 13.8574321], + [52.1995948, 13.8572604], + [52.1996818, 13.8571155], + [52.1998000, 13.8570029], + [52.2000659, 13.8568236], + [52.2003868, 13.8566005], + [52.2007182, 13.8564460], + [52.2008760, 13.8564117], + [52.2009865, 13.8564117], + [52.2011390, 13.8564202], + [52.2012267, 13.8564496], + [52.2012544, 13.8564577], + [52.2013179, 13.8564803], + [52.2020491, 13.8571756], + [52.2026014, 13.8576991], + [52.2027592, 13.8578879], + [52.2027960, 13.8579309], + [52.2028960, 13.8580939], + [52.2030170, 13.8583343], + [52.2036587, 13.8597076], + [52.2052946, 13.8633039], + [52.2064332, 13.8658435], + [52.2067856, 13.8666332], + [52.2068961, 13.8668477], + [52.2070777, 13.8670890], + [52.2073723, 13.8674409], + [52.2077457, 13.8679387], + [52.2083874, 13.8687455], + [52.2093341, 13.8699214], + [52.2099652, 13.8707540], + [52.2102282, 13.8712089], + [52.2104228, 13.8715694], + [52.2106122, 13.8718955], + [52.2107619, 13.8721756], + [52.2108695, 13.8723771], + [52.2110747, 13.8727610], + [52.2111514, 13.8729047], + [52.2114010, 13.8733718], + [52.2114694, 13.8735006], + [52.2115430, 13.8736636], + [52.2116086, 13.8737571], + [52.2116770, 13.8738172], + [52.2117611, 13.8738515], + [52.2118664, 13.8738566], + [52.2119322, 13.8738439], + [52.2121058, 13.8737924], + [52.2122583, 13.8737495], + [52.2123265, 13.8737260], + [52.2124213, 13.8736894], + [52.2127466, 13.8734888], + [52.2128263, 13.8734491], + [52.2131313, 13.8733117], + [52.2133943, 13.8731830], + [52.2136625, 13.8731057], + [52.2139465, 13.8730456], + [52.2143619, 13.8730113], + [52.2148773, 13.8729942], + [52.2152275, 13.8730325], + [52.2153110, 13.8730398], + [52.2157442, 13.8730848], + [52.2158833, 13.8731036]] + + +mockNodesData01 = MockNodesData(_WAY_NODES_COORDS_01) + +# OSM Query around B96 south of Berlin +mockOSMResponse01 = MockOSMQueryResponse('selfdrive/mapd/test/mock_osm_response_01.xml', + [52.31400353586984, 13.447158941786366]) + +# OSM Query on curvy town area south of Germany. +mockOSMResponse02 = MockOSMQueryResponse('selfdrive/mapd/test/mock_osm_response_02.xml', + [48.16573269276522, 9.81418473659117]) + +# OSM Query around Frankfurter alle in bErlin +mockOSMResponse03 = MockOSMQueryResponse('selfdrive/mapd/test/mock_osm_response_03.xml', + [52.516974999999995, 13.4432827]) + +mockWayCollection01 = WayCollection(mockOSMResponse01.ways, mockOSMResponse01.query_center) +mockWayCollection02 = WayCollection(mockOSMResponse02.ways, mockOSMResponse02.query_center) +mockWayCollection03 = WayCollection(mockOSMResponse03.ways, mockOSMResponse02.query_center) + +# Normal curvy Way. way id: 179532213 with 35 Nodes. +mockOSMWay_01_01_LongCurvy = next(way for way in mockOSMResponse01.ways if way.id == 179532213) + +# Looped way. way id: 29233907 +mockOSMWay_01_02_Loop = next(way for way in mockOSMResponse01.ways if way.id == 29233907) + +# Complex curvy road through town with intersections. way id:178450395 +mockOSMWay_02_01_CurvyTownWithIntersections = next(way for way in mockOSMResponse02.ways if way.id == 178450395) + +# Valid divertion for way 02_01 at node: 34785115. way id: 27955186 +mockOSMWay_02_02_Divertion_34785115 = next(way for way in mockOSMResponse02.ways if way.id == 27955186) + +# 3 node way. way id: 807781992 +mockOSMWay_02_03_Short_3_node_way = next(way for way in mockOSMResponse02.ways if way.id == 807781992) + +# data composing route 01 in way collection 02 +mockRouteData_02_01 = MockRouteData([60890967, 737120246, 601406617, 60890971, 178450395], mockWayCollection02, + first_node_id=201962346) + +# data composing route 02 in way collection 02. Single WR +mockRouteData_02_02_single_wr = MockRouteData([178450395], mockWayCollection02, first_node_id=762086638) + +# data composing route 03 in way collection 02. Multiple speed limits +mockRouteData_02_03 = MockRouteData([158799549, 798805532, 28707704, 158797898, 602249535, 602249536, 825823509, + 178449088, 916462523, 158796386], mockWayCollection02, + first_node_id=252601829) + +# data composing route 01 in way collection 03. Sharp turns +mockRouteData_03_01 = MockRouteData([4782480, 316869458, 4782482], mockWayCollection03, first_node_id=29271691) + +# 1000mt section with one full sin cycle as curv values. +mockCurveSectionSin = MockCurveSection(lambda x: np.sin(x * 2 * np.pi)) + +# 200mt section with changing curvature rate. +mockCurveSteepCurvChange = MockCurveSection(lambda x: 0.05 * x**3 - 0.007 * x**2 + 0.001 * x, df=200) + +# _MIN_SPEED_SECTION_LENGTH section with changing curvature rate. +mockCurveSteepCurvChangeShort = MockCurveSection( + lambda x: 0.05 * x**3 - 0.007 * x**2 + 0.001 * x, df=_MIN_SPEED_SECTION_LENGTH) + +# 200mt section with smooth changing curvature rate. no deviation over 2. +mockCurveSmoothCurveChange = MockCurveSection(lambda x: 0.0002 * x**3 - 0.001 * x**2 + 0.6 * x, df=200) diff --git a/selfdrive/mapd/test/mock_osm_response_01.xml b/selfdrive/mapd/test/mock_osm_response_01.xml new file mode 100644 index 00000000000000..d1e2a219ced8d3 --- /dev/null +++ b/selfdrive/mapd/test/mock_osm_response_01.xml @@ -0,0 +1,9908 @@ + + + +The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/selfdrive/mapd/test/mock_osm_response_02.xml b/selfdrive/mapd/test/mock_osm_response_02.xml new file mode 100644 index 00000000000000..bbcf72f3efee4d --- /dev/null +++ b/selfdrive/mapd/test/mock_osm_response_02.xml @@ -0,0 +1,11529 @@ + + + +The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/selfdrive/mapd/test/mock_osm_response_03.xml b/selfdrive/mapd/test/mock_osm_response_03.xml new file mode 100644 index 00000000000000..6efbfc8c42deff --- /dev/null +++ b/selfdrive/mapd/test/mock_osm_response_03.xml @@ -0,0 +1,10062 @@ + + + +The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/selfdrive/mapd/test/test_NodesData.py b/selfdrive/mapd/test/test_NodesData.py new file mode 100644 index 00000000000000..0b36c9713806eb --- /dev/null +++ b/selfdrive/mapd/test/test_NodesData.py @@ -0,0 +1,366 @@ +import unittest +import numpy as np +from selfdrive.mapd.lib.geo import DIRECTION +from selfdrive.config import Conversions as CV +from selfdrive.mapd.lib.WayRelation import WayRelation +from selfdrive.mapd.lib.NodesData import nodes_raw_data_array_for_wr, node_calculations, \ + spline_curvature_calculations, split_speed_section_by_sign, split_speed_section_by_curv_degree, speed_section, \ + speed_limits_for_curvatures_data, is_wr_a_valid_divertion_from_node, SpeedLimitSection, TurnSpeedLimitSection, \ + NodesData, NodeDataIdx +from selfdrive.mapd.test.mock_data import mockOSMWay_01_01_LongCurvy, mockNodesData01, mockCurveSectionSin, \ + mockCurveSteepCurvChange, mockCurveSteepCurvChangeShort, mockCurveSmoothCurveChange, \ + mockOSMWay_02_01_CurvyTownWithIntersections, mockOSMWay_02_02_Divertion_34785115, mockOSMWay_02_03_Short_3_node_way, \ + mockRouteData_02_01, mockRouteData_02_02_single_wr, mockRouteData_02_03, mockRouteData_03_01 +from numpy.testing import assert_array_almost_equal + + +class TestNodesDataFileFunctions(unittest.TestCase): + def test_nodes_raw_data_array_for_wr(self): + wr = WayRelation(mockOSMWay_01_01_LongCurvy) + data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float) + data = nodes_raw_data_array_for_wr(wr) + + assert_array_almost_equal(data, data_e) + + def test_nodes_raw_data_array_for_wr_flips_when_backwards(self): + wr = WayRelation(mockOSMWay_01_01_LongCurvy) + wr.direction = DIRECTION.BACKWARD + + data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float) + data_e = np.flip(data_e, axis=0) + + data = nodes_raw_data_array_for_wr(wr) + + assert_array_almost_equal(data, data_e) + + def test_nodes_raw_data_array_for_wr_drops_last(self): + wr = WayRelation(mockOSMWay_01_01_LongCurvy) + data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float)[:-1] + data = nodes_raw_data_array_for_wr(wr, drop_last=True) + + assert_array_almost_equal(data, data_e) + + def test_node_calculations(self): + points = mockNodesData01.radians + + v, dp, dn, dr, b = node_calculations(points) + + assert_array_almost_equal(v, mockNodesData01.v) + assert_array_almost_equal(dp, mockNodesData01.dp) + assert_array_almost_equal(dn, mockNodesData01.dn) + assert_array_almost_equal(dr, mockNodesData01.dr) + assert_array_almost_equal(b, mockNodesData01.b) + + def test_node_calculations_index_error(self): + points = mockNodesData01.radians[:2] + + with self.assertRaises(IndexError): + node_calculations(points) + + def test_spline_curvature_calculations(self): + vect = mockNodesData01.v + dist_prev = mockNodesData01.dp + + curv, curv_ds = spline_curvature_calculations(vect, dist_prev) + + assert_array_almost_equal(curv, mockNodesData01.curv) + assert_array_almost_equal(curv_ds, mockNodesData01.curv_ds) + + def test_spline_curvature_calculations_with_route_data(self): + mockRouteData_02_01.reset() + nodes_data = mockRouteData_02_01._nodes_data + vect = np.column_stack((nodes_data[:, 4], nodes_data[:, 5])) + dist_prev = nodes_data[:, 6] + + curv, curv_ds = spline_curvature_calculations(vect, dist_prev) + + assert_array_almost_equal(curv, mockRouteData_02_01._curv) + assert_array_almost_equal(curv_ds, mockRouteData_02_01._curv_ds) + + def test_split_speed_section_by_sign(self): + curv_sec = mockCurveSectionSin.curv_sec + new_secs = split_speed_section_by_sign(curv_sec) + + # 3 sections with matching initial and final distance + self.assertEqual(len(new_secs), 3) + self.assertEqual(new_secs[0][0][2], mockCurveSectionSin.di) + self.assertEqual(new_secs[2][-1][2], mockCurveSectionSin.df) + + # All new sections has same sign internally + for sec in new_secs: + self.assertEqual(np.average(sec, axis=0)[1], sec[0][1]) + + # Sections change sign + for idx in range(2): + self.assertNotEqual(new_secs[idx][0][1], new_secs[idx + 1][0][1]) + + # total items consistency + lenghts = [len(sec) for sec in new_secs] + self.assertEqual(len(curv_sec), sum(lenghts)) + + def test_split_speed_section_by_curv_degree(self): + curv_sec = mockCurveSteepCurvChange.curv_sec + new_secs = split_speed_section_by_curv_degree(curv_sec) + + # 3 sections with matching initial and final distance + self.assertEqual(len(new_secs), 3) + self.assertEqual(new_secs[0][0][2], mockCurveSteepCurvChange.di) + self.assertEqual(new_secs[2][-1][2], mockCurveSteepCurvChange.df) + + # Sections split at the right points + split_dist = [sec[-1][2] for sec in new_secs] + self.assertListEqual(split_dist, [50., 150., 200.]) + + def test_split_speed_section_by_curv_degree_does_nothing_if_short(self): + curv_sec = mockCurveSteepCurvChangeShort.curv_sec + new_secs = split_speed_section_by_curv_degree(curv_sec) + + self.assertEqual(len(new_secs), 1) + assert_array_almost_equal(curv_sec, new_secs[0]) + + def test_split_speed_section_by_curv_degree_does_nothing_if_no_substantial_change(self): + curv_sec = mockCurveSmoothCurveChange.curv_sec + new_secs = split_speed_section_by_curv_degree(curv_sec) + + self.assertEqual(len(new_secs), 1) + assert_array_almost_equal(curv_sec, new_secs[0]) + + def test_speed_section(self): + curv_sec = mockCurveSectionSin.curv_sec + + speed_secs = speed_section(curv_sec) + expected = np.array([0., 1000., 1.51657509, 1.]) + + assert_array_almost_equal(speed_secs, expected) + + def test_speed_limits_for_curvatures_data(self): + curv = mockCurveSectionSin.curv + curv_ds = mockCurveSectionSin.curv_ds + + expected = np.array([ + [10., 490., 1.51657509, 1.], + [510., 990., 1.51657509, -1.]]) + limits = speed_limits_for_curvatures_data(curv, curv_ds) + + assert_array_almost_equal(limits, expected) + + def test_is_wr_a_valid_divertion_from_node(self): + wr = WayRelation(mockOSMWay_02_01_CurvyTownWithIntersections) + mockOSMWay_02_02_Divertion_34785115.tags['oneway'] = 'yes' + wr_div = WayRelation(mockOSMWay_02_02_Divertion_34785115) + + # False if id already in route + wr_ids = [wr.id, wr_div.id] + self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 34785115, wr_ids)) + + # True if id not in route, node_id is edge and not prohibited + wr_ids = [wr.id, 11111, 22222] + self.assertTrue(is_wr_a_valid_divertion_from_node(wr_div, 34785115, wr_ids)) + + # False if id not in route, node_id is edge but prohibited (wrong direction from node 319503453) + self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 319503453, wr_ids)) + + # False if id not in route, node_id is not edge + self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 44444, wr_ids)) + + +class TestSpeedLimitSection(unittest.TestCase): + def test_speed_limit_section_init(self): + section = SpeedLimitSection(10., 20., 50.) + + self.assertEqual(section.start, 10.) + self.assertEqual(section.end, 20.) + self.assertEqual(section.value, 50.) + + +class TestTurnSpeedLimitSection(unittest.TestCase): + def test_turn_speed_limit_section_init(self): + section = TurnSpeedLimitSection(10., 20., 50., -1.) + + self.assertEqual(section.start, 10.) + self.assertEqual(section.end, 20.) + self.assertEqual(section.value, 50.) + self.assertEqual(section.curv_sign, -1.) + + +class TestNodesData(unittest.TestCase): + def test_init_with_empty_list(self): + nd = NodesData([], {}) + + self.assertEqual(len(nd._nodes_data), 0) + num_diverstions = sum([len(d) for d in nd._divertions]) + self.assertEqual(num_diverstions, 0) + self.assertEqual(len(nd._curvature_speed_sections_data), 0) + + def test_init_with_single_wr_includes_all_wr_nodes(self): + mockRouteData_02_02_single_wr.reset() + way_relations = mockRouteData_02_02_single_wr.wrs + wr_index = mockRouteData_02_02_single_wr.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + assert_array_almost_equal(nd._nodes_data, mockRouteData_02_02_single_wr._nodes_data) + assert_array_almost_equal(nd._curvature_speed_sections_data, + mockRouteData_02_02_single_wr._curvature_speed_sections_data) + self.assertListEqual(nd._divertions, mockRouteData_02_02_single_wr._divertions) + self.assertEqual(len(nd._nodes_data), len(way_relations[0].way.nodes)) + self.assertEqual(len(nd._curvature_speed_sections_data), 6) + num_diverstions = sum([len(d) for d in nd._divertions]) + self.assertEqual(num_diverstions, 6) + + def test_init_with_less_than_4_nodes(self): + wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way) + + nd = NodesData([wr_t], {}) + + self.assertEqual(len(nd._nodes_data), 0) + num_diverstions = sum([len(d) for d in nd._divertions]) + self.assertEqual(num_diverstions, 0) + self.assertEqual(len(nd._curvature_speed_sections_data), 0) + + def test_init_with_multiple_wr(self): + mockRouteData_02_01.reset() + way_relations = mockRouteData_02_01.wrs + wr_index = mockRouteData_02_01.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + assert_array_almost_equal(nd._nodes_data, mockRouteData_02_01._nodes_data) + assert_array_almost_equal(nd._curvature_speed_sections_data, mockRouteData_02_01._curvature_speed_sections_data) + self.assertListEqual(nd._divertions, mockRouteData_02_01._divertions) + self.assertEqual(len(nd._curvature_speed_sections_data), 9) + num_diverstions = sum([len(d) for d in nd._divertions]) + self.assertEqual(num_diverstions, 14) + + def test_count(self): + mockRouteData_02_01.reset() + way_relations = mockRouteData_02_01.wrs + wr_index = mockRouteData_02_01.way_collection.wr_index + num_n = sum([len(wr.way.nodes) for wr in way_relations]) - len(way_relations) + 1 + + nd = NodesData(way_relations, wr_index) + + self.assertEqual(nd.count, num_n) + + def test_get_on_empty(self): + wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way) + + nd = NodesData([wr_t], {}) + assert_array_almost_equal(nd.get(NodeDataIdx.node_id), np.array([])) + + def test_get_values(self): + mockRouteData_02_01.reset() + way_relations = mockRouteData_02_01.wrs + wr_index = mockRouteData_02_01.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + assert_array_almost_equal(nd.get(NodeDataIdx.node_id), mockRouteData_02_01._nodes_data[:, 0]) + assert_array_almost_equal(nd.get(NodeDataIdx.lat), mockRouteData_02_01._nodes_data[:, 1]) + assert_array_almost_equal(nd.get(NodeDataIdx.lon), mockRouteData_02_01._nodes_data[:, 2]) + assert_array_almost_equal(nd.get(NodeDataIdx.speed_limit), mockRouteData_02_01._nodes_data[:, 3]) + assert_array_almost_equal(nd.get(NodeDataIdx.x), mockRouteData_02_01._nodes_data[:, 4]) + assert_array_almost_equal(nd.get(NodeDataIdx.y), mockRouteData_02_01._nodes_data[:, 5]) + assert_array_almost_equal(nd.get(NodeDataIdx.dist_prev), mockRouteData_02_01._nodes_data[:, 6]) + assert_array_almost_equal(nd.get(NodeDataIdx.dist_next), mockRouteData_02_01._nodes_data[:, 7]) + assert_array_almost_equal(nd.get(NodeDataIdx.dist_route), mockRouteData_02_01._nodes_data[:, 8]) + assert_array_almost_equal(nd.get(NodeDataIdx.bearing), mockRouteData_02_01._nodes_data[:, 9]) + + def test_speed_limits_ahead_from_empty(self): + wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way) + + nd = NodesData([wr_t], {}) + self.assertEqual(len(nd.speed_limits_ahead(1, 10.)), 0) + + def test_speed_limits_ahead(self): + mockRouteData_02_03.reset() + way_relations = mockRouteData_02_03.wrs + wr_index = mockRouteData_02_03.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + # empty when ahead_idx is none. + self.assertEqual(len(nd.speed_limits_ahead(None, 10.)), 0) + + # All limist from 0 + all_limits = nd.speed_limits_ahead(1, nd.get(NodeDataIdx.dist_next)[0]) + self.assertEqual(len(all_limits), 4) # 4 limits on this mock road. + self.assertListEqual([sl.value for sl in all_limits], [v * CV.KPH_TO_MS for v in [50, 100, 50, 100]]) + for idx, sl in enumerate(all_limits): + self.assertTrue(sl.end > sl.start) + self.assertTrue(sl.value > 0.) + if idx == 0: + self.assertEqual(sl.start, 0.) + else: + self.assertEqual(sl.start, all_limits[idx - 1].end) + self.assertNotEqual(sl.value, all_limits[idx - 1].value) + + def test_distance_to_end_from_empty(self): + wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way) + + nd = NodesData([wr_t], {}) + self.assertIsNone(nd.distance_to_end(1, 10.)) + + def test_distance_to_end(self): + mockRouteData_02_03.reset() + way_relations = mockRouteData_02_03.wrs + wr_index = mockRouteData_02_03.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + # none when ahead_idx is none. + self.assertIsNone(nd.distance_to_end(None, 10.)) + + # From the begining + expected = np.sum(nd.get(NodeDataIdx.dist_next)) + self.assertAlmostEqual(nd.distance_to_end(1, nd.get(NodeDataIdx.dist_next)[0]), expected) + self.assertAlmostEqual(nd.get(NodeDataIdx.dist_route)[-1], expected) + + # From the node next to last + expected = nd.get(NodeDataIdx.dist_next)[-2] + self.assertAlmostEqual(nd.distance_to_end(nd.count - 2, 0.), expected) + + def test_distance_to_node(self): + mockRouteData_02_03.reset() + way_relations = mockRouteData_02_03.wrs + wr_index = mockRouteData_02_03.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + dist_to_node_ahead = 10. + node_id = 1887995486 # Some node id in the middle of the way. idx 50 + node_idx = np.nonzero(nd.get(NodeDataIdx.node_id) == node_id)[0][0] + + # none when ahead_idx is none. + self.assertIsNone(nd.distance_to_node(node_id, None, dist_to_node_ahead)) + + # From the begining + expected = nd.get(NodeDataIdx.dist_route)[node_idx] + self.assertAlmostEqual(nd.distance_to_node(node_id, 1, nd.get(NodeDataIdx.dist_next)[0]), expected) + + # From the end + expected = -np.sum(nd.get(NodeDataIdx.dist_next)[node_idx:]) + self.assertAlmostEqual(nd.distance_to_node(node_id, len(nd.get(NodeDataIdx.node_id)) - 1, 0.), expected) + + # From some node behind including dist to node ahead + ahead_idx = node_idx - 10 + expected = np.sum(nd.get(NodeDataIdx.dist_next)[ahead_idx:node_idx]) + dist_to_node_ahead + self.assertAlmostEqual(nd.distance_to_node(node_id, ahead_idx, dist_to_node_ahead), expected) + + # From some node ahead including dist to node ahead + ahead_idx = node_idx + 10 + expected = -np.sum(nd.get(NodeDataIdx.dist_next)[node_idx:ahead_idx]) + dist_to_node_ahead + self.assertAlmostEqual(nd.distance_to_node(node_id, ahead_idx, dist_to_node_ahead), expected) + + def test_test(self): + mockRouteData_03_01.reset() + way_relations = mockRouteData_03_01.wrs + wr_index = mockRouteData_03_01.way_collection.wr_index + + nd = NodesData(way_relations, wr_index) + + print(nd._nodes_data[:, 0]) + + all_limits = nd.curvatures_speed_limit_sections_ahead(16, 0.) + print(all_limits) + +# TODO: Missing tests for curvatures_speed_limit_sections_ahead and possible_divertions diff --git a/selfdrive/mapd/test/test_WayRelation.py b/selfdrive/mapd/test/test_WayRelation.py new file mode 100644 index 00000000000000..75ee8e209a3645 --- /dev/null +++ b/selfdrive/mapd/test/test_WayRelation.py @@ -0,0 +1,651 @@ +import copy +import unittest +import numpy as np +from unittest import mock +from numpy.testing import assert_array_almost_equal +from datetime import datetime as dt, timezone, timedelta +from selfdrive.config import Conversions as CV +from selfdrive.mapd.lib.WayRelation import WayRelation, is_osm_time_condition_active, \ + conditional_speed_limit_for_osm_tag_limit_string, speed_limit_for_osm_tag_limit_string +from selfdrive.mapd.config import LANE_WIDTH +from selfdrive.mapd.lib.geo import DIRECTION, R, vectors +from selfdrive.mapd.test.mock_data import mockOSMWay_01_01_LongCurvy, mockOSMWay_01_02_Loop, \ + mockOSMWay_02_01_CurvyTownWithIntersections + + +class TestWayRelationFileFunctions(unittest.TestCase): + def test_speed_limit_for_osm_tag_limit_string(self): + values = [ + None, # Invalid + "1000", # Invalid + "60 kph", # Invalid + "100", + "30 mph", + "DE:zone:40", + "DE:zone:50 mph", + "AR:urban", + "CZ:pedestrian_zone", + "DK:urban", + "DK:rural", + "DK:motorway", + "DE:living_street", + "DE:residential", + "DE:urban", + "DE:rural", + "DE:trunk", # No limit + "DE:motorway", # No limit + "GB:nsl_restricted", + "GB:nsl_single", + "GB:nsl_dual", + "GB:motorway", + "GB:invalid", # Invalid + ] + + expected = [ + 0., + 0., + 0., + 100. * CV.KPH_TO_MS, + 30. * CV.MPH_TO_MS, + 40. * CV.KPH_TO_MS, + 50. * CV.MPH_TO_MS, + 40. * CV.KPH_TO_MS, + 20. * CV.KPH_TO_MS, + 50. * CV.KPH_TO_MS, + 80. * CV.KPH_TO_MS, + 130. * CV.KPH_TO_MS, + 7. * CV.KPH_TO_MS, + 30. * CV.KPH_TO_MS, + 50. * CV.KPH_TO_MS, + 100. * CV.KPH_TO_MS, + 0., + 0., + 30. * CV.MPH_TO_MS, + 60. * CV.MPH_TO_MS, + 70. * CV.MPH_TO_MS, + 70. * CV.MPH_TO_MS, + 0., + ] + + result = [speed_limit_for_osm_tag_limit_string(sls) for sls in values] + + self.assertEqual(result, expected) + + @mock.patch('selfdrive.mapd.lib.WayRelation.dt') + def test_is_osm_time_condition_active(self, mock_dt): + tz = timezone(timedelta(hours=1), 'berlin') + wed_10_10_am = dt(2021, 9, 1, 10, 10, 0) + mock_dt.now.return_value = wed_10_10_am + mock_dt.tzinfo = tz + mock_dt.combine = dt.combine + mock_dt.strptime = dt.strptime + + values = [ + "WE", # Invalid + "We", + "Mo", + "Fr", + "Tu-Th", + "10:00", # Invalid + "10:00-10:30", + "We 10:00-10:30", + "SU 10:00-10:30", # Valid, SU string not considered a day string. + "Sa 10:00-10:30", + "Tu-Th 10:00-10:30", + ] + + expected = [ + False, # Invalid + True, + False, + False, + True, + False, # Invalid + True, + True, + True, + False, + True, + ] + + result = [is_osm_time_condition_active(cs) for cs in values] + + self.assertEqual(result, expected) + + @mock.patch('selfdrive.mapd.lib.WayRelation.dt') + def test_conditional_speed_limit_for_osm_tag_limit_string(self, mock_dt): + tz = timezone(timedelta(hours=1), 'berlin') + wed_10_10_am = dt(2021, 9, 1, 10, 10, 0) + mock_dt.now.return_value = wed_10_10_am + mock_dt.tzinfo = tz + mock_dt.combine = dt.combine + mock_dt.strptime = dt.strptime + + values = [ + None, # Invalid + "Hola", # Invalid + "100 @ (WE)", # Invalid + "x @ (We)", # Invalid + "100 @ (We)", + "100 @ (Mo)", + "100 @ (Fr)", + "100 @ (Tu-Th)", + "100 @ (10:00)", # Invalid + "100 @ (10:00-10:30)", + "100 @ (We 10:00-10:30)", + "100 @ (SU 10:00-10:30)", # Valid, SU string not considered a day string. + "100 @ (Sa 10:00-10:30)", + "100 @ (Tu-Th 10:00-10:30)", + "100 @ (Mo-Th;Su)", + "100 @ (Mo Th;Fr-Sa)", + "100 @ (Fr-Su;Mo-Tu)", + "100 @ (10:00-10:30;15:00-16:00)", + "100 @ (We;Mo-Tu)", + "100 @ (We 10:00-10:30;Th 15:00-16:00)", + "100 @ (Tu 10:00-10:30;Th 15:00-16:00)", + ] + + _100 = 100. * CV.KPH_TO_MS + + expected = [ + 0., # Invalid + 0., # Invalid + 0., # Invalid + 0., # Invalid + _100, + 0., + 0., + _100, + 0., # Invalid + _100, + _100, + _100, + 0., + _100, + _100, + _100, + 0., + _100, + _100, + _100, + 0. + ] + + result = [conditional_speed_limit_for_osm_tag_limit_string(ls) for ls in values] + + self.assertEqual(result, expected) + + +class TestWayRelation(unittest.TestCase): + def test_way_relation_init(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + + nodes_np_expected = np.radians(np.array([[nd.lat, nd.lon] for nd in wayRelation.way.nodes], dtype=float)) + v = vectors(wayRelation._nodes_np) + way_distances_expected = np.linalg.norm(v * R, axis=1) + way_bearings_expected = np.arctan2(v[:, 0], v[:, 1]) + bbox_expected = np.array([ + [0.91321784, 0.2346417], + [0.91344672, 0.23475751]]) + + self.assertEqual(wayRelation.way.id, 179532213) + self.assertIsNone(wayRelation.parent_wr_id) + self.assertEqual(wayRelation.direction, DIRECTION.NONE) + self.assertEqual(wayRelation._speed_limit, None) + self.assertEqual(wayRelation._one_way, 'yes') + self.assertEqual(wayRelation.name, None) + self.assertEqual(wayRelation.ref, 'B 96') + self.assertEqual(wayRelation.highway_type, 'trunk') + self.assertEqual(wayRelation.highway_rank, 10) + self.assertEqual(wayRelation.lanes, 2) + assert_array_almost_equal(wayRelation._nodes_np, nodes_np_expected) + assert_array_almost_equal(wayRelation._way_distances, way_distances_expected) + assert_array_almost_equal(wayRelation._way_bearings, way_bearings_expected) + assert_array_almost_equal(wayRelation.bbox, bbox_expected) + self.assertEqual(wayRelation.edge_nodes_ids, [wayRelation.way.nodes[0].id, wayRelation.way.nodes[-1].id]) + + def test_way_relation_init_with_parent(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy, parent=WayRelation(mockOSMWay_01_02_Loop)) + + self.assertEqual(wayRelation.way.id, 179532213) + self.assertEqual(wayRelation.parent_wr_id, 29233907) + + def test_way_relation_equality(self): + wayRelation1 = WayRelation(mockOSMWay_01_01_LongCurvy) + wayRelation2 = copy.copy(wayRelation1) + wayRelation3 = copy.deepcopy(wayRelation1) + wayRelation3.way.id = 123 + + self.assertEqual(wayRelation1, wayRelation2) + self.assertNotEqual(wayRelation1, wayRelation3) + + def test_way_relation_reset_location_variables(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + self.make_wayRelation_location_dirty(wayRelation) + + wayRelation.reset_location_variables() + + self.assert_wayRelation_variables_reset(wayRelation) + + def test_way_relation_id(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + + self.assertEqual(wayRelation.id, 179532213) + + def test_way_relation_road_name(self): + # road name when no tag for name or ref + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + self.assertIsNone(wayRelation.road_name) + # road name based on ref tag + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + self.assertEqual(wayRelation.road_name, "B 96") + # road name based on name tag + wayRelation = WayRelation(mockOSMWay_02_01_CurvyTownWithIntersections) + self.assertEqual(wayRelation.road_name, "Hauptstraße") + + def test_way_relation_update_resets_on_update(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + self.make_wayRelation_location_dirty(wayRelation) + location_rad = np.array([0., 0.]) # Location outside bbox + + wayRelation.update(location_rad, 0., 10.) + + self.assertFalse(wayRelation.is_location_in_bbox(location_rad)) + self.assert_wayRelation_variables_reset(wayRelation) + + def test_way_relation_update_only_resets_if_no_possible_found(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + location_rad = wayRelation.bbox[0] # Location inside bbox but outside actual way (due to padding) + + wayRelation.update(location_rad, 0., 10.) + + self.assertTrue(wayRelation.is_location_in_bbox(location_rad)) + self.assert_wayRelation_variables_reset(wayRelation) + + def test_way_relation_updates_in_the_correct_direction_with_correct_property_values(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + location_rad = np.radians(np.array([52.32855593146639, 13.445320150125069])) + bearing_rad = 0. + + wayRelation.update(location_rad, bearing_rad, 10.) + + self.assertTrue(wayRelation.is_location_in_bbox(location_rad)) + self.assertEqual(wayRelation.direction, DIRECTION.FORWARD) + self.assertEqual(wayRelation.ahead_idx, 17) + self.assertEqual(wayRelation.behind_idx, 16) + self.assertAlmostEqual(wayRelation._distance_to_way, 3.43290781621360) + self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.320717420388962) + self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 25.4998961709014) + self.assertTrue(wayRelation.active) + self.assertFalse(wayRelation.diverting) + assert_array_almost_equal(wayRelation.location_rad, location_rad) + self.assertEqual(wayRelation.bearing_rad, bearing_rad) + self.assertIsNone(wayRelation._speed_limit) + + bearing_rad = 180. + + wayRelation.update(location_rad, bearing_rad, 10.) + + self.assertTrue(wayRelation.is_location_in_bbox(location_rad)) + self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD) + self.assertEqual(wayRelation.ahead_idx, 16) + self.assertEqual(wayRelation.behind_idx, 17) + self.assertAlmostEqual(wayRelation._distance_to_way, 3.43290781621360) + self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.9507682562504284) + self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 11.11623371145368) + self.assertTrue(wayRelation.active) + self.assertFalse(wayRelation.diverting) + assert_array_almost_equal(wayRelation.location_rad, location_rad) + self.assertEqual(wayRelation.bearing_rad, bearing_rad) + self.assertIsNone(wayRelation._speed_limit) + + def test_way_relation_updates_with_location_closest_to_way_when_multiple_possible(self): + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + location_rad = np.radians(np.array([52.313303275461564, 13.437729236325788])) + bearing_rad = np.radians(10.) + + wayRelation.update(location_rad, bearing_rad, 10.) + + self.assertTrue(wayRelation.is_location_in_bbox(location_rad)) + self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD) + self.assertEqual(wayRelation.ahead_idx, 26) + self.assertEqual(wayRelation.behind_idx, 27) + self.assertAlmostEqual(wayRelation._distance_to_way, 10.151775235257011) + self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.06371131069242782) + self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 10.174073707120915) + self.assertTrue(wayRelation.active) + self.assertFalse(wayRelation.diverting) + assert_array_almost_equal(wayRelation.location_rad, location_rad) + self.assertEqual(wayRelation.bearing_rad, bearing_rad) + self.assertIsNone(wayRelation._speed_limit) + + def test_way_relation_updates_will_become_inactive_if_too_far_from_way(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + # Location is 24.9 mts away from the way. There are 2 Lanes in this way. + location_rad = np.radians(np.array([52.328634560607746, 13.445609877522788])) + location_stdev = 5.5 # threshold is 4 * location_stdev + LANE_WIDTH + distance_threshold = 4. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2. + + wayRelation.update(location_rad, 0., location_stdev) + self.assertTrue(wayRelation.active) + self.assertLess(wayRelation._distance_to_way, distance_threshold) + + location_stdev = 5. + + wayRelation.update(location_rad, 0., location_stdev) + self.assertFalse(wayRelation.active) + + def test_way_relation_updates_will_update_diverting_correctly(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + # Location is 24.9 mts away from the way. There are 2 Lanes in this way. + location_rad = np.radians(np.array([52.328634560607746, 13.445609877522788])) + location_stdev = 11. + distance_threshold = 2. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2. + + wayRelation.update(location_rad, 0., location_stdev) + + self.assertLess(wayRelation._distance_to_way, distance_threshold) + self.assertFalse(wayRelation.diverting) + + location_stdev = 10. + distance_threshold = 2. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2. + + wayRelation.update(location_rad, 0., location_stdev) + + self.assertGreater(wayRelation._distance_to_way, distance_threshold) + self.assertTrue(wayRelation.diverting) + + def test_way_relation_update_direction_from_starting_node_resets_speed_limit(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + wayRelation._speed_limit = 10. + + wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[0].id) + + self.assertIsNone(wayRelation._speed_limit) + + def test_way_relation_update_direction_from_starting_node_updates_correctly(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[0].id) + self.assertEqual(wayRelation.direction, DIRECTION.FORWARD) + + wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[-1].id) + self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD) + + wayRelation.update_direction_from_starting_node(0) + self.assertEqual(wayRelation.direction, DIRECTION.NONE) + + def test_way_relation_is_location_in_bbox(self): + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + bbox = wayRelation.bbox + + loc_avg = np.average(bbox, axis=0) + loc_min = np.min(bbox, axis=0) + loc_max = np.max(bbox, axis=0) + + locations = [ + loc_avg, + loc_min, + loc_max, + [loc_avg[0], loc_min[1]], + [loc_avg[0], loc_max[1]], + [loc_min[0], loc_avg[1]], + [loc_max[0], loc_avg[1]], + loc_min - 0.1, + loc_max + 0.1, + [loc_avg[0], loc_min[1] - 0.1], + [loc_avg[0], loc_max[1] + 0.1], + [loc_min[0] - 0.1, loc_avg[1]], + [loc_max[0] + 0.1, loc_avg[1]], + ] + + is_in = [wayRelation.is_location_in_bbox(loc) for loc in locations] + + self.assertEqual(is_in, [True, True, True, True, True, True, True, False, False, False, False, False, False]) + + def test_way_relation_speed_limit_when_set(self): + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + wayRelation._speed_limit = 10. + + self.assertEqual(wayRelation.speed_limit, 10.) + + @mock.patch('selfdrive.mapd.lib.WayRelation.dt') + def test_way_relation_speed_limit_conditional(self, mock_dt): + tz = timezone(timedelta(hours=1), 'berlin') + wed_10_10_am = dt(2021, 9, 1, 10, 10, 0) + mock_dt.now.return_value = wed_10_10_am + mock_dt.tzinfo = tz + mock_dt.combine = dt.combine + mock_dt.strptime = dt.strptime + + # Reset all tags before teting + mockOSMWay_01_02_Loop.tags = {} + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + + # No Value + self.assertEqual(wayRelation.speed_limit, 0.) + + # Value on both directions + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed:conditional"] = "100 @ (We 10:00-10:30)" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + # Value on forward + wayRelation.way.tags.pop("maxspeed:conditional") + wayRelation._speed_limit = None + wayRelation.direction = DIRECTION.FORWARD + self.assertEqual(wayRelation.speed_limit, 0.) + + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed:forward:conditional"] = "100 @ (We 10:00-10:30)" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + # Value on backward + wayRelation._speed_limit = None + wayRelation.direction = DIRECTION.BACKWARD + self.assertEqual(wayRelation.speed_limit, 0.) + + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed:backward:conditional"] = "100 @ (We 10:00-10:30)" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + def test_way_relation_speed_limit_maxspeed(self): + # Reset all tags before teting + mockOSMWay_01_02_Loop.tags = {} + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + + # No Value + self.assertEqual(wayRelation.speed_limit, 0.) + + # Value on both directions + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed"] = "100" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + # Value on forward + wayRelation.way.tags.pop("maxspeed") + wayRelation._speed_limit = None + wayRelation.direction = DIRECTION.FORWARD + self.assertEqual(wayRelation.speed_limit, 0.) + + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed:forward"] = "100" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + # Value on backward + wayRelation._speed_limit = None + wayRelation.direction = DIRECTION.BACKWARD + self.assertEqual(wayRelation.speed_limit, 0.) + + wayRelation._speed_limit = None + wayRelation.way.tags["maxspeed:backward"] = "100" + self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS) + + def test_way_relation_active_bearing_delta_reflects_internal_value(self): + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + wayRelation._active_bearing_delta = 10. + self.assertEqual(wayRelation.active_bearing_delta, 10.) + + def test_way_relation_is_one_way(self): + # Setup initial tags + mockOSMWay_01_02_Loop.tags = { + 'oneway': 'yes', + 'highway': 'unclassified' + } + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + + # oneway = yes + self.assertTrue(wayRelation.is_one_way) + + # oneway non existing + wayRelation._one_way = None + self.assertFalse(wayRelation.is_one_way) + + # highway = motorway + wayRelation.highway_type = 'motorway' + self.assertTrue(wayRelation.is_one_way) + + def test_way_relation_is_prohibited(self): + # Setup initial tags + mockOSMWay_01_02_Loop.tags = { + 'oneway': 'yes' + } + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + + # Direction undefined + wayRelation.direction = DIRECTION.NONE + self.assertTrue(wayRelation.is_prohibited) + + # oneway = yes + wayRelation.direction = DIRECTION.BACKWARD + self.assertTrue(wayRelation.is_prohibited) + + wayRelation.direction = DIRECTION.FORWARD + self.assertFalse(wayRelation.is_prohibited) + + # oneway non existing + wayRelation._one_way = None + self.assertFalse(wayRelation.is_one_way) + + wayRelation.direction = DIRECTION.BACKWARD + self.assertFalse(wayRelation.is_prohibited) + + def test_way_relation_distance_to_way_reflects_internal_value(self): + wayRelation = WayRelation(mockOSMWay_01_02_Loop) + wayRelation._distance_to_way = 10. + self.assertEqual(wayRelation.distance_to_way, 10.) + + def test_way_relation_node_ahead(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + # ahead_ids is None on init + self.assertIsNone(wayRelation.node_ahead) + + wayRelation.ahead_idx = 15 + self.assertEqual(wayRelation.node_ahead, wayRelation.way.nodes[15]) + + def test_way_relation_last_node(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + # direction is NONE on init + self.assertIsNone(wayRelation.last_node) + + # forward + wayRelation.direction = DIRECTION.FORWARD + self.assertEqual(wayRelation.last_node, wayRelation.way.nodes[-1]) + + # backward + wayRelation.direction = DIRECTION.BACKWARD + self.assertEqual(wayRelation.last_node, wayRelation.way.nodes[0]) + + def test_way_relation_last_node_coordinates(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + # direction is NONE on init + self.assertIsNone(wayRelation.last_node_coordinates) + + # forward + wayRelation.direction = DIRECTION.FORWARD + coords = np.radians(np.array([wayRelation.way.nodes[-1].lat, wayRelation.way.nodes[-1].lon], dtype=float)) + assert_array_almost_equal(wayRelation.last_node_coordinates, coords) + + # backward + wayRelation.direction = DIRECTION.BACKWARD + coords = np.radians(np.array([wayRelation.way.nodes[0].lat, wayRelation.way.nodes[0].lon], dtype=float)) + assert_array_almost_equal(wayRelation.last_node_coordinates, coords) + + def test_way_relation_node_before_edge_coordinates(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + + coords = wayRelation.node_before_edge_coordinates(0) + assert_array_almost_equal(coords, np.array([0., 0.])) + + coords = wayRelation.node_before_edge_coordinates(wayRelation.way.nodes[0].id) + coords_e = np.radians(np.array([wayRelation.way.nodes[1].lat, wayRelation.way.nodes[1].lon], dtype=float)) + assert_array_almost_equal(coords, coords_e) + + coords = wayRelation.node_before_edge_coordinates(wayRelation.way.nodes[-1].id) + coords_e = np.radians(np.array([wayRelation.way.nodes[-2].lat, wayRelation.way.nodes[-2].lon], dtype=float)) + assert_array_almost_equal(coords, coords_e) + + def test_way_relation_split_no_matching_node(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + + wrs = wayRelation.split(0) + self.assertEqual(len(wrs), 0) + + def test_way_relation_split_use_correct_ids(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + + wrs = wayRelation.split(wayRelation._nodes_ids[5], [-100, -200]) + self.assertEqual(wrs[0].id, -100) + self.assertEqual(wrs[1].id, -200) + + def test_way_relation_split_on_edge_node(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + edge_node_ids = wayRelation.edge_nodes_ids + + for edge_node_id in edge_node_ids: + wrs = wayRelation.split(edge_node_id) + self.assertEqual(len(wrs), 1) + self.assertEqual(wrs[0], wayRelation) + self.assertEqual(wrs[0].way.tags, wayRelation.way.tags) + + def test_way_relation_split_on_internal_node(self): + wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy) + way_ids = [-10, -20] + + for idx, node_id in enumerate(wayRelation._nodes_ids): + if idx == 0 or idx == len(wayRelation._nodes_ids) - 1: + continue + wrs = wayRelation.split(node_id, way_ids) + self.assertEqual(len(wrs), 2) + assert_array_almost_equal(wrs[0]._nodes_ids, wayRelation._nodes_ids[:idx + 1]) + assert_array_almost_equal(wrs[1]._nodes_ids, wayRelation._nodes_ids[idx:]) + self.assertIn(node_id, wrs[0].edge_nodes_ids) + self.assertIn(node_id, wrs[1].edge_nodes_ids) + self.assertEqual(wrs[0].way.tags, wayRelation.way.tags) + self.assertEqual(wrs[1].way.tags, wayRelation.way.tags) + self.assertEqual(way_ids, [wr.id for wr in wrs]) + + # Helpers + def make_wayRelation_location_dirty(self, wayRelation): + wayRelation.distance_to_node_ahead = 10. + wayRelation.location_rad = 0.8 + wayRelation.bearing_rad = 2. + wayRelation.active = True + wayRelation.diverting = True + wayRelation.ahead_idx = 5 + wayRelation.behind_idx = 4 + wayRelation._active_bearing_delta = 3. + wayRelation._distance_to_way = 20. + + def assert_wayRelation_variables_reset(self, wayRelation): + self.assertEqual(wayRelation.distance_to_node_ahead, 0.) + self.assertIsNone(wayRelation.location_rad) + self.assertIsNone(wayRelation.bearing_rad) + self.assertFalse(wayRelation.active) + self.assertFalse(wayRelation.diverting) + self.assertIsNone(wayRelation.ahead_idx) + self.assertIsNone(wayRelation.behind_idx) + self.assertIsNone(wayRelation._active_bearing_delta) + self.assertIsNone(wayRelation._distance_to_way) + + def wayRelation_mid_point_rad(self, wayRelation): + return np.average(wayRelation.bbox, axis=0) diff --git a/selfdrive/mapd/test/test_geo.py b/selfdrive/mapd/test/test_geo.py new file mode 100644 index 00000000000000..a18fe717b06769 --- /dev/null +++ b/selfdrive/mapd/test/test_geo.py @@ -0,0 +1,234 @@ +import unittest +from selfdrive.mapd.lib.geo import vectors, ref_vectors, bearing_to_points, distance_to_points +import numpy as np +from numpy.testing import assert_array_almost_equal +from selfdrive.mapd.test.mock_data import mockNodesData01 + + +class TestMapsdGeoLibrary(unittest.TestCase): + def test_vectors(self): + points = mockNodesData01.radians + expected = np.array([ + [-1.34011951e-05, 1.00776468e-05], + [-5.83610920e-06, 4.41046897e-06], + [-7.83348567e-06, 5.94114032e-06], + [-7.08560788e-06, 5.30408795e-06], + [-6.57632550e-06, 4.05791838e-06], + [-1.16077872e-06, 6.91151252e-07], + [-1.53178098e-05, 9.62215139e-06], + [-5.76314175e-06, 3.55176643e-06], + [-1.61124141e-05, 9.86127759e-06], + [-1.48006628e-05, 8.58192512e-06], + [-1.72237209e-06, 1.60570482e-06], + [-8.68985228e-06, 9.22062311e-06], + [-1.42922812e-06, 1.51494711e-06], + [-3.39761486e-06, 2.57087743e-06], + [-2.75467373e-06, 1.28631255e-06], + [-1.57501989e-05, 5.72309451e-06], + [-2.52143954e-06, 1.34565295e-06], + [-1.65278643e-06, 1.28630942e-06], + [-2.22196114e-05, 1.64360838e-05], + [-5.88675934e-06, 4.08234746e-06], + [-1.83673390e-06, 1.46782408e-06], + [-1.55004206e-06, 1.51843800e-06], + [-1.20451533e-06, 2.06298011e-06], + [-1.91801338e-06, 4.64083285e-06], + [-2.38653483e-06, 5.60076524e-06], + [-1.65269781e-06, 5.78402290e-06], + [-3.66908309e-07, 2.75412965e-06], + [0.00000000e+00, 1.92858882e-06], + [9.09242615e-08, 2.66162711e-06], + [3.14490354e-07, 1.53065382e-06], + [8.66452477e-08, 4.83456208e-07], + [2.41750593e-07, 1.10828411e-06], + [7.43745228e-06, 1.27618831e-05], + [5.59968054e-06, 9.63947367e-06], + [2.01951467e-06, 2.75413219e-06], + [4.59952643e-07, 6.42281301e-07], + [1.74353749e-06, 1.74533121e-06], + [2.57144338e-06, 2.11185266e-06], + [1.46893187e-05, 1.11999169e-05], + [3.84659229e-05, 2.85527952e-05], + [2.71627936e-05, 1.98727946e-05], + [8.44632540e-06, 6.15058628e-06], + [2.29420323e-06, 1.92859222e-06], + [2.58083439e-06, 3.16952222e-06], + [3.76373643e-06, 5.14174911e-06], + [5.32416098e-06, 6.51707770e-06], + [8.62890928e-06, 1.11998258e-05], + [1.25762497e-05, 1.65231340e-05], + [8.90452991e-06, 1.10148240e-05], + [4.86505726e-06, 4.59023120e-06], + [3.85545276e-06, 3.39642031e-06], + [3.48753893e-06, 3.30566145e-06], + [2.99557303e-06, 2.61276368e-06], + [2.15496788e-06, 1.87797727e-06], + [4.10564937e-06, 3.58142649e-06], + [1.53680853e-06, 1.33866906e-06], + [4.99540175e-06, 4.35635790e-06], + [1.37744970e-06, 1.19380643e-06], + [1.74319821e-06, 1.28456429e-06], + [9.99931238e-07, 1.14493663e-06], + [6.42735560e-07, 1.19380547e-06], + [3.66818436e-07, 1.46782199e-06], + [5.45413874e-08, 1.83783170e-06], + [-1.35818548e-07, 1.14842666e-06], + [-5.50758101e-07, 3.02989178e-06], + [-4.58785270e-07, 2.66162724e-06], + [-2.51315555e-07, 1.19031459e-06], + [-3.91409773e-07, 1.65457223e-06], + [-2.14525206e-06, 5.67755902e-06], + [-4.24558096e-07, 1.39102753e-06], + [-1.46936730e-06, 5.32325561e-06], + [-1.37632061e-06, 4.59021715e-06], + [-8.26642899e-07, 4.68097349e-06], + [-6.42702724e-07, 4.95673534e-06], + [-3.66796960e-07, 7.25009780e-06], + [-1.82861669e-07, 8.99542699e-06], + [4.09564134e-07, 6.11214315e-06], + [7.80629912e-08, 1.45734993e-06], + [4.81205526e-07, 7.56076647e-06], + [2.01036346e-07, 2.42775302e-06]]) + + v = vectors(points) + assert_array_almost_equal(v, expected) + + def test_ref_vectors(self): + points = mockNodesData01.radians + expected = np.array([ + [1.59924145e-04, -1.07153714e-04], + [1.46520873e-04, -9.70788297e-05], + [1.40683931e-04, -9.26694631e-05], + [1.32849368e-04, -8.67297434e-05], + [1.25762852e-04, -8.14268689e-05], + [1.19185869e-04, -7.73700167e-05], + [1.18024984e-04, -7.66790438e-05], + [1.02705711e-04, -6.70592230e-05], + [9.69420991e-05, -6.35082196e-05], + [8.08284530e-05, -5.36489556e-05], + [6.60268961e-05, -4.50685727e-05], + [6.43043874e-05, -4.34630144e-05], + [5.56137708e-05, -3.42431117e-05], + [5.41844341e-05, -3.27282671e-05], + [5.07866397e-05, -3.01576270e-05], + [4.80318817e-05, -2.88714948e-05], + [3.22813286e-05, -2.31493755e-05], + [2.97598330e-05, -2.18038275e-05], + [2.81069973e-05, -2.05175815e-05], + [5.88679032e-06, -4.08230278e-06], + [0.00000000e+00, 0.00000000e+00], + [-1.83673390e-06, 1.46782408e-06], + [-3.38677236e-06, 2.98626574e-06], + [-4.59127869e-06, 5.04925111e-06], + [-6.50926460e-06, 9.69009532e-06], + [-8.89575243e-06, 1.52908806e-05], + [-1.05483839e-05, 2.10749224e-05], + [-1.09152548e-05, 2.38290571e-05], + [-1.09152276e-05, 2.57576459e-05], + [-1.08242659e-05, 2.84192717e-05], + [-1.05097542e-05, 2.99499212e-05], + [-1.04231024e-05, 3.04333762e-05], + [-1.01813369e-05, 3.15416571e-05], + [-2.74371711e-06, 4.43034426e-05], + [2.85599752e-06, 5.39428964e-05], + [4.87550206e-06, 5.66970360e-05], + [5.33545066e-06, 5.73393202e-05], + [7.07897615e-06, 5.90846634e-05], + [9.65040026e-06, 6.11965396e-05], + [2.43395796e-05, 7.23966392e-05], + [6.28046063e-05, 1.00950641e-04], + [8.99657904e-05, 1.20825635e-04], + [9.84114021e-05, 1.26977201e-04], + [1.00705361e-04, 1.28906084e-04], + [1.03285783e-04, 1.32075942e-04], + [1.07048835e-04, 1.37218192e-04], + [1.12372096e-04, 1.43736004e-04], + [1.20999382e-04, 1.54937080e-04], + [1.33573053e-04, 1.71462176e-04], + [1.42475686e-04, 1.82478533e-04], + [1.47339899e-04, 1.87069658e-04], + [1.51194707e-04, 1.90466811e-04], + [1.54681601e-04, 1.93773152e-04], + [1.57676653e-04, 1.96386513e-04], + [1.59831239e-04, 1.98264929e-04], + [1.63936150e-04, 2.01847201e-04], + [1.65472675e-04, 2.03186195e-04], + [1.70467147e-04, 2.07543619e-04], + [1.71844334e-04, 2.08737728e-04], + [1.73587247e-04, 2.10022678e-04], + [1.74586922e-04, 2.11167839e-04], + [1.75229389e-04, 2.12361789e-04], + [1.75595876e-04, 2.13829694e-04], + [1.75650001e-04, 2.15667538e-04], + [1.75513922e-04, 2.16815933e-04], + [1.74962478e-04, 2.19845700e-04], + [1.74503092e-04, 2.22507224e-04], + [1.74251509e-04, 2.23697482e-04], + [1.73859727e-04, 2.25351966e-04], + [1.71713202e-04, 2.31029044e-04], + [1.71288336e-04, 2.32419977e-04], + [1.69817793e-04, 2.37742908e-04], + [1.68440467e-04, 2.42332824e-04], + [1.67612807e-04, 2.47013617e-04], + [1.66969033e-04, 2.51970213e-04], + [1.66600674e-04, 2.59220232e-04], + [1.66415880e-04, 2.68215619e-04], + [1.66824132e-04, 2.74327850e-04], + [1.66901881e-04, 2.75785216e-04], + [1.67381459e-04, 2.83346086e-04], + [1.67581971e-04, 2.85773882e-04]]) + + v = ref_vectors(points[20], points) + assert_array_almost_equal(v, expected) + + def test_bearing_to_points(self): + points = mockNodesData01.radians + expected = np.array([ + 2.16112265, 2.15595027, 2.15326799, 2.14916735, 2.14538642, + 2.14657678, 2.14694997, 2.1492257, 2.1507589, 2.15676899, + 2.16973441, 2.1651606, 2.12270237, 2.11416356, 2.10665211, + 2.11201708, 2.19291574, 2.2031069, 2.20136186, 2.17712517, + 0., -0.8965745, -0.84815954, -0.73792895, -0.59150953, + -0.5269061, -0.46406215, -0.42954043, -0.4008254, -0.36391371, + -0.33748609, -0.32996807, -0.31223189, -0.06185112, 0.05289544, + 0.08578116, 0.0927833, 0.11924233, 0.15640718, 0.32432622, + 0.55653415, 0.64003094, 0.6593301, 0.66319086, 0.66367982, + 0.66251077, 0.66354137, 0.66302176, 0.66181884, 0.66291139, + 0.66714676, 0.67095594, 0.67367984, 0.6765003, 0.67847961, + 0.68212344, 0.68345356, 0.68762778, 0.68876073, 0.69070183, + 0.69085143, 0.68988665, 0.68753177, 0.68348884, 0.68051081, + 0.67220053, 0.66506824, 0.66177969, 0.65712162, 0.63916951, + 0.6351146, 0.62025347, 0.60741567, 0.59618923, 0.58521935, + 0.57122582, 0.55532475, 0.54636839, 0.54422542, 0.53357655, + 0.53037033]) + + v = bearing_to_points(points[20], points) + assert_array_almost_equal(v, expected) + + def test_distance_to_points(self): + points = mockNodesData01.radians + expected = np.array([ + 1226.82569068, 1120.13820773, 1073.61121415, 1011.10016574, + 954.81557436, 905.58045038, 896.97734399, 781.7102819, + 738.58271117, 618.26145463, 509.47052142, 494.6403804, + 416.22483123, 403.42108699, 376.42615499, 357.15106681, + 253.15957483, 235.11572972, 221.77439728, 45.65465979, + 0., 14.98414, 28.77606056, 43.49299446, + 74.39463425, 112.74005248, 150.19482607, 167.03665191, + 178.28443483, 193.80834084, 202.28154097, 205.01173833, + 211.22777104, 282.88676739, 344.25957352, 362.66370657, + 367.00206795, 379.23951996, 394.82505328, 486.76073331, + 757.70254732, 960.03439155, 1023.81434529, 1042.49401713, + 1068.53770096, 1109.12696535, 1162.74555108, 1252.847351, + 1385.17179405, 1475.42502599, 1517.57849916, 1549.79838056, + 1580.12405964, 1605.05483058, 1622.98937809, 1657.19268821, + 1669.99157205, 1711.63883132, 1723.09133393, 1736.47655688, + 1746.16073119, 1754.63481838, 1763.34186103, 1772.62691273, + 1777.76189094, 1790.62024447, 1802.11488235, 1807.1040605, + 1813.90756815, 1834.49265566, 1840.00708445, 1861.96087374, + 1880.81678093, 1902.42091191, 1926.37194131, 1963.78301115, + 2011.62679077, 2046.18028824, 2054.37811294, 2097.30347724, + 2111.28586072]) + + v = distance_to_points(points[20], points) + assert_array_almost_equal(v, expected) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f11d72266ca568..7b85fd1a0d5743 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -5,6 +5,7 @@ from selfdrive.controls.lib.events import Events from selfdrive.monitoring.driver_monitor import DriverStatus from selfdrive.locationd.calibrationd import Calibration +from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus def dmonitoringd_thread(sm=None, pm=None): @@ -15,6 +16,7 @@ def dmonitoringd_thread(sm=None, pm=None): sm = messaging.SubMaster(['driverState', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverState']) driver_status = DriverStatus(rhd=Params().get_bool("IsRHD")) + hands_on_wheel_status = HandsOnWheelStatus() sm['liveCalibration'].calStatus = Calibration.INVALID sm['liveCalibration'].rpyCalib = [0, 0, 0] @@ -23,6 +25,8 @@ def dmonitoringd_thread(sm=None, pm=None): v_cruise_last = 0 driver_engaged = False + steering_wheel_engaged = False + hands_on_wheel_monitoring_enabled = Params().get_bool("HandsOnWheelMonitoring") # 10Hz <- dmonitoringmodeld while True: @@ -34,12 +38,15 @@ def dmonitoringd_thread(sm=None, pm=None): # Get interaction if sm.updated['carState']: v_cruise = sm['carState'].cruiseState.speed - driver_engaged = len(sm['carState'].buttonEvents) > 0 or \ - v_cruise != v_cruise_last or \ - sm['carState'].steeringPressed or \ - sm['carState'].gasPressed + steering_wheel_engaged = len(sm['carState'].buttonEvents) > 0 or \ + v_cruise != v_cruise_last or \ + sm['carState'].steeringPressed + driver_engaged = steering_wheel_engaged or sm['carState'].gasPressed if driver_engaged: driver_status.update(Events(), True, sm['controlsState'].enabled, sm['carState'].standstill) + # Update events and state from hands on wheel monitoring status when steering wheel in engaged + if steering_wheel_engaged and hands_on_wheel_monitoring_enabled: + hands_on_wheel_status.update(Events(), True, sm['controlsState'].enabled, sm['carState'].vEgo) v_cruise_last = v_cruise if sm.updated['modelV2']: @@ -56,6 +63,9 @@ def dmonitoringd_thread(sm=None, pm=None): # Update events from driver state driver_status.update(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill) + # Update events and state from hands on wheel monitoring status + if hands_on_wheel_monitoring_enabled: + hands_on_wheel_status.update(events, steering_wheel_engaged, sm['controlsState'].enabled, sm['carState'].vEgo) # build driverMonitoringState packet dat = messaging.new_message('driverMonitoringState') @@ -74,6 +84,7 @@ def dmonitoringd_thread(sm=None, pm=None): "isLowStd": driver_status.pose.low_std, "hiStdCount": driver_status.hi_stds, "isActiveMode": driver_status.active_monitoring_mode, + "handsOnWheelState": hands_on_wheel_status.hands_on_wheel_state, } pm.send('driverMonitoringState', dat) diff --git a/selfdrive/monitoring/hands_on_wheel_monitor.py b/selfdrive/monitoring/hands_on_wheel_monitor.py new file mode 100644 index 00000000000000..3148aab40ffe07 --- /dev/null +++ b/selfdrive/monitoring/hands_on_wheel_monitor.py @@ -0,0 +1,51 @@ +from cereal import log, car +from selfdrive.config import Conversions as CV + +EventName = car.CarEvent.EventName +HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState + +_PRE_ALERT_THRESHOLD = 150 # 15s +_PROMPT_ALERT_THRESHOLD = 300 # 30s +_TERMINAL_ALERT_THRESHOLD = 600 # 60s + +_MIN_MONITORING_SPEED = 10 * CV.KPH_TO_MS # No monitoring underd 10kph + + +class HandsOnWheelStatus(): + def __init__(self): + self.hands_on_wheel_state = HandsOnWheelState.none + self.hands_off_wheel_cnt = 0 + + def update(self, events, steering_wheel_engaged, ctrl_active, v_ego): + if v_ego < _MIN_MONITORING_SPEED or not ctrl_active: + self.hands_on_wheel_state = HandsOnWheelState.none + self.hands_off_wheel_cnt = 0 + return + + if steering_wheel_engaged: + # Driver has hands on steering wheel + self.hands_on_wheel_state = HandsOnWheelState.ok + self.hands_off_wheel_cnt = 0 + return + + self.hands_off_wheel_cnt += 1 + alert = None + + if self.hands_off_wheel_cnt >= _TERMINAL_ALERT_THRESHOLD: + # terminal red alert: disengagement required + self.hands_on_wheel_state = HandsOnWheelState.terminal + alert = EventName.keepHandsOnWheel + elif self.hands_off_wheel_cnt >= _PROMPT_ALERT_THRESHOLD: + # prompt orange alert + self.hands_on_wheel_state = HandsOnWheelState.critical + alert = EventName.promptKeepHandsOnWheel + elif self.hands_off_wheel_cnt >= _PRE_ALERT_THRESHOLD: + # pre green alert + self.hands_on_wheel_state = HandsOnWheelState.warning + alert = EventName.preKeepHandsOnWheel + else: + # hands off wheel for acceptable period of time. + self.hands_on_wheel_state = HandsOnWheelState.minor + + if alert is not None: + events.add(alert) diff --git a/selfdrive/monitoring/test_hands_monitoring.py b/selfdrive/monitoring/test_hands_monitoring.py new file mode 100644 index 00000000000000..ef998037ed25d3 --- /dev/null +++ b/selfdrive/monitoring/test_hands_monitoring.py @@ -0,0 +1,139 @@ +# flake8: noqa + +import unittest +import numpy as np +from cereal import car, log +from common.realtime import DT_DMON +from selfdrive.controls.lib.events import Events +from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus, _PRE_ALERT_THRESHOLD, \ + _PROMPT_ALERT_THRESHOLD, _TERMINAL_ALERT_THRESHOLD, \ + _MIN_MONITORING_SPEED + +EventName = car.CarEvent.EventName +HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState + +_TEST_TIMESPAN = 120 # seconds + +# some common state vectors +test_samples = int(_TEST_TIMESPAN / DT_DMON) +half_test_samples = int(test_samples / 2.) +always_speed_over_threshold = [_MIN_MONITORING_SPEED + 1.] * test_samples +always_speed_under_threshold = [_MIN_MONITORING_SPEED - 1.] * test_samples +always_true = [True] * test_samples +always_false = [False] * test_samples +true_then_false = [True] * half_test_samples + [False] * (test_samples - half_test_samples) + + +def run_HOWState_seq(steering_wheel_interaction, openpilot_status, speed_status): + # inputs are all 10Hz + HOWS = HandsOnWheelStatus() + events_from_HOWM = [] + hands_on_wheel_state_from_HOWM = [] + + for idx in range(len(steering_wheel_interaction)): + e = Events() + # evaluate events at 10Hz for tests + HOWS.update(e, steering_wheel_interaction[idx], openpilot_status[idx], speed_status[idx]) + events_from_HOWM.append(e) + hands_on_wheel_state_from_HOWM.append(HOWS.hands_on_wheel_state) + + assert len(events_from_HOWM) == len(steering_wheel_interaction), 'somethings wrong' + assert len(hands_on_wheel_state_from_HOWM) == len(steering_wheel_interaction), 'somethings wrong' + return events_from_HOWM, hands_on_wheel_state_from_HOWM + + +class TestHandsMonitoring(unittest.TestCase): + # 0. op engaged over monitoring speed, driver has hands on wheel all the time + def test_hands_on_all_the_time(self): + events_output, state_output = run_HOWState_seq(always_true, always_true, always_speed_over_threshold) + self.assertTrue(np.sum([len(event) for event in events_output]) == 0) + self.assertEqual(state_output, [HandsOnWheelState.ok for x in range(len(state_output))]) + + # 1. op engaged under monitoring speed, steering wheel interaction is irrelevant + def test_monitoring_under_threshold_speed(self): + events_output, state_output = run_HOWState_seq(true_then_false, always_true, always_speed_under_threshold) + self.assertTrue(np.sum([len(event) for event in events_output]) == 0) + self.assertEqual(state_output, [HandsOnWheelState.none for x in range(len(state_output))]) + + # 2. op engaged over monitoring speed, driver has no hands on wheel all the time + def test_hands_off_all_the_time(self): + events_output, state_output = run_HOWState_seq(always_false, always_true, always_speed_over_threshold) + # Assert correctness before _PRE_ALERT_THRESHOLD + self.assertTrue(np.sum([len(event) for event in events_output[:_PRE_ALERT_THRESHOLD - 1]]) == 0) + self.assertEqual(state_output[:_PRE_ALERT_THRESHOLD - 1], + [HandsOnWheelState.minor for x in range(_PRE_ALERT_THRESHOLD - 1)]) + # Assert correctness before _PROMPT_ALERT_THRESHOLD + self.assertEqual([event.names[0] for event in events_output[_PRE_ALERT_THRESHOLD:_PROMPT_ALERT_THRESHOLD - 1]], + [EventName.preKeepHandsOnWheel for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)]) + self.assertEqual(state_output[_PRE_ALERT_THRESHOLD:_PROMPT_ALERT_THRESHOLD - 1], + [HandsOnWheelState.warning for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)]) + # Assert correctness before _TERMINAL_ALERT_THRESHOLD + self.assertEqual( + [event.names[0] for event in events_output[_PROMPT_ALERT_THRESHOLD:_TERMINAL_ALERT_THRESHOLD - 1]], + [EventName.promptKeepHandsOnWheel for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)]) + self.assertEqual( + state_output[_PROMPT_ALERT_THRESHOLD:_TERMINAL_ALERT_THRESHOLD - 1], + [HandsOnWheelState.critical for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)]) + # Assert correctness after _TERMINAL_ALERT_THRESHOLD + self.assertEqual([event.names[0] for event in events_output[_TERMINAL_ALERT_THRESHOLD:]], + [EventName.keepHandsOnWheel for x in range(test_samples - _TERMINAL_ALERT_THRESHOLD)]) + self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD:], + [HandsOnWheelState.terminal for x in range(test_samples - _TERMINAL_ALERT_THRESHOLD)]) + + # 3. op engaged over monitoring speed, alert status resets to none when going under monitoring speed + def test_status_none_when_speeds_goes_down(self): + speed_vector = always_speed_over_threshold[:-1] + [_MIN_MONITORING_SPEED - 1.] + events_output, state_output = run_HOWState_seq(always_false, always_true, speed_vector) + # Assert correctness after _TERMINAL_ALERT_THRESHOLD + self.assertEqual([event.names[0] for event in events_output[_TERMINAL_ALERT_THRESHOLD:test_samples - 1]], + [EventName.keepHandsOnWheel for x in range(test_samples - 1 - _TERMINAL_ALERT_THRESHOLD)]) + self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD:test_samples - 1], + [HandsOnWheelState.terminal for x in range(test_samples - 1 - _TERMINAL_ALERT_THRESHOLD)]) + # Assert correctes on last sample where speed went under monitoring threshold + self.assertEqual(len(events_output[-1]), 0) + self.assertEqual(state_output[-1], HandsOnWheelState.none) + + # 4. op engaged over monitoring speed, alert status resets to ok when user interacts with steering wheel, + # process repeats once hands are off wheel. + def test_status_ok_after_interaction_with_wheel(self): + interaction_vector = always_false[:_TERMINAL_ALERT_THRESHOLD] + [True + ] + always_false[_TERMINAL_ALERT_THRESHOLD + 1:] + events_output, state_output = run_HOWState_seq(interaction_vector, always_true, always_speed_over_threshold) + # Assert correctness after _TERMINAL_ALERT_THRESHOLD + self.assertEqual(events_output[_TERMINAL_ALERT_THRESHOLD - 1].names[0], EventName.keepHandsOnWheel) + self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD - 1], HandsOnWheelState.terminal) + # Assert correctnes for one sample when user interacts with steering wheel + self.assertEqual(len(events_output[_TERMINAL_ALERT_THRESHOLD]), 0) + self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD], HandsOnWheelState.ok) + # Assert process correctness on second run + offset = _TERMINAL_ALERT_THRESHOLD + 1 + self.assertTrue(np.sum([len(event) for event in events_output[offset:offset + _PRE_ALERT_THRESHOLD - 1]]) == 0) + self.assertEqual(state_output[offset:offset + _PRE_ALERT_THRESHOLD - 1], + [HandsOnWheelState.minor for x in range(_PRE_ALERT_THRESHOLD - 1)]) + self.assertEqual( + [event.names[0] for event in events_output[offset + _PRE_ALERT_THRESHOLD:offset + _PROMPT_ALERT_THRESHOLD - 1]], + [EventName.preKeepHandsOnWheel for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)]) + self.assertEqual(state_output[offset + _PRE_ALERT_THRESHOLD:offset + _PROMPT_ALERT_THRESHOLD - 1], + [HandsOnWheelState.warning for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)]) + self.assertEqual([ + event.names[0] + for event in events_output[offset + _PROMPT_ALERT_THRESHOLD:offset + _TERMINAL_ALERT_THRESHOLD - 1] + ], [EventName.promptKeepHandsOnWheel for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)]) + self.assertEqual( + state_output[offset + _PROMPT_ALERT_THRESHOLD:offset + _TERMINAL_ALERT_THRESHOLD - 1], + [HandsOnWheelState.critical for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)]) + self.assertEqual([event.names[0] for event in events_output[offset + _TERMINAL_ALERT_THRESHOLD:]], + [EventName.keepHandsOnWheel for x in range(test_samples - offset - _TERMINAL_ALERT_THRESHOLD)]) + self.assertEqual(state_output[offset + _TERMINAL_ALERT_THRESHOLD:], + [HandsOnWheelState.terminal for x in range(test_samples - offset - _TERMINAL_ALERT_THRESHOLD)]) + + # 5. op not engaged, always hands off wheel + # - monitor should stay quiet when not engaged + def test_pure_dashcam_user(self): + events_output, state_output = run_HOWState_seq(always_false, always_false, always_speed_over_threshold) + self.assertTrue(np.sum([len(event) for event in events_output]) == 0) + self.assertEqual(state_output, [HandsOnWheelState.none for x in range(len(state_output))]) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc index 40972e07ab78a0..0b182ce0c5e0bd 100644 --- a/selfdrive/ui/paint.cc +++ b/selfdrive/ui/paint.cc @@ -30,6 +30,89 @@ static void ui_draw_text(const UIState *s, float x, float y, const char *string, nvgText(s->vg, x, y, string, NULL); } +static void ui_draw_circle(UIState *s, float x, float y, float size, NVGcolor color) { + nvgBeginPath(s->vg); + nvgCircle(s->vg, x, y, size); + nvgFillColor(s->vg, color); + nvgFill(s->vg); +} + +static void ui_draw_speed_sign(UIState *s, float x, float y, int size, float speed, const char *subtext, + float subtext_size, const char *font_name, bool is_map_sourced, bool is_active) { + NVGcolor ring_color = is_active ? COLOR_RED : COLOR_BLACK_ALPHA(.2f * 255); + NVGcolor inner_color = is_active ? COLOR_WHITE : COLOR_WHITE_ALPHA(.35f * 255); + NVGcolor text_color = is_active ? COLOR_BLACK : COLOR_BLACK_ALPHA(.3f * 255); + + ui_draw_circle(s, x, y, float(size), ring_color); + ui_draw_circle(s, x, y, float(size) * 0.8, inner_color); + + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + + const std::string speedlimit_str = std::to_string((int)std::nearbyint(speed)); + ui_draw_text(s, x, y, speedlimit_str.c_str(), 120, text_color, font_name); + ui_draw_text(s, x, y + 55, subtext, subtext_size, text_color, font_name); + + if (is_map_sourced) { + const int img_size = 35; + const int img_y = int(y - 55); + ui_draw_image(s, {int(x - (img_size / 2)), img_y - (img_size / 2), img_size, img_size}, "map_source_icon", + is_active ? 1. : .3); + } +} + +static void ui_draw_turn_speed_sign(UIState *s, float x, float y, int width, float speed, int curv_sign, + const char *subtext, const char *font_name, bool is_active) { + const float stroke_w = 15.0; + NVGcolor border_color = is_active ? COLOR_RED : COLOR_BLACK_ALPHA(.2f * 255); + NVGcolor inner_color = is_active ? COLOR_WHITE : COLOR_WHITE_ALPHA(.35f * 255); + NVGcolor text_color = is_active ? COLOR_BLACK : COLOR_BLACK_ALPHA(.3f * 255); + + const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle + const float R = width / 2.0 - stroke_w / 2.0; + const float A = 0.73205; + const float h2 = 2.0 * R / (1.0 + A); + const float h1 = A * h2; + const float L = 4.0 * R / sqrt(3.0); + + // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive + // state due to stroke transparency being different from inner transparency. + nvgBeginPath(s->vg); + nvgMoveTo(s->vg, x, y - R + cS); + nvgLineTo(s->vg, x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0); + nvgLineTo(s->vg, x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0); + nvgClosePath(s->vg); + + nvgFillColor(s->vg, inner_color); + nvgFill(s->vg); + + // Draw the stroke + nvgLineJoin(s->vg, NVG_ROUND); + nvgStrokeWidth(s->vg, stroke_w); + nvgStrokeColor(s->vg, border_color); + + nvgBeginPath(s->vg); + nvgMoveTo(s->vg, x, y - R); + nvgLineTo(s->vg, x - L / 2.0, y + h1 + h2 - R); + nvgLineTo(s->vg, x + L / 2.0, y + h1 + h2 - R); + nvgClosePath(s->vg); + + nvgStroke(s->vg); + + // Draw the turn sign + if (curv_sign != 0) { + const int img_size = 35; + const int img_y = int(y - R + stroke_w + 30); + ui_draw_image(s, {int(x - (img_size / 2)), img_y, img_size, img_size}, + curv_sign > 0 ? "turn_left_icon" : "turn_right_icon", is_active ? 1. : .3); + } + + // Draw the texts. + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + const std::string speedlimit_str = std::to_string((int)std::nearbyint(speed)); + ui_draw_text(s, x, y + 25, speedlimit_str.c_str(), 90., text_color, font_name); + ui_draw_text(s, x, y + 65, subtext, 30., text_color, font_name); +} + static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillColor, NVGcolor glowColor) { // glow float g_xo = sz/5; @@ -199,6 +282,72 @@ static void ui_draw_vision_maxspeed(UIState *s) { } } +static void ui_draw_vision_speedlimit(UIState *s) { + auto longitudinal_plan = (*s->sm)["longitudinalPlan"].getLongitudinalPlan(); + const float speedLimit = longitudinal_plan.getSpeedLimit(); + const float speedLimitOffset = longitudinal_plan.getSpeedLimitOffset(); + + if (speedLimit > 0.0 && s->scene.engageable) { + const Rect maxspeed_rect = {bdr_s * 2, int(bdr_s * 1.5), 184, 202}; + const Rect speed_sign_rect = {maxspeed_rect.centerX() - speed_sgn_r, maxspeed_rect.bottom() + bdr_s, + 2 * speed_sgn_r, 2 * speed_sgn_r}; + const float speed = speedLimit * (s->scene.is_metric ? 3.6 : 2.2369362921); + const float speed_offset = speedLimitOffset * (s->scene.is_metric ? 3.6 : 2.2369362921); + + auto speedLimitControlState = longitudinal_plan.getSpeedLimitControlState(); + const bool force_active = s->scene.speed_limit_control_enabled && + seconds_since_boot() < s->scene.last_speed_limit_sign_tap + 2.0; + const bool inactive = !force_active && (!s->scene.speed_limit_control_enabled || + speedLimitControlState == cereal::LongitudinalPlan::SpeedLimitControlState::INACTIVE); + const bool temp_inactive = !force_active && (s->scene.speed_limit_control_enabled && + speedLimitControlState == cereal::LongitudinalPlan::SpeedLimitControlState::TEMP_INACTIVE); + + const int distToSpeedLimit = int(longitudinal_plan.getDistToSpeedLimit() * + (s->scene.is_metric ? 1.0 : 3.28084) / 10.0) * 10; + const bool is_map_sourced = longitudinal_plan.getIsMapSpeedLimit(); + const std::string distance_str = std::to_string(distToSpeedLimit) + (s->scene.is_metric ? "m" : "f"); + const std::string offset_str = speed_offset > 0.0 ? "+" + std::to_string((int)std::nearbyint(speed_offset)) : ""; + const std::string inactive_str = temp_inactive ? "TEMP" : ""; + const std::string substring = inactive || temp_inactive ? inactive_str : + distToSpeedLimit > 0 ? distance_str : offset_str; + const float substring_size = inactive || temp_inactive || distToSpeedLimit > 0 ? 30.0 : 50.0; + + ui_draw_speed_sign(s, speed_sign_rect.centerX(), speed_sign_rect.centerY(), speed_sgn_r, speed, substring.c_str(), + substring_size, "sans-bold", is_map_sourced, !inactive && !temp_inactive); + + s->scene.speed_limit_sign_touch_rect = Rect{speed_sign_rect.x - speed_sgn_touch_pad, + speed_sign_rect.y - speed_sgn_touch_pad, + speed_sign_rect.w + 2 * speed_sgn_touch_pad, + speed_sign_rect.h + 2 * speed_sgn_touch_pad}; + } +} + +static void ui_draw_vision_turnspeed(UIState *s) { + auto longitudinal_plan = (*s->sm)["longitudinalPlan"].getLongitudinalPlan(); + const float turnSpeed = longitudinal_plan.getTurnSpeed(); + const float vEgo = (*s->sm)["carState"].getCarState().getVEgo(); + const bool show = turnSpeed > 0.0 && (turnSpeed < vEgo || s->scene.show_debug_ui); + + if (show) { + const Rect maxspeed_rect = {bdr_s * 2, int(bdr_s * 1.5), 184, 202}; + const Rect speed_sign_rect = {maxspeed_rect.centerX() - speed_sgn_r, + maxspeed_rect.bottom() + int(1.5 * bdr_s) + 2 * speed_sgn_r, + 2 * speed_sgn_r, maxspeed_rect.h}; + const float speed = turnSpeed * (s->scene.is_metric ? 3.6 : 2.2369362921); + + auto turnSpeedControlState = longitudinal_plan.getTurnSpeedControlState(); + const bool is_active = turnSpeedControlState > cereal::LongitudinalPlan::SpeedLimitControlState::TEMP_INACTIVE; + + const int curveSign = longitudinal_plan.getTurnSign(); + const int distToTurn = int(longitudinal_plan.getDistToTurn() * + (s->scene.is_metric ? 1.0 : 3.28084) / 10.0) * 10; + const std::string distance_str = std::to_string(distToTurn) + (s->scene.is_metric ? "m" : "f"); + + ui_draw_turn_speed_sign(s, speed_sign_rect.centerX(), speed_sign_rect.centerY(), speed_sign_rect.w, speed, + curveSign, distToTurn > 0 ? distance_str.c_str() : "", "sans-bold", is_active); + } +} + static void ui_draw_vision_speed(UIState *s) { const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363)); const std::string speed_str = std::to_string((int)std::nearbyint(speed)); @@ -208,7 +357,27 @@ static void ui_draw_vision_speed(UIState *s) { } static void ui_draw_vision_event(UIState *s) { - if (s->scene.engageable) { + auto longitudinal_plan = (*s->sm)["longitudinalPlan"].getLongitudinalPlan(); + auto visionTurnControllerState = longitudinal_plan.getVisionTurnControllerState(); + if (s->scene.show_debug_ui && + visionTurnControllerState > cereal::LongitudinalPlan::VisionTurnControllerState::DISABLED && + s->scene.engageable) { + // draw a rectangle with colors indicating the state with the value of the acceleration inside. + const int size = 184; + const Rect rect = {s->fb_w - size - bdr_s, int(bdr_s * 1.5), size, size}; + ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 30.); + + auto source = longitudinal_plan.getLongitudinalPlanSource(); + const int alpha = source == cereal::LongitudinalPlan::LongitudinalPlanSource::TURN ? 255 : 100; + const QColor &color = tcs_colors[int(visionTurnControllerState)]; + NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), alpha); + ui_draw_rect(s->vg, rect, nvg_color, 10, 20.); + + const float vision_turn_speed = longitudinal_plan.getVisionTurnSpeed() * (s->scene.is_metric ? 3.6 : 2.2369363); + std::string acc_str = std::to_string((int)std::nearbyint(vision_turn_speed)); + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + ui_draw_text(s, rect.centerX(), rect.centerY(), acc_str.c_str(), 56, COLOR_WHITE_ALPHA(alpha), "sans-bold"); + } else if (s->scene.engageable) { // draw steering wheel const int radius = 96; const int center_x = s->fb_w - radius - bdr_s * 2; @@ -216,6 +385,30 @@ static void ui_draw_vision_event(UIState *s) { const QColor &color = bg_colors[s->status]; NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), color.alpha()); ui_draw_circle_image(s, center_x, center_y, radius, "wheel", nvg_color, 1.0f); + + // draw hands on wheel pictogram under wheel pictogram. + auto handsOnWheelState = (*s->sm)["driverMonitoringState"].getDriverMonitoringState().getHandsOnWheelState(); + if (handsOnWheelState >= cereal::DriverMonitoringState::HandsOnWheelState::WARNING) { + NVGcolor color = COLOR_RED; + if (handsOnWheelState == cereal::DriverMonitoringState::HandsOnWheelState::WARNING) { + color = COLOR_YELLOW; + } + const int wheel_y = center_y + bdr_s + 2 * radius; + ui_draw_circle_image(s, center_x, wheel_y, radius, "hands_on_wheel", color, 1.0f); + } + } +} + +static void ui_draw_vision_bottom_bar(UIState *s) { + auto liveMapData = (*s->sm)["liveMapData"].getLiveMapData(); + const std::string road_name = liveMapData.getCurrentRoadName(); + + if (!road_name.empty() && s->scene.show_debug_ui) { + const int h = 60; + const Rect rect = {0, s->fb_h - h, s->fb_w, h}; + ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 0.); + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + ui_draw_text(s, rect.centerX(), rect.centerY(), road_name.c_str(), 52., COLOR_WHITE_ALPHA(200), "sans-bold"); } } @@ -231,8 +424,11 @@ static void ui_draw_vision_header(UIState *s) { nvgRGBAf(0, 0, 0, 0.45), nvgRGBAf(0, 0, 0, 0)); ui_fill_rect(s->vg, {0, 0, s->fb_w , header_h}, gradient); ui_draw_vision_maxspeed(s); + ui_draw_vision_speedlimit(s); ui_draw_vision_speed(s); + ui_draw_vision_turnspeed(s); ui_draw_vision_event(s); + ui_draw_vision_bottom_bar(s); } static void ui_draw_vision(UIState *s) { @@ -358,6 +554,10 @@ void ui_nvg_init(UIState *s) { std::vector> images = { {"wheel", "../assets/img_chffr_wheel.png"}, {"driver_face", "../assets/img_driver_face.png"}, + {"hands_on_wheel", "../assets/img_hands_on_wheel.png"}, + {"turn_left_icon", "../assets/img_turn_left_icon.png"}, + {"turn_right_icon", "../assets/img_turn_right_icon.png"}, + {"map_source_icon", "../assets/img_world_icon.png"}, }; for (auto [name, file] : images) { s->images[name] = nvgCreateImage(s->vg, file, 1); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 1587cf8e14f89e..58f1963413c069 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -73,6 +73,47 @@ TogglesPanel::TogglesPanel(QWidget *parent) : QWidget(parent) { "In this mode openpilot will ignore lanelines and just drive how it thinks a human would.", "../assets/offroad/icon_road.png", this)); + + toggles.append(new ParamControl("HandsOnWheelMonitoring", + "Enable Hands on Wheel Monitoring", + "Monitor and alert when driver is not keeping the hands on the steering wheel.", + "../assets/offroad/icon_openpilot.png", + this)); + toggles.append(new ParamControl("TurnVisionControl", + "Enable vision based turn control", + "Use vision path predictions to estimate the appropiate speed to drive through turns ahead.", + "../assets/offroad/icon_road.png", + this)); + toggles.append(new ParamControl("SpeedLimitControl", + "Enable Speed Limit Control", + "Use speed limit signs information from map data and car interface to automatically adapt cruise speed to road limits.", + "../assets/offroad/icon_speed_limit.png", + this)); + toggles.append(new ParamControl("SpeedLimitPercOffset", + "Enable Speed Limit Offset", + "Set speed limit slightly higher than actual speed limit for a more natural drive.", + "../assets/offroad/icon_speed_limit.png", + this)); + toggles.append(new ParamControl("TurnSpeedControl", + "Enable Map Data Turn Control", + "Use curvature info from map data to define speed limits to take turns ahead", + "../assets/offroad/icon_openpilot.png", + this)); + toggles.append(new ParamControl("ShowDebugUI", + "Show debug UI elements", + "Show UI elements that aid debugging.", + "../assets/offroad/icon_calibration.png", + this)); + toggles.append(new ParamControl("EnableDebugSnapshot", + "Debug snapshot on screen center tap", + "Stores snapshot file with current state of some modules.", + "../assets/offroad/icon_calibration.png", + this)); + toggles.append(new ParamControl("DisableDisengageOnGas", + "Disable disengage on gas", + "Disable default comma stock disengage on gas feature", + "../assets/offroad/icon_openpilot.png", + this)); #ifdef ENABLE_MAPS toggles.append(new ParamControl("NavSettingTime24h", diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 75593cda2a151d..7c1e9f348d2796 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -1,6 +1,7 @@ #include "selfdrive/ui/qt/onroad.h" #include +#include #include #include "selfdrive/common/swaglog.h" @@ -45,7 +46,10 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { void OnroadWindow::updateState(const UIState &s) { SubMaster &sm = *(s.sm); QColor bgColor = bg_colors[s.status]; - if (sm.updated("controlsState")) { + if ((sm.frame - s.scene.display_debug_alert_frame) <= 1 * UI_FREQ) { + alerts->updateAlert(DEBUG_SNAPSHOT_ALERT, bgColor); + } + else if (sm.updated("controlsState")) { const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); alerts->updateAlert({QString::fromStdString(cs.getAlertText1()), QString::fromStdString(cs.getAlertText2()), @@ -69,13 +73,98 @@ void OnroadWindow::updateState(const UIState &s) { } } +void issue_debug_snapshot(SubMaster &sm) { + auto longitudinal_plan = sm["longitudinalPlan"].getLongitudinalPlan(); + auto live_map_data = sm["liveMapData"].getLiveMapData(); + auto car_state = sm["carState"].getCarState(); + + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + std::ostringstream param_name_os; + param_name_os << std::put_time(&tm, "%Y-%m-%d--%H-%M-%S"); + + std::ostringstream os; + os.setf(std::ios_base::fixed); + os.precision(2); + os << "Datetime: " << param_name_os.str() << ", vEgo: " << car_state.getVEgo() * 3.6 << "\n\n"; + os.precision(6); + os << "Location: (" << live_map_data.getLastGpsLatitude() << ", " << live_map_data.getLastGpsLongitude() << ")\n"; + os.precision(2); + os << "Bearing: " << live_map_data.getLastGpsBearingDeg() << "; "; + os << "GPSSpeed: " << live_map_data.getLastGpsSpeed() * 3.6 << "\n\n"; + os.precision(1); + os << "Speed Limit: " << live_map_data.getSpeedLimit() * 3.6 << ", "; + os << "Valid: " << live_map_data.getSpeedLimitValid() << "\n"; + os << "Speed Limit Ahead: " << live_map_data.getSpeedLimitAhead() * 3.6 << ", "; + os << "Valid: " << live_map_data.getSpeedLimitAheadValid() << ", "; + os << "Distance: " << live_map_data.getSpeedLimitAheadDistance() << "\n"; + os << "Turn Speed Limit: " << live_map_data.getTurnSpeedLimit() * 3.6 << ", "; + os << "Valid: " << live_map_data.getTurnSpeedLimitValid() << ", "; + os << "End Distance: " << live_map_data.getTurnSpeedLimitEndDistance() << ", "; + os << "Sign: " << live_map_data.getTurnSpeedLimitSign() << "\n\n"; + + const auto turn_speeds = live_map_data.getTurnSpeedLimitsAhead(); + os << "Turn Speed Limits Ahead:\n"; + os << "VALUE\tDIST\tSIGN\n"; + + if (turn_speeds.size() == 0) { + os << "-\t-\t-" << "\n\n"; + } else { + const auto distances = live_map_data.getTurnSpeedLimitsAheadDistances(); + const auto signs = live_map_data.getTurnSpeedLimitsAheadSigns(); + for(int i = 0; i < turn_speeds.size(); i++) { + os << turn_speeds[i] * 3.6 << "\t" << distances[i] << "\t" << signs[i] << "\n"; + } + os << "\n"; + } + + os << "SPEED LIMIT CONTROLLER:\n"; + os << "sl: " << longitudinal_plan.getSpeedLimit() * 3.6 << ", "; + os << "state: " << int(longitudinal_plan.getSpeedLimitControlState()) << ", "; + os << "isMap: " << longitudinal_plan.getIsMapSpeedLimit() << "\n\n"; + + os << "TURN SPEED CONTROLLER:\n"; + os << "speed: " << longitudinal_plan.getTurnSpeed() * 3.6 << ", "; + os << "state: " << int(longitudinal_plan.getTurnSpeedControlState()) << "\n\n"; + + os << "VISION TURN CONTROLLER:\n"; + os << "speed: " << longitudinal_plan.getVisionTurnSpeed() * 3.6 << ", "; + os << "state: " << int(longitudinal_plan.getVisionTurnControllerState()); + + Params().put(param_name_os.str().c_str(), os.str().c_str(), os.str().length()); + QUIState::ui_state.scene.display_debug_alert_frame = sm.frame; +} + void OnroadWindow::mousePressEvent(QMouseEvent* e) { - if (map != nullptr) { - bool sidebarVisible = geometry().x() > 0; + bool sidebarVisible = geometry().x() > 0; + bool propagate_event = true; + + // Toggle speed limit control enabled + SubMaster &sm = *(QUIState::ui_state.sm); + auto longitudinal_plan = sm["longitudinalPlan"].getLongitudinalPlan(); + + Rect speed_limit_touch_rect = QUIState::ui_state.scene.speed_limit_sign_touch_rect; + Rect debug_tap_rect = {rect().center().x() - 200, rect().center().y() - 200, 400, 400}; + + if (longitudinal_plan.getSpeedLimit() > 0.0 && speed_limit_touch_rect.ptInRect(e->x(), e->y())) { + // If touching the speed limit sign area when visible + QUIState::ui_state.scene.last_speed_limit_sign_tap = seconds_since_boot(); + QUIState::ui_state.scene.speed_limit_control_enabled = !QUIState::ui_state.scene.speed_limit_control_enabled; + Params().putBool("SpeedLimitControl", QUIState::ui_state.scene.speed_limit_control_enabled); + propagate_event = false; + } + else if (QUIState::ui_state.scene.debug_snapshot_enabled && debug_tap_rect.ptInRect(e->x(), e->y())) { + issue_debug_snapshot(*(QUIState::ui_state.sm)); + propagate_event = false; + } + else if (map != nullptr) { map->setVisible(!sidebarVisible && !map->isVisible()); } + // propagation event to parent(HomeWindow) - QWidget::mousePressEvent(e); + if (propagate_event) { + QWidget::mousePressEvent(e); + } } void OnroadWindow::offroadTransition(bool offroad) { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index f6fbb495e12156..c7c20bdcfc98c7 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -260,6 +260,11 @@ static void update_status(UIState *s) { } else { s->vipc_client = s->vipc_client_rear; } + + s->scene.speed_limit_control_enabled = Params().getBool("SpeedLimitControl"); + s->scene.speed_limit_perc_offset = Params().getBool("SpeedLimitPercOffset"); + s->scene.show_debug_ui = Params().getBool("ShowDebugUI"); + s->scene.debug_snapshot_enabled = Params().getBool("EnableDebugSnapshot"); } else { s->vipc_client->connected = false; } @@ -272,6 +277,7 @@ QUIState::QUIState(QObject *parent) : QObject(parent) { ui_state.sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "deviceState", "roadCameraState", "pandaState", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", + "longitudinalPlan", "liveMapData", }); ui_state.fb_w = vwp_w; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 7ad3b8b5626056..9e1e8199d85e1a 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -67,12 +67,19 @@ const Alert CONTROLS_WAITING_ALERT = {"openpilot Unavailable", "Waiting for cont const Alert CONTROLS_UNRESPONSIVE_ALERT = {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, AudibleAlert::CHIME_WARNING_REPEAT}; + +const Alert DEBUG_SNAPSHOT_ALERT = {"Debug snapshot collected", "", + "debugTapDetected", cereal::ControlsState::AlertSize::SMALL, + AudibleAlert::CHIME_WARNING2}; const int CONTROLS_TIMEOUT = 5; const int bdr_s = 30; const int header_h = 420; const int footer_h = 280; +const int speed_sgn_r = 96; +const int speed_sgn_touch_pad = 50; + const int UI_FREQ = 20; // Hz typedef enum UIStatus { @@ -89,6 +96,14 @@ const QColor bg_colors [] = { [STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1), }; +const QColor tcs_colors [] = { + [int(cereal::LongitudinalPlan::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), + [int(cereal::LongitudinalPlan::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), + [int(cereal::LongitudinalPlan::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), + [int(cereal::LongitudinalPlan::VisionTurnControllerState::LEAVING) + ] = QColor(0x17, 0x86, 0x44, 0xf1), +}; + typedef struct { float x, y; } vertex_data; @@ -103,6 +118,17 @@ typedef struct UIScene { mat3 view_from_calib; bool world_objects_visible; + // Debug UI + bool show_debug_ui; + bool debug_snapshot_enabled; + uint64_t display_debug_alert_frame; + + // Speed limit control + bool speed_limit_control_enabled; + bool speed_limit_perc_offset; + Rect speed_limit_sign_touch_rect; + double last_speed_limit_sign_tap; + cereal::PandaState::PandaType pandaType; // modelV2 diff --git a/tools/lib/expand_logs.py b/tools/lib/expand_logs.py new file mode 100644 index 00000000000000..880ae3f37c8340 --- /dev/null +++ b/tools/lib/expand_logs.py @@ -0,0 +1,64 @@ +import os +import sys +import re +import shutil +import argparse + +DONGLE_ID = '52a8edf5ce93e696' +SEGMENT_NAME_RE = r'[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+' +OP_SEGMENT_DIR_RE = r'^({})$'.format(SEGMENT_NAME_RE) + + +class Expander(object): + def __init__(self, data_dir): + self._expand_directories(data_dir) + + def _expand_directories(self, data_dir): + files = os.listdir(data_dir) + + for f in files: + fullpath = os.path.join(data_dir, f) + if not os.path.isdir(fullpath): + continue + + op_match = re.match(OP_SEGMENT_DIR_RE, f) + if not op_match: + continue + + subfiles = os.listdir(fullpath) + for sf in subfiles: + oldpath = os.path.join(fullpath, sf) + newname = '{}|{}--{}'.format(DONGLE_ID, f, sf) + newpath = os.path.join(data_dir, newname) + print('Will move froom {} to {}'.format(oldpath, newpath)) + os.rename(oldpath, newpath) + + print('Will remove source dir {}'.format(fullpath)) + shutil.rmtree(fullpath) + + +def get_arg_parser(): + parser = argparse.ArgumentParser( + description="Expand downloaded segments from device into single folder", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("data_dir", nargs='?', default=os.getenv('UNLOGGER_DATA_DIR'), + help="Path to directory in which log and camera files are located.") + + return parser + + +def main(argv): + args = get_arg_parser().parse_args(sys.argv[1:]) + + if args.data_dir is not None: + Expander(args.data_dir) + print("done") + else: + print("missing data dir") + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/tools/lib/route_framereader.py b/tools/lib/route_framereader.py index 07d353498b5998..314917cc649840 100644 --- a/tools/lib/route_framereader.py +++ b/tools/lib/route_framereader.py @@ -37,7 +37,7 @@ def __init__(self, camera_paths, cache_paths, frame_id_lookup, **kwargs): will also be used for frame position indices. """ if not isinstance(camera_paths, dict): - camera_paths = {int(k.split('?')[0].split('/')[-2]): k for k in camera_paths if k is not None} + camera_paths = {int(k.split('--')[2]): k for k in camera_paths} self._first_camera_idx = min(camera_paths.keys()) self._frame_readers = _FrameReaderDict(camera_paths, cache_paths, kwargs) diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 750612777ba15a..ff070bd5e82af0 100644 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -35,25 +35,25 @@ def compare_loads(self, url, start=0, length=None): self.assertEqual(file_cached.get_length(), file_downloaded.get_length()) self.assertEqual(response_cached, response_downloaded) - def test_small_file(self): - # Make sure we don't force cache - os.environ["FILEREADER_CACHE"] = "0" - small_file_url = "https://raw.githubusercontent.com/commaai/openpilot/master/SAFETY.md" - # If you want large file to be larger than a chunk - # large_file_url = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/fcamera.hevc" + # def test_small_file(self): + # # Make sure we don't force cache + # os.environ["FILEREADER_CACHE"] = "0" + # small_file_url = "https://raw.githubusercontent.com/commaai/openpilot/master/SAFETY.md" + # # If you want large file to be larger than a chunk + # # large_file_url = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/fcamera.hevc" - # Load full small file - self.compare_loads(small_file_url) + # # Load full small file + # self.compare_loads(small_file_url) - file_small = URLFile(small_file_url) - length = file_small.get_length() + # file_small = URLFile(small_file_url) + # length = file_small.get_length() - self.compare_loads(small_file_url, length - 100, 100) - self.compare_loads(small_file_url, 50, 100) + # self.compare_loads(small_file_url, length - 100, 100) + # self.compare_loads(small_file_url, 50, 100) - # Load small file 100 bytes at a time - for i in range(length // 100): - self.compare_loads(small_file_url, 100 * i, 100) + # # Load small file 100 bytes at a time + # for i in range(length // 100): + # self.compare_loads(small_file_url, 100 * i, 100) def test_large_file(self): large_file_url = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/qlog.bz2" diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 2809390292fa55..1f03220ad0957c 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -198,7 +198,7 @@ def ui_thread(addr, frame_address): lines = [ info_font.render("ENABLED", True, GREEN if sm['controlsState'].enabled else BLACK), - info_font.render("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", True, YELLOW), + info_font.render("SPEED: " + str(round(sm['carState'].vEgo * 3.6, 1)) + " km/h", True, YELLOW), info_font.render("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), True, YELLOW), info_font.render("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), True, YELLOW), None, diff --git a/tools/replay/unlogger.py b/tools/replay/unlogger.py index 78bf4e6844d1ae..43f214d320d64a 100755 --- a/tools/replay/unlogger.py +++ b/tools/replay/unlogger.py @@ -87,6 +87,9 @@ def _read_logs(self, cookie, pub_types): if typ not in pub_types: continue + if typ == "liveMapData": + print(msg) + # **** special case certain message types **** if typ == "roadEncodeIdx" and msg.roadEncodeIdx.type == fullHEVC: # this assumes the roadEncodeIdx always comes before the frame diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index 2bb89807fc4794..431cc7d138f90d 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -297,7 +297,7 @@ def bridge(q): if is_openpilot_engaged: sm.update(0) # TODO gas and brake is deprecated - throttle_op = clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + throttle_op = clip(sm['carControl'].actuators.accel/1.6, 0.0, 1.0) brake_op = clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) steer_op = sm['carControl'].actuators.steeringAngleDeg