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