Browse Source

Meta, Fuel,TextCred,Overrides

pull/49/head
Jxck-S 3 years ago
parent
commit
492c21db49
  1. 3
      .gitignore
  2. 10
      Pipfile
  3. 624
      Pipfile.lock
  4. 14
      __main__.py
  5. 214
      aircraft_type_fuel_consumption_rates.json
  6. 2
      calculate_headings.py
  7. 11
      configs/mainconf.ini
  8. 25
      configs/plane1.ini
  9. 76
      defAirport.py
  10. 10
      defDiscord.py
  11. 83
      defSS.py
  12. 38
      defTelegram.py
  13. 9
      defTweet.py
  14. 52
      fuel_calc.py
  15. 51
      meta_toolkit.py
  16. 18
      modify_image.py
  17. 327
      planeClass.py

3
.gitignore vendored

@ -6,3 +6,6 @@ __pycache__
dependencies
testing
lookup_route.py
icao_url_gen.py
install.sh
coul_icao_gen.py

10
Pipfile

@ -18,6 +18,14 @@ selenium = "*"
opensky-api = {editable = true, git = "https://github.com/openskynetwork/opensky-api.git", subdirectory = "python"}
webdriver-manager = "*"
shapely = "*"
pycairo = "*"
geog = "*"
py-staticmaps = "*"
pyproj = "*"
pandas = "*"
lxml = "*"
beautifulsoup4 = "*"
python-telegram-bot = "*"
configparser = "*"
[requires]
python_version = "3.9"

624
Pipfile.lock generated

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "5131229ab384051accd51e665cc63da8e2d08a651ad0c9df09041fca9f306977"
"sha256": "35921a1158bc5b11d88e9f2a4702ee3ae09228a7d5438d1562ba62f24b02fd05"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,52 @@
]
},
"default": {
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
"version": "==1.4.4"
},
"apscheduler": {
"hashes": [
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
"sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
],
"version": "==3.6.3"
},
"async-generator": {
"hashes": [
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
],
"markers": "python_version >= '3.5'",
"version": "==1.10"
},
"attrs": {
"hashes": [
"sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
"sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.4.0"
},
"beautifulsoup4": {
"hashes": [
"sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf",
"sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"
],
"index": "pypi",
"version": "==4.10.0"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"certifi": {
"hashes": [
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
@ -23,6 +69,62 @@
],
"version": "==2021.10.8"
},
"cffi": {
"hashes": [
"sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3",
"sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2",
"sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636",
"sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20",
"sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728",
"sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27",
"sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66",
"sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443",
"sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0",
"sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7",
"sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39",
"sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605",
"sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a",
"sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37",
"sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029",
"sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139",
"sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc",
"sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df",
"sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14",
"sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880",
"sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2",
"sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a",
"sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e",
"sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474",
"sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024",
"sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8",
"sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0",
"sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e",
"sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a",
"sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e",
"sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032",
"sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6",
"sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e",
"sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b",
"sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e",
"sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954",
"sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962",
"sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c",
"sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4",
"sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55",
"sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962",
"sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023",
"sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c",
"sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6",
"sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8",
"sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382",
"sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7",
"sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc",
"sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997",
"sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"
],
"markers": "os_name == 'nt' and implementation_name != 'pypy'",
"version": "==1.15.0"
},
"charset-normalizer": {
"hashes": [
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
@ -44,23 +146,55 @@
"sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa",
"sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d"
],
"markers": "python_version >= '3.6'",
"index": "pypi",
"version": "==5.2.0"
},
"crayons": {
"cryptography": {
"hashes": [
"sha256:bd33b7547800f2cfbd26b38431f9e64b487a7de74a947b0fafc89b45a601813f",
"sha256:e73ad105c78935d71fe454dd4b85c5c437ba199294e7ffd3341842bc683654b1"
"sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b",
"sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51",
"sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7",
"sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d",
"sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6",
"sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29",
"sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9",
"sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf",
"sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815",
"sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf",
"sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85",
"sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77",
"sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86",
"sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb",
"sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e",
"sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0",
"sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3",
"sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84",
"sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2",
"sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"
],
"version": "==0.4.0"
"version": "==36.0.2"
},
"discord-webhook": {
"hashes": [
"sha256:17e475d8a52fe0bfa26b071925f55087600e9bb96e821b611dc463f4b4998c89",
"sha256:f3d660df572caaa9c2621edd7e8634a70d6d8295ce9256c365838312457069a1"
"sha256:1557a3a86ec556d5fceeff3d0c8601affd83770280b7358a3c7f4fb7c8ee32e5",
"sha256:5c59e3c5b52be8d9273aebdc5647f564a3c24ce4f75635fd1d9e4d9d8dad30f4"
],
"index": "pypi",
"version": "==0.14.0"
"version": "==0.15.0"
},
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"geog": {
"hashes": [
"sha256:9b6b020b72bf135d49299115e5a4a751f2432def4d4cd87d10b48a5ae51ec643"
],
"index": "pypi",
"version": "==0.0.2"
},
"geographiclib": {
"hashes": [
@ -77,6 +211,14 @@
"index": "pypi",
"version": "==2.2.0"
},
"h11": {
"hashes": [
"sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
"sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
],
"markers": "python_version >= '3.6'",
"version": "==0.13.0"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
@ -85,6 +227,99 @@
"markers": "python_version >= '3'",
"version": "==3.3"
},
"lxml": {
"hashes": [
"sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169",
"sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428",
"sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc",
"sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85",
"sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696",
"sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507",
"sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3",
"sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430",
"sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03",
"sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9",
"sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b",
"sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7",
"sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5",
"sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654",
"sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca",
"sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9",
"sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c",
"sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63",
"sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe",
"sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9",
"sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9",
"sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1",
"sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939",
"sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68",
"sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613",
"sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63",
"sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e",
"sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4",
"sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79",
"sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1",
"sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e",
"sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141",
"sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb",
"sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939",
"sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a",
"sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93",
"sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9",
"sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2",
"sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6",
"sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa",
"sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150",
"sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea",
"sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33",
"sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76",
"sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807",
"sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a",
"sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4",
"sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15",
"sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f",
"sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429",
"sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c",
"sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5",
"sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870",
"sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b",
"sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8",
"sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c",
"sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87",
"sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0",
"sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23",
"sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170",
"sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"
],
"index": "pypi",
"version": "==4.8.0"
},
"numpy": {
"hashes": [
"sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676",
"sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4",
"sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce",
"sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123",
"sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1",
"sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e",
"sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5",
"sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d",
"sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a",
"sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab",
"sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75",
"sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168",
"sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4",
"sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f",
"sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18",
"sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62",
"sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe",
"sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430",
"sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802",
"sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"
],
"markers": "python_version < '3.10' and platform_machine != 'aarch64' and platform_machine != 'arm64'",
"version": "==1.22.3"
},
"oauthlib": {
"hashes": [
"sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2",
@ -96,9 +331,44 @@
"opensky-api": {
"editable": true,
"git": "https://github.com/openskynetwork/opensky-api.git",
"ref": "d576cf260affd99156e352528ea46817273512d7",
"ref": "68c8304471fe32f5eaca8a41144fdad35d8a8ce4",
"subdirectory": "python"
},
"outcome": {
"hashes": [
"sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958",
"sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"
],
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
"pandas": {
"hashes": [
"sha256:0259cd11e7e6125aaea3af823b80444f3adad6149ff4c97fef760093598b3e34",
"sha256:04dd15d9db538470900c851498e532ef28d4e56bfe72c9523acb32042de43dfb",
"sha256:0b1a13f647e4209ed7dbb5da3497891d0045da9785327530ab696417ef478f84",
"sha256:19f7c632436b1b4f84615c3b127bbd7bc603db95e3d4332ed259dc815c9aaa26",
"sha256:1b384516dbb4e6aae30e3464c2e77c563da5980440fbdfbd0968e3942f8f9d70",
"sha256:1d85d5f6be66dfd6d1d8d13b9535e342a2214260f1852654b19fa4d7b8d1218b",
"sha256:2e5a7a1e0ecaac652326af627a3eca84886da9e667d68286866d4e33f6547caf",
"sha256:3129a35d9dad1d80c234dd78f8f03141b914395d23f97cf92a366dcd19f8f8bf",
"sha256:358b0bc98a5ff067132d23bf7a2242ee95db9ea5b7bbc401cf79205f11502fd3",
"sha256:3dfb32ed50122fe8c5e7f2b8d97387edd742cc78f9ec36f007ee126cd3720907",
"sha256:4e1176f45981c8ccc8161bc036916c004ca51037a7ed73f2d2a9857e6dbe654f",
"sha256:508c99debccd15790d526ce6b1624b97a5e1e4ca5b871319fb0ebfd46b8f4dad",
"sha256:6105af6533f8b63a43ea9f08a2ede04e8f43e49daef0209ab0d30352bcf08bee",
"sha256:6d6ad1da00c7cc7d8dd1559a6ba59ba3973be6b15722d49738b2be0977eb8a0c",
"sha256:7ea47ba1d6f359680130bd29af497333be6110de8f4c35b9211eec5a5a9630fa",
"sha256:8db93ec98ac7cb5f8ac1420c10f5e3c43533153f253fe7fb6d891cf5aa2b80d2",
"sha256:96e9ece5759f9b47ae43794b6359bbc54805d76e573b161ae770c1ea59393106",
"sha256:bbb15ad79050e8b8d39ec40dd96a30cd09b886a2ae8848d0df1abba4d5502a67",
"sha256:c614001129b2a5add5e3677c3a213a9e6fd376204cb8d17c04e84ff7dfc02a73",
"sha256:e6a7bbbb7950063bfc942f8794bc3e31697c020a14f1cd8905fc1d28ec674a01",
"sha256:f02e85e6d832be37d7f16cf6ac8bb26b519ace3e5f3235564a91c7f658ab2a43"
],
"index": "pypi",
"version": "==1.4.1"
},
"pillow": {
"hashes": [
"sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97",
@ -148,6 +418,84 @@
"index": "pypi",
"version": "==0.12.0"
},
"py-staticmaps": {
"hashes": [
"sha256:5aba5ad59f30a63f860e76ed99407a6efb24eaad5c8997aa8617363989f17389"
],
"index": "pypi",
"version": "==0.4.0"
},
"pycairo": {
"hashes": [
"sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b",
"sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39",
"sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b",
"sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6",
"sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6",
"sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02",
"sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41",
"sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c",
"sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4"
],
"index": "pypi",
"version": "==1.21.0"
},
"pycparser": {
"hashes": [
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
],
"version": "==2.21"
},
"pyopenssl": {
"hashes": [
"sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf",
"sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"
],
"version": "==22.0.0"
},
"pyproj": {
"hashes": [
"sha256:067a5c6099949edd66e9a10b139af4e2f65ebadb9f59583923a1d3feefac749a",
"sha256:0e1fd560b509b722db6566fa9685252f25640e93464d09e13d5190ed7ab491ba",
"sha256:235b52d8700ffb6be1f3638b1e25d83a9c13edcdb793236d8a98fd39227c5c27",
"sha256:277072176a17471c0b1d25d6cae75401d81e9b50ea625ba546f5b79acd757dfc",
"sha256:2c41c9b7b5e1a1b0acc2b7b2f5de65b226f7b96c870888e4f679ff96322b1ed0",
"sha256:3d28b84913cd849832a8f154c0e0c2ee4618057f7389ee68bfdb2145e7ed78cc",
"sha256:4125e6704751d0e82d8d912d9851da097e8d38599d4c45f9944faaeb21771938",
"sha256:44b5590c0b8dd002154916e170ef88f57abf91005b34bcb23faef97abb4d42c2",
"sha256:4d2fc49c73d9f34e932bf37926d56916ba1b6f2f693cd4d8cc1d0d9eacc0e537",
"sha256:5a105bfe37c78416d2641cd5d3368c99057d041f15f8d51ea3898953b21395c9",
"sha256:99f171da5f885efeec8d7fb2e2557175ffa8834eeb488842b1f52ac78a9a98e5",
"sha256:ab4baf781721640659db83a6b4da636fc403008f4978c668275754284c946778",
"sha256:b15e199c1da8fd132e11dfa68d8cf65d4812dedabc776b308df778ecd0d07658",
"sha256:b48dd9e5736957707fce1d9253fb0772bcf80480198c7790e21fed73fee61240",
"sha256:ce1adec823738e2d7c6af019fc38f58b4204bacfc782e4430373578c672f3833",
"sha256:ce8bfbc212729e9a643f5f5d77f7a93394e032eda1e2d8799ae902d08add747e",
"sha256:dbf479bd481774ad217e9db5674868eee8f01dfe3868f61753328895ae7da61a",
"sha256:e70a1ea6f198cace1a492397bdd0a46e640201120973293d6c48031e370d6a87",
"sha256:eca8ecf2b6b3225d93c723e6a2f51143d9195ac407f69e979363cdde344b93bb",
"sha256:fcceb6736085bf19291b707bc67c8cebe05330bd02268e9b8eba6d28a1905fce"
],
"index": "pypi",
"version": "==3.3.0"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
],
"version": "==1.7.1"
},
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
"python-magic": {
"hashes": [
"sha256:1a2c81e8f395c744536369790bd75094665e9644110a6623bcc3bbea30f03973",
@ -156,13 +504,37 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.4.25"
},
"python-slugify": {
"hashes": [
"sha256:00003397f4e31414e922ce567b3a4da28cf1436a53d332c9aeeb51c7d8c469fd",
"sha256:8c0016b2d74503eb64761821612d58fcfc729493634b1eb0575d8f5b4aa1fbcf"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==6.1.1"
},
"python-telegram-bot": {
"hashes": [
"sha256:534f5bb0ff4ca34c9252e97e0b3bcdab81d97be0eb4821682a361cb426c00e55",
"sha256:baeff704baa2ac3dc17a944c02da888228ad258e89be2e5bcbd13a8a5102d573"
],
"index": "pypi",
"version": "==13.11"
},
"pytz": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
],
"index": "pypi",
"version": "==2021.1"
"version": "==2022.1"
},
"pytz-deprecation-shim": {
"hashes": [
"sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
"sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==0.1.0.post0"
},
"requests": {
"hashes": [
@ -180,42 +552,90 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.3.1"
},
"s2sphere": {
"hashes": [
"sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf",
"sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"
],
"version": "==0.2.5"
},
"selenium": {
"hashes": [
"sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
"sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
"sha256:14d28a628c831c105d38305c881c9c7847199bfd728ec84240c5e86fa1c9bd5a"
],
"index": "pypi",
"version": "==3.141.0"
"version": "==4.1.3"
},
"shapely": {
"hashes": [
"sha256:052eb5b9ba756808a7825e8a8020fb146ec489dd5c919e7d139014775411e688",
"sha256:1641724c1055459a7e2b8bbe47ba25bdc89554582e62aec23cb3f3ca25f9b129",
"sha256:17df66e87d0fe0193910aeaa938c99f0b04f67b430edb8adae01e7be557b141b",
"sha256:182716ffb500d114b5d1b75d7fd9d14b7d3414cef3c38c0490534cc9ce20981a",
"sha256:2df5260d0f2983309776cb41bfa85c464ec07018d88c0ecfca23d40bfadae2f1",
"sha256:35be1c5d869966569d3dfd4ec31832d7c780e9df760e1fe52131105685941891",
"sha256:46da0ea527da9cf9503e66c18bab6981c5556859e518fe71578b47126e54ca93",
"sha256:4c10f317e379cc404f8fc510cd9982d5d3e7ba13a9cfd39aa251d894c6366798",
"sha256:4f3c59f6dbf86a9fc293546de492f5e07344e045f9333f3a753f2dda903c45d1",
"sha256:60e5b2282619249dbe8dc5266d781cc7d7fb1b27fa49f8241f2167672ad26719",
"sha256:617bf046a6861d7c6b44d2d9cb9e2311548638e684c2cd071d8945f24a926263",
"sha256:6593026cd3f5daaea12bcc51ae5c979318070fefee210e7990cb8ac2364e79a1",
"sha256:6871acba8fbe744efa4f9f34e726d070bfbf9bffb356a8f6d64557846324232b",
"sha256:791477edb422692e7dc351c5ed6530eb0e949a31b45569946619a0d9cd5f53cb",
"sha256:8e7659dd994792a0aad8fb80439f59055a21163e236faf2f9823beb63a380e19",
"sha256:8f15b6ce67dcc05b61f19c689b60f3fe58550ba994290ff8332f711f5aaa9840",
"sha256:90a3e2ae0d6d7d50ff2370ba168fbd416a53e7d8448410758c5d6a5920646c1d",
"sha256:a3774516c8a83abfd1ddffb8b6ec1b0935d7fe6ea0ff5c31a18bfdae567b4eba",
"sha256:a5c3a50d823c192f32615a2a6920e8c046b09e07a58eba220407335a9cd2e8ea",
"sha256:b40cc7bb089ae4aa9ddba1db900b4cd1bce3925d2a4b5837b639e49de054784f",
"sha256:da38ed3d65b8091447dc3717e5218cc336d20303b77b0634b261bc5c1aa2bae8",
"sha256:de618e67b64a51a0768d26a9963ecd7d338a2cf6e9e7582d2385f88ad005b3d1",
"sha256:e3afccf0437edc108eef1e2bb9cc4c7073e7705924eb4cd0bf7715cd1ef0ce1b"
"sha256:0ca96a3314b7a38a3bb385531469de1fcf2b2c2979ec2aa4f37b4c70632cf1ad",
"sha256:2020fda37c708d44a613c020cea09e81e476f96866f348afc2601e66c0e71db1",
"sha256:2381ce0aff67d569eb509bcc051264aa5fbdc1fdd54f4c09963d0e09f16a8f1b",
"sha256:363df36370f28fdc7789857929f6ff27e659f64087b4c89f7a47ed43bd3bfe4d",
"sha256:3a3602ba2e7715ddd5d4114173dec83d3181bfb2497e8589676c284aa739fd67",
"sha256:3e792635e92c9aacd1452a589a4fa2970114b6a9b1165e09655481f6e58970f5",
"sha256:437fff3b6274be26ffa3e450de711ee01e436324b5a405952add2146227e3eb5",
"sha256:44cb895b1710f7559c28d69dfa08cafe4f58cd4b7a87091a55bdf6711ad9ad66",
"sha256:493902923fdd135316161a4ece5294ba3ce81accaa54540d2af3b93f7231143a",
"sha256:54aeb2a57978ce731fd52289d0e1deee7c232d41aed53091f38776378f644184",
"sha256:5a420e7112b55a1587412a5b03ebf59e302ddd354da68516d3721718f6b8a7c5",
"sha256:679789d774cfe09ca05118cab78c0a6a42985b3ed23bc93606272a4509b4df28",
"sha256:69d5352fb977655c85d2f40a05ae24fc5053cccee77d0a8b1f773e54804e723e",
"sha256:83f3c8191d30ae0e3dd557434c48ca591d75342d5a3f42fc5148ec42796be624",
"sha256:89bc5f3abc1ccbc7682c2e1664153c4f8f125fa9c24bff4abca48685739d5636",
"sha256:8cf7331f61780506976fe2175e069d898e1b04ace73be21aad55c3ee92e58e3a",
"sha256:9248aad099ecf228fbdd877b0c668823dd83c48798cf04d49a1be75167e3a7ce",
"sha256:93ff06ff05fbe2be843b93c7b1ad8292e56e665ba01b4708f75ae8a757972e9f",
"sha256:aea1e87450adffba3d04ccbaa790df719bb7aa23b05ac797ad16be236a5d0db8",
"sha256:b4d35e72022b2dbf152d476b0362596011c674ff68be9fc8f2e68e71d86502ca",
"sha256:b82fc74d5efb11a71283c4ed69b4e036997cc70db4b73c646207ddf0476ade44",
"sha256:bab5ff7c576588acccd665ecce2a0fe7b47d4ce0398f2d5c1e5b2e27d09398d2",
"sha256:bc6063875182515d3888180cc4cbdbaa6443e4a4386c4bb25499e9875b75dcac",
"sha256:c4c366e18edf91196a399f8f0f046f93516002e6d8af0b57c23e7c7d91944b16",
"sha256:dc0f46212f84c57d13189fc33cf61e13eee292704d7652e931e4b51c54b0c73c",
"sha256:f109064bdb0753a6bac6238538cfeeb4a09739e2d556036b343b2eabeb9520b2"
],
"index": "pypi",
"version": "==1.7.1"
"version": "==1.8.1.post1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
},
"sortedcontainers": {
"hashes": [
"sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88",
"sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"
],
"version": "==2.4.0"
},
"soupsieve": {
"hashes": [
"sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb",
"sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.1"
},
"svgwrite": {
"hashes": [
"sha256:ca63d76396d1f6f099a2b2d8cf1419e1c1de8deece9a2b7f4da0632067d71d43",
"sha256:d304a929f197d31647c287c10eee0f64378058e1c83a9df83433a5980864e59f"
],
"markers": "python_version >= '3.6'",
"version": "==1.4.2"
},
"tabulate": {
"hashes": [
@ -225,37 +645,135 @@
"index": "pypi",
"version": "==0.8.9"
},
"text-unidecode": {
"hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.3"
},
"tornado": {
"hashes": [
"sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb",
"sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c",
"sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288",
"sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95",
"sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558",
"sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe",
"sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791",
"sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d",
"sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326",
"sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b",
"sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4",
"sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c",
"sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910",
"sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5",
"sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c",
"sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0",
"sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675",
"sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd",
"sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f",
"sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c",
"sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea",
"sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6",
"sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05",
"sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd",
"sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575",
"sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a",
"sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37",
"sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795",
"sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f",
"sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32",
"sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c",
"sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01",
"sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4",
"sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2",
"sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921",
"sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085",
"sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df",
"sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102",
"sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5",
"sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68",
"sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
],
"markers": "python_version >= '3.5'",
"version": "==6.1"
},
"trio": {
"hashes": [
"sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070",
"sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a"
],
"markers": "python_version >= '3.7'",
"version": "==0.20.0"
},
"trio-websocket": {
"hashes": [
"sha256:5b558f6e83cc20a37c3b61202476c5295d1addf57bd65543364e0337e37ed2bc",
"sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"
],
"markers": "python_version >= '3.5'",
"version": "==0.9.2"
},
"tweepy": {
"hashes": [
"sha256:b28d72073b794141ad1cfdc34cb52b83b14d6b34cd4c5ea9d47bb85159b656e8",
"sha256:bd3af045bed9cd6a838c32a1344d48191096e5cb930012452f8397b12c89d785"
"sha256:8ba5774ac1663b09e5fce1b030daf076f2c9b3ddbf2e7e7ea0bae762e3b1fe3e",
"sha256:f281bb53ab3ba999ff5e3d743d92d3ed543ee5551c7250948f9e56190ec7a43e"
],
"index": "pypi",
"version": "==4.0.0"
"version": "==4.8.0"
},
"tzdata": {
"hashes": [
"sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9",
"sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"
],
"markers": "python_version >= '3.6'",
"version": "==2022.1"
},
"tzlocal": {
"hashes": [
"sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09",
"sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"
],
"markers": "python_version >= '3.6'",
"version": "==4.1"
},
"urllib3": {
"extras": [
"secure",
"socks"
],
"hashes": [
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.8"
"version": "==1.26.9"
},
"webdriver-manager": {
"hashes": [
"sha256:50a6e174106542f5335cacc387cec7ada26812babc1aeca61c208a1bab2ac2c5",
"sha256:c6d81590aae6fc0fb10cf7dd20c8c1b9bb043501f9cf62c316a854a0de841e32"
"sha256:2eb7c2fe38ec5b06e2090164923e4dfb7c3ac4e7140333a3de9c7956f5047858",
"sha256:b5b91b5df83181e002263fe27296967a5b19cb1ebe8e4a63ee83538394037df4"
],
"index": "pypi",
"version": "==3.4.2"
"version": "==3.5.4"
},
"websocket-client": {
"hashes": [
"sha256:074e2ed575e7c822fc0940d31c3ac9bb2b1142c303eafcf3e304e6ce035522e8",
"sha256:6278a75065395418283f887de7c3beafb3aa68dada5cacbe4b214e8d26da499b"
"sha256:50b21db0058f7a953d67cc0445be4b948d7fc196ecbeb8083d68d94628e4abf6",
"sha256:722b171be00f2b90e1d4fb2f2b53146a536ca38db1da8ff49c972a4e1365d0ef"
],
"markers": "python_version >= '3.6'",
"version": "==1.3.1"
"markers": "python_version >= '3.7'",
"version": "==1.3.2"
},
"wsproto": {
"hashes": [
"sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b",
"sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"
],
"markers": "python_version >= '3.7'",
"version": "==1.1.0"
}
},
"develop": {}

14
__main__.py

@ -1,4 +1,5 @@
import configparser
from logging import DEBUG
import time
from colorama import Fore, Back, Style
import platform
@ -48,11 +49,11 @@ main_config.read('./configs/mainconf.ini')
source = main_config.get('DATA', 'SOURCE')
if main_config.getboolean('DISCORD', 'ENABLE'):
from defDiscord import sendDis
sendDis("Started", main_config)
sendDis("Started", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID'))
def service_exit(signum, frame):
if main_config.getboolean('DISCORD', 'ENABLE'):
from defDiscord import sendDis
sendDis("Service Stop", main_config)
sendDis("Service Stop", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID'))
raise SystemExit("Service Stop")
signal.signal(signal.SIGTERM, service_exit)
if os.path.isfile("lookup_route.py"):
@ -82,6 +83,7 @@ try:
except pytz.exceptions.UnknownTimeZoneError:
tz = pytz.UTC
last_ra_count = None
print(len(planes), "Planes configured")
while True:
datetime_tz = datetime.now(tz)
if datetime_tz.hour == 0 and datetime_tz.minute == 0:
@ -138,12 +140,12 @@ try:
for planeData in data['ac']:
data_indexed[planeData[icao_key].upper()] = planeData
for key, obj in planes.items():
try:
if key in data_indexed.keys():
if api_version == 1:
obj.run_adsbx_v1(data_indexed[key.upper()])
elif api_version == 2:
obj.run_adsbx_v2(data_indexed[key.upper()])
except KeyError:
else:
obj.run_empty()
else:
for obj in planes.values():
@ -206,10 +208,10 @@ except Exception as e:
except OSError:
pass
import logging
logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s')
logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s',level=logging.DEBUG)
logging.Formatter.converter = time.gmtime
logging.error(e)
logging.error(str(traceback.format_exc()))
from defDiscord import sendDis
sendDis(str("Error Exiting: " + str(e) + "Failed on " + key), main_config, "crash_latest.log")
sendDis(str("Error Exiting: " + str(e) + " Failed on " + "https://globe.adsbexchange.com/?icao=" + key), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log")
raise e

214
aircraft_type_fuel_consumption_rates.json

@ -0,0 +1,214 @@
{
"EA50": {
"name": "Eclipse 550",
"galph": 76,
"category": "VLJ"
},
"LJ31": {
"name": "Learjet 31",
"galph": 202,
"category": "Light"
},
"LJ40": {
"name": "Learjet 40",
"galph": 207,
"category": "Light"
},
"PC24": {
"name": "Pilatus PC-24",
"galph": 154,
"category": "Light"
},
"LJ45": {
"name": "Learjet 45",
"galph": 205,
"category": "Super Light"
},
"LJ70": {
"name": "Learjet 70",
"galph": 198,
"category": "Super Light"
},
"LJ75": {
"name": "Learjet 75",
"galph": 199,
"category": "Super Light"
},
"G150": {
"name": "Gulfstream G150",
"galph": 228,
"category": "Midsize"
},
"LJ60": {
"name": "Learjet 60",
"galph": 239,
"category": "Midsize"
},
"GALX": {
"name": "Gulfstream G200",
"galph": 278,
"category": "Super Midsize"
},
"G280": {
"name": "Gulfstream G280",
"galph": 297,
"category": "Super Midsize"
},
"GLF5": {
"name": "Gulfstream G500",
"galph": 447,
"category": "Large"
},
"GLF6": {
"name": "Gulfstream G650",
"galph": 503,
"category": "Ultra Long Range"
},
"PC12": {
"name": "Pilatus PC-12",
"galph": 66,
"category": "Turboprop Aircraft"
},
"GLEX": {
"name": "Global",
"galph": 500,
"category": "Ultra Long Range"
}
,
"CL30": {
"name": "Challenger 300",
"galph": 295,
"category": "Super Midsize"
}, "B742": {
"name": "Boeing 747-200",
"galph": 3830,
"category": "Large"
}, "T38": {
"name": "T-38 Talon",
"galph": 375,
"category": "Fighter"
}, "WB57": {
"name": "Martin B-57 Canberra",
"galph": 531,
"category": "Twinjet Tactical Bomber and Reconnaissance"
}, "B74S": {
"name": "747 SP",
"galph": 2289,
"category": "Large"
}, "B752": {
"name": "757 200",
"galph": 877,
"category": "Large"
},
"B738": {
"name": "737 800",
"galph": 832,
"category": "Medium"
},
"B737": {
"name": "737 700",
"galph": 796,
"category": "Medium"
},
"A320": {
"name": "A320",
"galph": 800,
"category": "Medium"
},
"P3": {
"name": "Lockheed Orion P3",
"galph": 671,
"category": "Turboprop"
},
"C750": {
"name": "Cessna 750 Citation X",
"galph": 347,
"category": "Small Private Jet"
},
"FA7X": {
"name": "Dassult Falcon 7X",
"galph": 380,
"category": "Small Private Jet"
},
"F900": {
"name": "Dassult Falcon 900",
"galph": 347,
"category": "Small Private Jet"
},
"H25B": {
"name": "Hawker 750/850",
"galph": 270,
"category": "Small Private Jet"
},
"C680": {
"name": "Cessna 680 Citation",
"galph": 247,
"category": "Small Private Jet"
},
"GLF3": {
"name": "Gulfstream 3",
"galph": 568,
"category": "Heavy Private Jet"
},
"GLF4": {
"name": "Gulfstream 4",
"galph": 479,
"category": "Heavy Private Jet"
},
"CL60": {
"name": "Bombardier CL-600 Challenge",
"galph": 262,
"category": "Mid-size Private Jet"
},
"A139": {
"name": "Agusta-Bell AW139",
"galph": 150,
"category": "Medium Utility Helicopter"
},
"GL5T": {
"name": "Global 5000",
"galph": 455,
"category": "Heavy Private Jet"
},
"GA6C": {
"name": "Gulfstream G600",
"galph": 458,
"category": "Heavy Private Jet"
},
"A337": {
"name": "Airbus Beluga XL",
"galph": 1800,
"category": "Large Transport Aircraft"
},
"A3ST": {
"name": "Airbus Beluga",
"galph": 1260,
"category": "Large Transport Aircraft"
},
"F2TH": {
"name": "Dassault Falcon 2000",
"galph": 245,
"category": "Medium Private Jet"
},
"GA5C": {
"name": "Gulfstream G500",
"galph": 402,
"category": "Large Private Jet"
},
"C130": {
"name": "Lockheed C130",
"galph": 740,
"category": "Medium Cargo"
},
"B762": {
"name": "Boeing 767 200",
"galph": 1722,
"category": "Wide-body"
},
"B772": {
"name": "Boeing 777 200",
"galph": 2300,
"category": "Wide-body"
}
}

2
calculate_headings.py

@ -15,7 +15,6 @@ def calculate_cardinal(d):
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = int(round(d / (360. / len(dirs))))
card = dirs[ix % len(dirs)]
print(card)
return card
def calculate_deg_change(new_heading, original_heading):
"""Calculates change between two headings, returns negative degree if change is left, positive if right"""
@ -29,6 +28,7 @@ def calculate_deg_change(new_heading, original_heading):
track_change = normal
if direction == "left":
track_change *= -1
track_change = round(track_change, 2)
print(f"Track change of {track_change}° which is {direction}")
return track_change

11
configs/mainconf.ini

@ -36,4 +36,13 @@ API_KEY = googleapikey
[DISCORD]
ENABLE = FALSE
USERNAME = usernamehere
URL = webhookurl
URL = webhookurl
[TFRS]
URL = http://127.0.0.1:5000/detailed_all
[TWITTER]
#GLOBAL TWITTER CONSUMER KEY AND CONSUMERS SECRET ONLY NEED TO BE HERE NOW
ENABLE = False
CONSUMER_KEY = ck
CONSUMER_SECRET = cs

25
configs/plane1.ini

@ -2,8 +2,12 @@
#Plane to track, based of ICAO or ICAO24 which is the unique transponder address of a plane.
ICAO = icaohere
#Optional Per Plane Override
#DATA_LOSS_MINS = 20
#Optional Per Plane Overrides
; OVERRIDE_REG=
; OVERRIDE_ICAO_TYPE=
; OVERRIDE_TYPELONG =
; DATA_LOSS_MINS = 20
[MAP]
#Map to create from Google Static Maps or screenshot global tar1090 from globe.adsbexchange.com
@ -21,8 +25,6 @@ TYPES = [small_airport, medium_airport, large_airport]
[TWITTER]
ENABLE = FALSE
TITLE =
CONSUMER_KEY = ckhere
CONSUMER_SECRET = cshere
ACCESS_TOKEN = athere
ACCESS_TOKEN_SECRET = atshere
@ -39,4 +41,17 @@ URL = webhookurl
#Role to tag optional, the role ID
ROLE_ID =
Title =
USERNAME = plane-notify
USERNAME = plane-notify
[META]
ENABLE = False
FB_PAGE_ID =
IG_USER_ID =
ACCESS_TOKEN =
[TELEGRAM]
ENABLE = False
TITLE =
ROOM_ID =
BOT_TOKEN =

76
defAirport.py

@ -1,45 +1,41 @@
import csv
import math
def add_airport_region(airport_dict):
# Get full region/state name from iso region name
with open('./dependencies/regions.csv', 'r', encoding='utf-8') as regions_csv:
regions_csv = csv.DictReader(filter(lambda row: row[0] != '#', regions_csv))
for region in regions_csv:
if region['code'] == airport_dict['iso_region']:
airport_dict['region'] = region['name']
return airport_dict
#Get full region/state name from iso region name
with open('./dependencies/regions.csv', 'r', encoding='utf-8') as regions_csv:
regions_csv = csv.DictReader(filter(lambda row: row[0]!='#', regions_csv))
for region in regions_csv:
if region['code'] == airport_dict['iso_region']:
airport_dict['region'] = region['name']
return airport_dict
def getClosestAirport(latitude, longitude, allowed_types):
from geopy.distance import geodesic
plane = (latitude, longitude)
closest_airport_dict = dict()
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0] != '#', airport_csv))
for airport in airport_csv_reader:
if airport['type'] in allowed_types:
airport_coord = float(airport['latitude_deg']), float(airport['longitude_deg'])
airport_dist = float((geodesic(plane, airport_coord).mi))
if not closest_airport_dict or airport_dist < closest_airport_dist:
closest_airport_dict = airport
closest_airport_dist = airport_dist
closest_airport_dict['distance_mi'] = closest_airport_dist
# Convert indent key to icao key as its labeled icao in other places not ident
closest_airport_dict['icao'] = closest_airport_dict.pop('gps_code')
closest_airport_dict = add_airport_region(closest_airport_dict)
return closest_airport_dict
from geopy.distance import geodesic
plane = (latitude, longitude)
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0]!='#', airport_csv))
for airport in airport_csv_reader:
if airport['type'] in allowed_types:
airport_coord = float(airport['latitude_deg']), float(airport['longitude_deg'])
airport_dist = float((geodesic(plane, airport_coord).mi))
if "closest_airport_dict" not in locals():
closest_airport_dict = airport
closest_airport_dist = airport_dist
elif airport_dist < closest_airport_dist:
closest_airport_dict = airport
closest_airport_dist = airport_dist
closest_airport_dict['distance_mi'] = closest_airport_dist
#Convert indent key to icao key as its labeled icao in other places not ident
closest_airport_dict['icao'] = closest_airport_dict.pop('gps_code')
closest_airport_dict = add_airport_region(closest_airport_dict)
return closest_airport_dict
def get_airport_by_icao(icao):
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0] != '#', airport_csv))
for airport in airport_csv_reader:
if airport['gps_code'] == icao:
matching_airport = airport
# Convert indent key to icao key as its labeled icao in other places not ident
matching_airport['icao'] = matching_airport.pop('gps_code')
break
matching_airport = add_airport_region(matching_airport)
return matching_airport
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0]!='#', airport_csv))
for airport in airport_csv_reader:
if airport['gps_code'] == icao:
matching_airport = airport
#Convert indent key to icao key as its labeled icao in other places not ident
matching_airport['icao'] = matching_airport.pop('gps_code')
break
matching_airport = add_airport_region(matching_airport)
return matching_airport

10
defDiscord.py

@ -1,12 +1,14 @@
def sendDis(message, config, file_name = None, role_id = None):
def sendDis(message, config, role_id = None, *file_names):
import requests
from discord_webhook import DiscordWebhook
if role_id != None:
message += f" <@&{role_id}>"
webhook = DiscordWebhook(url=config.get('DISCORD', 'URL'), content=message[0:1999], username=config.get('DISCORD', 'USERNAME'))
if file_name != None:
with open(file_name, "rb") as f:
webhook.add_file(file=f.read(), filename=file_name)
if file_names != []:
for file_name in file_names:
with open(file_name, "rb") as f:
webhook.add_file(file=f.read(), filename=file_name)
try:
webhook.execute()
except requests.exceptions.RequestException:

83
defSS.py

@ -1,22 +1,28 @@
import json
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False):
def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False, overrides={}):
chrome_options = webdriver.ChromeOptions()
chrome_options.headless = True
chrome_options.add_argument('window-size=800,800')
chrome_options.add_argument('ignore-certificate-errors')
chrome_options.add_argument("--enable-logging --v=1")
#Plane images issue loading when in headless setting agent fixes.
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
import os
import platform
if platform.system() == "Linux" and os.geteuid()==0:
chrome_options.add_argument('--no-sandbox') # required when running as root user. otherwise you would get no sandbox errors.
browser = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options)
browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
url = f"https://globe.adsbexchange.com/?{url_params}"
print(url)
browser.set_page_load_timeout(80)
browser.get(url)
WebDriverWait(browser, 40).until(lambda d: d.execute_script("return jQuery.active == 0"))
remove_id_elements = ["show_trace", "credits", 'infoblock_close', 'selected_photo_link', "history_collapse"]
for element in remove_id_elements:
try:
@ -35,37 +41,58 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac
except:
print("Couldn't disable sidebar on map")
#Remove share
try:
element = browser.find_element_by_xpath("//*[contains(text(), 'Share')]")
browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
except:
print("Couldn't remove share button from map")
# try:
# element = browser.find_element_by_xpath("//*[contains(text(), 'Copy Link')]")
# browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
# except Exception as e:
# print("Couldn't remove share button from map", e)
#browser.execute_script("toggleFollow()")
if enable_labels:
browser.find_element_by_tag_name('body').send_keys('l')
if enable_track_labels:
browser.find_element_by_tag_name('body').send_keys('k')
WebDriverWait(browser, 40).until(lambda d: d.execute_script("return jQuery.active == 0"))
try:
photo_box = browser.find_element_by_id("silhouette")
except:
pass
else:
import requests, json
photo_list = json.loads(requests.get("https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/photo-list.json").text)
if "icao" in url_params:
import re
from selenium.webdriver.support import expected_conditions as EC
time.sleep(15)
icao = re.search('icao=(.+?)&', url_params).group(1).lower()
print(icao)
if icao in photo_list.keys():
browser.execute_script("arguments[0].id = 'airplanePhoto';", photo_box)
browser.execute_script(f"arguments[0].src = 'https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/images/{photo_list[icao]['reg']}.jpg';", photo_box)
copyright = browser.find_element_by_id("copyrightInfo")
browser.execute_script("arguments[0].id = 'copyrightInfoFreeze';", copyright)
browser.execute_script("$('#copyrightInfoFreeze').css('font-size', '12px');")
browser.execute_script(f"arguments[0].appendChild(document.createTextNode('Image © {photo_list[icao]['photographer']}'))", copyright)
if 'reg' in overrides.keys():
element = browser.find_element_by_id("selected_registration")
browser.execute_script(f"arguments[0].innerText = '* {overrides['reg']}'", element)
reg = overrides['reg']
else:
try:
reg = browser.find_element_by_id("selected_registration").get_attribute("innerHTML")
print(reg)
except Exception as e:
print("Couldn't find reg in tar1090", e)
reg = None
if reg is not None:
try:
try:
myElem = WebDriverWait(browser, 150).until(EC.presence_of_element_located((By.ID, 'silhouette')))
photo_box = browser.find_element_by_id("silhouette")
except:
photo_box = browser.find_element_by_id("airplanePhoto")
finally:
import requests, json
photo_list = json.loads(requests.get("https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/photo-list.json").text)
if reg in photo_list.keys():
browser.execute_script("arguments[0].id = 'airplanePhoto';", photo_box)
browser.execute_script(f"arguments[0].src = 'https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/images/{reg}.jpg';", photo_box)
copyright = browser.find_element_by_id("copyrightInfo")
browser.execute_script("arguments[0].id = 'copyrightInfoFreeze';", copyright)
browser.execute_script("$('#copyrightInfoFreeze').css('font-size', '12px');")
#browser.execute_script("""var element = arguments[0];
#element.parentNode.removeChild(element.firstChild);""", copyright)
browser.execute_script(f"arguments[0].appendChild(document.createTextNode('Image © {photo_list[reg]['photographer']}'))", copyright)
except Exception as e:
print("Error on changing photo", e)
if 'type' in overrides.keys():
element = browser.find_element_by_id("selected_icaotype")
browser.execute_script(f"arguments[0].innerText = '* {overrides['type']}'", element)
if 'typelong' in overrides.keys():
element = browser.find_element_by_id("selected_typelong")
browser.execute_script(f"arguments[0].innerText = '* {overrides['typelong']}'", element)
time.sleep(5)
browser.save_screenshot(file_path)
browser.quit()
@ -76,4 +103,4 @@ def generate_adsbx_screenshot_time_params(timestamp):
print(timestamp_dt)
start_time = timestamp_dt - timedelta(minutes=1)
time_params = "&showTrace=" + timestamp_dt.strftime("%Y-%m-%d") + "&startTime=" + start_time.strftime("%H:%M:%S") + "&endTime=" + timestamp_dt.strftime("%H:%M:%S")
return time_params
return time_params

38
defTelegram.py

@ -0,0 +1,38 @@
def sendTeleg(photo, message, config):
import telegram
sent = False
retry_c = 0
while sent == False:
try:
bot = telegram.Bot(token=config.get('TELEGRAM', 'BOT_TOKEN'), request=telegram.utils.request.Request(connect_timeout=20, read_timeout=20))
sent = bot.send_photo(chat_id=config.get('TELEGRAM', 'ROOM_ID'), photo=photo, caption=message, parse_mode=telegram.ParseMode.MARKDOWN, timeout=20, )
except Exception as err:
print('err.args:')
print(err.args)
print(f"Unexpected {err=}, {type(err)=}")
print("\nString err:\n"+str(err))
if retry_c > 4:
print('Telegram attempts exceeded. Message not sent.')
break
elif str(err) == 'Unauthorized':
print('Invalid Telegram bot token, message not sent.')
break
elif str(err) == 'Timed out':
retry_c += 1
print('Telegram timeout count: '+str(retry_c))
pass
elif str(err) == 'Chat not found':
print('Invalid Telegram Chat ID, message not sent.')
break
elif str(err)[:35] == '[Errno 2] No such file or directory':
print('Telegram module couldn\'t find an image to send.')
break
elif str(err) == 'Media_caption_too_long':
print('Telegram image caption lenght exceeds 1024 characters. Message not send.')
break
else:
print('[X] Unknown Telegram error. Message not sent.')
break
else:
print("Telegram message successfully sent.")
return sent

9
defTweet.py

@ -1,9 +0,0 @@
# Authenticate to Twitter
def tweepysetup(config):
import tweepy
#DOCU
#https://realpython.com/twitter-bot-python-tweepy/
auth = tweepy.OAuthHandler(config.get('TWITTER', 'CONSUMER_KEY'), config.get('TWITTER', 'CONSUMER_SECRET'))
auth.set_access_token(config.get('TWITTER', 'ACCESS_TOKEN'), config.get('TWITTER', 'ACCESS_TOKEN_SECRET'))
tweet_api = tweepy.API(auth, wait_on_rate_limit=True)
return tweet_api

52
fuel_calc.py

@ -0,0 +1,52 @@
import json
import requests
def get_avg_fuel_price():
import pandas as pd
from bs4 import BeautifulSoup
try:
response = requests.get("https://www.airnav.com/fuel/report.html")
soup = BeautifulSoup(response.text, 'lxml') # Parse the HTML as a string
table = soup.find_all('table')[3] # Grab the first table
nation_wide = table.find_all('tr')[4]
nation_wide_avg_jeta = nation_wide.find_all('td')[6]
print("Current nationwide Jet-A fuel price avg per G is $", nation_wide_avg_jeta.text[1:])
return(float(nation_wide_avg_jeta.text[1:]))
except Exception as e:
print(e)
return None
def fuel_calculation(aircraft_icao_type, minutes):
"""Calculates fuel usage, price, c02 output of a flight depending on aircraft type and flight length"""
with open("aircraft_type_fuel_consumption_rates.json", "r") as f:
fuellist = json.loads(f.read())
#avg_fuel_price_per_gallon = 5.08
fuel_flight_info = {}
if aircraft_icao_type in fuellist.keys():
avg_fuel_price_per_gallon = get_avg_fuel_price()
galph = fuellist[aircraft_icao_type]["galph"]
fuel_used_gal = round(galph * (minutes/60), 2)
fuel_flight_info["fuel_price"] = round(fuel_used_gal * avg_fuel_price_per_gallon)
fuel_used_kg = fuel_used_gal * 3.04
c02_tons = round((fuel_used_kg * 3.15 ) / 907.185)
fuel_flight_info['fuel_used_kg'] = round(fuel_used_kg)
fuel_flight_info["fuel_used_gal"] = round(fuel_used_gal)
fuel_flight_info['fuel_used_lters'] = round(fuel_used_gal*3.78541)
fuel_flight_info["fuel_used_pds"] = round(fuel_used_kg * 2.20462)
fuel_flight_info["c02_tons"] = c02_tons
print ("Fuel info", fuel_flight_info)
return fuel_flight_info
else:
print("Can't calculate fuel info unknown aircraft ICAO type")
return None
def fuel_message(fuel_info):
cost = "{:,}".format(fuel_info['fuel_price'])
gallons = "{:,}".format(fuel_info['fuel_used_gal'])
lters = "{:,}".format(fuel_info['fuel_used_lters'])
pds = "{:,}".format(fuel_info['fuel_used_pds'])
kgs = "{:,}".format(fuel_info['fuel_used_kg'])
fuel_message = f"~ {gallons} gallons ({lters} liters). \n~ {pds} pds ({kgs} kg) of jet fuel used. \n~ ${cost} cost of fuel. \n~ {fuel_info['c02_tons']} tons of CO2 emissions."
print(fuel_message)
return fuel_message
#fuel_info = fuel_calculation("B738", 180)
#fuel_message(fuel_info)

51
meta_toolkit.py

@ -0,0 +1,51 @@
import requests
import json
def post_fb(page_id, file_path, message, access_token):
"""Posts to Facebook with Image"""
import os
file_name = os.path.basename(file_path)
files= {'image':(file_name, open(file_path, 'rb'), "multipart/form-data")}
url = f"https://graph.facebook.com/{page_id}/photos?message={message}&access_token={access_token}"
resp = requests.post(url, files=files)
resp.raise_for_status()
print("Facebook Post Response: ", resp.json())
return resp.json()
def get_fb_post_image_link(post_id, access_token):
"""Returns Highest Resolution image link of a Facebook Post by FBID"""
url = f"https://graph.facebook.com/{post_id}?fields=images&access_token={access_token}"
resp = requests.get(url)
resp.raise_for_status()
image_url = resp.json()['images'][0]['source']
print("Highest Resoulution Image URL for FBID", post_id, "is", image_url)
return image_url
def post_to_instagram(ig_user_id, access_token, image_url, caption):
"""Posts to Instagram"""
post_url = f'https://graph.facebook.com/v13.0/{ig_user_id}/media'
payload = {
'caption': caption,
'access_token': access_token,
'image_url': image_url
}
resp = requests.post(post_url, data=payload)
resp.raise_for_status()
print("IG Media Response:", resp.json())
result = json.loads(resp.text)
if 'id' in result:
creation_id = result['id']
second_url = f'https://graph.facebook.com/v13.0/{ig_user_id}/media_publish'
second_payload = {
'creation_id': creation_id,
'access_token':access_token
}
resp = requests.post(second_url, data=second_payload)
resp.raise_for_status()
print('Posted to Instagram', caption, "IG response:", resp.json())
else:
print('Could not post to Instagram: ', resp.json())
def post_to_meta_both(fb_page_id, ig_user_id, file_path, message, access_token):
"""Posts to Facebook and Instagram"""
post_info = post_fb(fb_page_id, file_path, message, access_token)
fb_image_link = get_fb_post_image_link(post_info['id'], access_token)
post_to_instagram(ig_user_id, access_token, fb_image_link, message)

18
modify_image.py

@ -1,4 +1,4 @@
def append_airport(filename, airport):
def append_airport(filename, airport, text_credit=None):
from PIL import Image, ImageDraw, ImageFont
distance_mi = airport['distance_mi']
icao = airport['icao']
@ -27,15 +27,17 @@ def append_airport(filename, airport):
#Header Box
draw.rectangle(((401, 738), (549, 760)), fill= navish)
#ADSBX Logo
draw.rectangle(((658, 762), (800, 782)), fill= white)
adsbx = Image.open("./dependencies/ADSBX_Logo.png")
adsbx = adsbx.resize((25, 25), Image.ANTIALIAS)
image.paste(adsbx, (632, 757), adsbx)
#
# adsbx = Image.open("./dependencies/ADSBX_Logo.png")
# adsbx = adsbx.resize((25, 25), Image.ANTIALIAS)
# image.paste(adsbx, (632, 757), adsbx)
#Create Text
#ADSBX Credit
(x, y) = (660, 760)
text = "adsbexchange.com"
draw.text((x, y), text, fill=black, font=head_font)
if text_credit is not None:
draw.rectangle(((658, 762), (800, 782)), fill= white)
(x, y) = (660, 760)
text = text_credit
draw.text((x, y), text, fill=black, font=head_font)
#Nearest Airport Header
(x, y) = (422, 740)
text = "Nearest Airport"

327
planeClass.py

@ -41,14 +41,21 @@ class Plane:
self.track = None
self.last_track = None
self.circle_history = None
self.type = None
if self.config.has_option('DATA', 'DATA_LOSS_MINS'):
self.data_loss_mins = self.config.getint('DATA', 'DATA_LOSS_MINS')
else:
self.data_loss_mins = Plane.main_config.getint('DATA', 'DATA_LOSS_MINS')
#Setup Tweepy
if self.config.getboolean('TWITTER', 'ENABLE'):
from defTweet import tweepysetup
self.tweet_api = tweepysetup(self.config)
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
import tweepy
twitter_app_auth = tweepy.OAuthHandler(Plane.main_config.get('TWITTER', 'CONSUMER_KEY'), Plane.main_config.get('TWITTER', 'CONSUMER_SECRET'))
twitter_app_auth.set_access_token(config.get('TWITTER', 'ACCESS_TOKEN'), config.get('TWITTER', 'ACCESS_TOKEN_SECRET'))
self.tweet_api = tweepy.API(twitter_app_auth, wait_on_rate_limit=True)
try:
self.latest_tweet_id = self.tweet_api.user_timeline(count = 1)[0]
except IndexError:
self.latest_tweet_id = None
#Setup PushBullet
if self.config.getboolean('PUSHBULLET', 'ENABLE'):
from pushbullet import Pushbullet
@ -57,7 +64,7 @@ class Plane:
def run_opens(self, ac_dict):
#Parse OpenSky Vector
from colorama import Fore, Back, Style
self.printheader("head")
self.print_header("BEGIN")
#print (Fore.YELLOW + "OpenSky Sourced Data: ", ac_dict)
try:
self.__dict__.update({'icao' : ac_dict.icao24.upper(), 'callsign' : ac_dict.callsign, 'latitude' : ac_dict.latitude, 'longitude' : ac_dict.longitude, 'on_ground' : bool(ac_dict.on_ground), 'squawk' : ac_dict.squawk, 'track' : float(ac_dict.heading)})
@ -72,14 +79,14 @@ class Plane:
except ValueError as e:
print("Got data but some data is invalid!")
print(e)
self.printheader("foot")
self.print_header("END")
else:
self.feeding = True
self.run_check()
def run_adsbx_v1(self, ac_dict):
#Parse ADBSX V1 Vector
from colorama import Fore, Back, Style
self.printheader("head")
self.print_header("BEGIN")
#print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
try:
#postime is divided by 1000 to get seconds from milliseconds, from timestamp expects secs.
@ -92,7 +99,7 @@ class Plane:
print("Got data but some data is invalid!")
print(e)
print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
self.printheader("foot")
self.print_header("END")
else:
self.feeding = True
self.run_check()
@ -100,7 +107,7 @@ class Plane:
def run_adsbx_v2(self, ac_dict):
#Parse ADBSX V2 Vector
from colorama import Fore, Back, Style
self.printheader("head")
self.print_header("BEGIN")
print(ac_dict)
try:
self.__dict__.update({'icao' : ac_dict['hex'].upper(), 'latitude' : float(ac_dict['lat']), 'longitude' : float(ac_dict['lon']), 'speed': ac_dict['gs']})
@ -116,6 +123,8 @@ class Plane:
self.on_ground = True
if ac_dict.get('flight') is not None:
self.callsign = ac_dict.get('flight').strip()
else:
self.callsign = None
if ac_dict.get('dbFlags') is not None:
self.db_flags = ac_dict['dbFlags']
if 'nav_modes' in ac_dict:
@ -142,7 +151,7 @@ class Plane:
print("Got data but some data is invalid!")
print(e)
print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
self.printheader("foot")
self.print_header("END")
else:
#Error Handling for bad data, sometimes it would seem to be ADSB Decode error
if (not self.on_ground) and self.speed <= 10:
@ -169,13 +178,19 @@ class Plane:
]
output = list(filter(None, output))
return tabulate(output, [], 'fancy_grid')
def printheader(self, type):
def print_header(self, note):
from colorama import Fore, Back, Style
if type == "head":
header = str("--------- " + self.conf_file_path + " ---------------------------- ICAO: " + self.icao + " ---------------------------------------")
elif type == "foot":
header = "----------------------------------------------------------------------------------------------------"
print(Back.MAGENTA + header[0:100] + Style.RESET_ALL)
if note == "BEGIN":
header = f"---BEGIN---------{self.conf_file_path}"
elif note == "END":
header = f"---END"
remaning_len = 85 - len(header)
for x in range(0, remaning_len):
header += "-"
header += f"ICAO: {self.icao}---"
if note =="END":
header += Style.RESET_ALL + "\n"
print(Back.MAGENTA + header + Style.RESET_ALL)
def get_time_since(self, datetime_obj):
if datetime_obj != None:
time_since = datetime.now() - datetime_obj
@ -186,7 +201,7 @@ class Plane:
if self.config.has_option('MAP', 'OVERLAYS'):
overlays = self.config.get('MAP', 'OVERLAYS')
else:
overlays = ""
overlays = "null"
return overlays
def route_info(self):
from lookup_route import lookup_route, clean_data
@ -243,7 +258,7 @@ class Plane:
return route_to
def run_empty(self):
self.printheader("head")
self.print_header("BEGIN")
self.feeding = False
self.run_check()
def run_check(self):
@ -352,17 +367,19 @@ class Plane:
area = f"{municipality}, {state}"
location_string = (f"{area}, {country_code}")
print (Fore.GREEN + "Country Code:", country_code, "State:", state, "Municipality:", municipality + Style.RESET_ALL)
title_switch = {
"reg": self.reg,
"callsign": self.callsign,
"icao": self.icao,
}
dynamic_title = self.callsign or self.reg or self.icao
#Set Discord Title
if self.config.getboolean('DISCORD', 'ENABLE'):
self.dis_title = (title_switch.get(self.config.get('DISCORD', 'TITLE')) or "NA").strip() if self.config.get('DISCORD', 'TITLE') in title_switch.keys() else self.config.get('DISCORD', 'TITLE')
if self.config.get('DISCORD', 'TITLE') in ["DYNAMIC", "callsign"]:
self.dis_title = dynamic_title
else:
self.dis_title = self.config.get('DISCORD', 'TITLE')
#Set Twitter Title
if self.config.getboolean('TWITTER', 'ENABLE'):
self.twitter_title = (title_switch.get(self.config.get('TWITTER', 'TITLE')) or "NA") if self.config.get('TWITTER', 'TITLE') in title_switch.keys() else self.config.get('TWITTER', 'TITLE')
if self.config.get('TWITTER', 'TITLE') in ["DYNAMIC", "callsign"]:
self.twitter_title = dynamic_title
else:
self.twitter_title = self.config.get('TWITTER', 'TITLE')
#Takeoff and Land Notification
if self.tookoff or self.landed:
route_to = None
@ -392,26 +409,37 @@ class Plane:
self.takeoff_time = None
elif self.landed:
landed_time_msg = None
message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
landed_time = None
if self.icao != "A835AF":
message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
dirty_message = None
else:
message = (f"{type_header} {location_string}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
dirty_message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
print (message)
#Google Map or tar1090 screenshot
if self.config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
from defMap import getMap
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
elif self.config.get('MAP', 'OPTION') == "ADSBX":
elif Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays=" + self.get_adsbx_map_overlays()
get_adsbx_screenshot(self.map_file_name, url_params)
from modify_image import append_airport
append_airport(self.map_file_name, nearest_airport_dict)
text_credit = self.config.get('MAP', 'TEXT_CREDIT') if self.config.has_option('MAP', 'TEXT_CREDIT') else None
append_airport(self.map_file_name, nearest_airport_dict, text_credit)
else:
raise ValueError("Map option not set correctly in this planes conf")
#Telegram
if self.config.has_section('TELEGRAM') and self.config.getboolean('TELEGRAM', 'ENABLE'):
from defTelegram import sendTeleg
photo = open(self.map_file_name, "rb")
sendTeleg(photo, message, self.config)
#Discord
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = f"{self.dis_title} {message}".strip()
dis_message = f"{self.dis_title} {message}".strip() if dirty_message is None else f"{self.dis_title} {dirty_message}".strip()
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
sendDis(dis_message, self.config, self.map_file_name, role_id = role_id)
sendDis(dis_message, self.config, role_id, self.map_file_name)
#PushBullet
if self.config.getboolean('PUSHBULLET', 'ENABLE'):
with open(self.map_file_name, "rb") as pic:
@ -420,12 +448,50 @@ class Plane:
self.pb_channel.push_file(**map_data)
#Twitter
if self.config.getboolean('TWITTER', 'ENABLE'):
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Reg: {self.reg} On Ground: {str(self.on_ground)} Alt: {str(self.alt_ft)} Last Contact: {str(time_since_contact)} Trigger: {trigger_type}"
self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text)
self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + message).strip()), media_ids=[twitter_media_map_obj.media_id]).id
import tweepy
try:
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Reg: {self.reg} On Ground: {str(self.on_ground)} Alt: {str(self.alt_ft)} Last Contact: {str(time_since_contact)} Trigger: {trigger_type}"
self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text)
self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + message).strip()), media_ids=[twitter_media_map_obj.media_id]).id
except tweepy.errors.TweepyException as e:
print(e)
raise Exception(self.icao) from e
#Meta
if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'):
from meta_toolkit import post_to_meta_both
post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN"))
os.remove(self.map_file_name)
if self.landed:
if self.known_to_airport is not None and self.nearest_from_airport is not None and self.known_to_airport != self.nearest_from_airport:
from defAirport import get_airport_by_icao
from geopy.distance import geodesic
known_to_airport = get_airport_by_icao(self.known_to_airport)
nearest_from_airport = get_airport_by_icao(self.nearest_from_airport)
from_coord = (nearest_from_airport['latitude_deg'], nearest_from_airport['longitude_deg'])
to_coord = (known_to_airport['latitude_deg'], known_to_airport['longitude_deg'])
distance_mi = float(geodesic(from_coord, to_coord).mi)
distance_nm = distance_mi / 1.150779448
distance_message = f"{'{:,}'.format(round(distance_mi))} mile ({'{:,}'.format(round(distance_nm))} NM) flight from {nearest_from_airport['iata_code']} to {nearest_airport_dict['iata_code']}\n"
else:
distance_message = ""
if landed_time is not None and self.type is not None:
print("Running fuel info calc")
flight_time_min = landed_time.total_seconds() / 60
from fuel_calc import fuel_calculation, fuel_message
fuel_info = fuel_calculation(self.type, flight_time_min)
if fuel_info is not None:
fuel_message = fuel_message(fuel_info)
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = f"{self.dis_title} {distance_message} \nFlight Fuel Info ```{fuel_message}```".strip()
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
sendDis(dis_message, self.config, role_id)
if self.config.getboolean('TWITTER', 'ENABLE'):
try:
self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + distance_message + " " + fuel_message).strip()), in_reply_to_status_id = self.latest_tweet_id).id
except tweepy.errors.TweepyException as e:
print(e)
raise Exception(self.icao) from e
self.latest_tweet_id = None
self.recheck_route_time = None
self.known_to_airport = None
@ -436,13 +502,19 @@ class Plane:
route_to = self.route_info()
if route_to != None:
print(route_to)
#Telegram
if self.config.has_section('TELEGRAM') and self.config.getboolean('TELEGRAM', 'ENABLE'):
message = f"{self.dis_title} {route_to}".strip()
photo = open(self.map_file_name, "rb")
from defTelegram import sendTeleg
sendTeleg(photo, message, self.config)
#Discord
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = f"{self.dis_title} {route_to}".strip()
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
sendDis(dis_message, self.config, role_id = role_id)
sendDis(dis_message, self.config, role_id)
#Twitter
if self.config.getboolean('TWITTER', 'ENABLE'):
if self.config.getboolean('TWITTER', 'ENABLE') and self.icao == 'A835AF':
#tweet = self.tweet_api.user_timeline(count = 1)[0]
self.latest_tweet_id = self.tweet_api.update_status(status = f"{self.twitter_title} {route_to}".strip(), in_reply_to_status_id = self.latest_tweet_id).id
@ -469,15 +541,15 @@ class Plane:
squawk_message = (f"{self.dis_title} Squawking {self.last_emergency[1]} {emergency_squawks[self.squawk]}").strip()
print(squawk_message)
#Google Map or tar1090 screenshot
if self.config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
if self.config.get('MAP', 'OPTION') == "ADSBX":
if Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays=" + self.get_adsbx_map_overlays()
get_adsbx_screenshot(self.map_file_name, url_params)
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = (self.dis_title + " " + squawk_message)
sendDis(dis_message, self.config, self.map_file_name)
sendDis(dis_message, self.config, None, self.map_file_name)
os.remove(self.map_file_name)
#Realizes first time seeing emergency, stores time and type
elif self.squawk in emergency_squawks.keys() and not self.emergency_already_triggered and not self.on_ground:
@ -498,7 +570,7 @@ class Plane:
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}"
get_adsbx_screenshot(self.map_file_name, url_params)
sendDis(dis_message, self.config, self.map_file_name)
sendDis(dis_message, self.config, None, self.map_file_name)
#elif mode in ["Althold", "VNAV", "LNAV"] and self.sel_nav_alt != None:
# sendDis((dis_message + ", Sel Alt. " + str(self.sel_nav_alt) + ", Current Alt. " + str(self.alt_ft)), self.config)
else:
@ -509,7 +581,7 @@ class Plane:
print("Nav altitude is now", self.sel_nav_alt)
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = (self.dis_title + " Sel. alt. " + str("{:,} ft".format(self.sel_nav_alt)))
sendDis(dis_message,self.config)
sendDis(dis_message, self.config)
#Circling
if self.last_track is not None:
import time
@ -543,15 +615,138 @@ class Plane:
#rp = (points.representative_point()) #A represenative point, not centroid,
print(cent)
#print(rp)
distance_to_centroid = geodesic(aircraft_coords, cent.coords).mi
distance_to_centroid = round(geodesic(aircraft_coords, cent.coords).mi, 2)
print(f"Distance to centroid of circling coordinates {distance_to_centroid} miles")
if distance_to_centroid <= 15:
print("Within 15 miles of centroid, CIRCLING")
#Finds Nearest Airport
from defAirport import getClosestAirport
nearest_airport_dict = getClosestAirport(self.latitude, self.longitude, ["small_airport", "medium_airport", "large_airport"])
from calculate_headings import calculate_from_bearing, calculate_cardinal
from_bearing = calculate_from_bearing((float(nearest_airport_dict['latitude_deg']), float(nearest_airport_dict['longitude_deg'])), (self.latitude, self.longitude))
cardinal = calculate_cardinal(from_bearing)
#Finds Nearest TFR or in TFR
from shapely.geometry import MultiPoint
from geopy.distance import geodesic
from shapely.geometry.polygon import Polygon
from shapely.geometry import Point
import requests, json
tfr_url = Plane.main_config.get("TFRS", "URL")
response = requests.get(tfr_url, timeout=30)
tfrs = json.loads(response.text)
closest_tfr = None
in_tfr = None
for tfr in tfrs:
if in_tfr is not None:
break
elif tfr['details'] is not None and 'shapes' in tfr['details'].keys():
for index, shape in enumerate(tfr['details']['shapes']):
if 'txtName' not in shape.keys():
shape['txtName'] = 'shape_'+str(index)
polygon = None
if shape['type'] == "poly":
points = shape['points']
elif shape['type'] == "circle":
from functools import partial
import pyproj
from shapely.ops import transform
from shapely.geometry import Point
proj_wgs84 = pyproj.Proj('+proj=longlat +datum=WGS84')
def geodesic_point_buffer(lat, lon, km):
# Azimuthal equidistant projection
aeqd_proj = '+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0'
project = partial(
pyproj.transform,
pyproj.Proj(aeqd_proj.format(lat=lat, lon=lon)),
proj_wgs84)
buf = Point(0, 0).buffer(km * 1000) # distance in metres
return transform(project, buf).exterior.coords[:]
radius_km = float(shape['radius']) * 1.852
b = geodesic_point_buffer(shape['lat'], shape['lon'], radius_km)
points = []
for coordinate in b:
points.append([coordinate[1], coordinate[0]])
elif shape['type'] in ["polyarc", "polyexclude"]:
points = shape['all_points']
aircraft_location = Point(self.latitude, self.longitude)
if polygon is None:
polygon = Polygon(points)
if polygon.contains(aircraft_location):
in_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName']}
break
else:
point_dists = []
for point in points:
from geopy.distance import geodesic
point = tuple(point)
point_dists.append(float((geodesic((self.latitude, self.longitude), point).mi)))
distance = min(point_dists)
if closest_tfr is None:
closest_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName'], 'distance' : round(distance)}
elif distance < closest_tfr['distance']:
closest_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName'], 'distance' : round(distance)}
if in_tfr is not None:
for shape in in_tfr['info']['details']['shapes']:
if shape['txtName'] == in_tfr['closest_shape_name']:
valDistVerUpper, valDistVerLower = int(shape['valDistVerUpper']), int(shape['valDistVerLower'])
print("In TFR based off location checking alt next", in_tfr)
break
if not (self.alt_ft >= valDistVerLower and self.alt_ft <= valDistVerUpper):
print("But not in alt of TFR")
closest_tfr = in_tfr
closest_tfr['distance'] = 0
in_tfr = None
if in_tfr is None:
print("Closest TFR", closest_tfr)
#Generate Map
import staticmaps
context = staticmaps.Context()
context.set_tile_provider(staticmaps.tile_provider_OSM)
if in_tfr is not None:
shapes = in_tfr['info']['details']['shapes']
else:
shapes = closest_tfr['info']['details']['shapes']
def draw_poly(context, pairs):
pairs.append(pairs[0])
context.add_object(
staticmaps.Area(
[staticmaps.create_latlng(lat, lng) for lat, lng in pairs],
fill_color=staticmaps.parse_color("#FF000033"),
width=2,
color=staticmaps.parse_color("#8B0000"),
)
)
return context
for shape in shapes:
if shape['type'] == "poly":
pairs = shape['points']
context = draw_poly(context, pairs)
elif shape['type'] == "polyarc" or shape['type'] == "polyexclude":
pairs = shape['all_points']
context = draw_poly(context, pairs)
elif shape['type'] =="circle":
center = [shape['lat'], shape['lon']]
center1 = staticmaps.create_latlng(center[0], center[1])
context.add_object(staticmaps.Circle(center1, (float(shape['radius']) * 1.852), fill_color=staticmaps.parse_color("#FF000033"), color=staticmaps.parse_color("#8B0000"), width=2))
context.add_object(staticmaps.Marker(center1, color=staticmaps.RED))
def tfr_image(context, aircraft_coords):
from PIL import Image
heading = self.track
heading *= -1
im = Image.open('./dependencies/ac.png')
im_rotate = im.rotate(heading, resample=Image.BICUBIC)
import tempfile
rotated_file = f"{tempfile.gettempdir()}/rotated_ac.png"
im_rotate.save(rotated_file)
pos = staticmaps.create_latlng(aircraft_coords[0], aircraft_coords[1])
marker = staticmaps.ImageMarker(pos, rotated_file, origin_x=35, origin_y=35)
context.add_object(marker)
image = context.render_cairo(1000, 1000)
os.remove(rotated_file)
tfr_map_filename = f"{tempfile.gettempdir()}/{self.icao}_TFR_.png"
image.write_to_png(tfr_map_filename)
return tfr_map_filename
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=10&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}"
get_adsbx_screenshot(self.map_file_name, url_params)
@ -559,20 +754,46 @@ class Plane:
if "touchngo" in self.circle_history.keys():
message = f"Doing touch and goes at {nearest_airport_dict['icao']}"
else:
message = f"Circling over {nearest_airport_dict['icao']} at {self.alt_ft}ft"
message = f"Circling over {nearest_airport_dict['icao']} at {self.alt_ft}ft."
else:
message = f"Circling {round(nearest_airport_dict['distance_mi'], 2)}mi {cardinal} of {nearest_airport_dict['icao']}, {nearest_airport_dict['name']} at {self.alt_ft}ft"
message = f"Circling {round(nearest_airport_dict['distance_mi'], 2)}mi {cardinal} of {nearest_airport_dict['icao']}, {nearest_airport_dict['name']} at {self.alt_ft}ft. "
tfr_map_filename = None
if in_tfr is not None:
message += f" Inside TFR {in_tfr['info']['NOTAM']}, a TFR for {in_tfr['info']['Type'].title()}"
tfr_map_filename = tfr_image(context, (self.latitude, self.longitude))
elif in_tfr is None and "distance" in closest_tfr.keys() and closest_tfr["distance"] <= 20:
message += f" {closest_tfr['distance']} miles from TFR {closest_tfr['info']['NOTAM']}, a TFR for {closest_tfr['info']['Type']}"
tfr_map_filename = tfr_image(context, (self.latitude, self.longitude))
elif in_tfr is None and "distance" not in closest_tfr.keys():
message += f" near TFR {closest_tfr['info']['NOTAM']}, a TFR for {closest_tfr['info']['Type']}"
raise Exception(message)
print(message)
#Telegram
if self.config.has_section('TELEGRAM') and self.config.getboolean('TELEGRAM', 'ENABLE'):
photo = open(self.map_file_name, "rb")
from defTelegram import sendTeleg
sendTeleg(photo, message, self.config)
if self.config.getboolean('DISCORD', 'ENABLE'):
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
sendDis(message, self.config, self.map_file_name, role_id)
if tfr_map_filename is not None:
sendDis(message, self.config, role_id, self.map_file_name, tfr_map_filename)
elif tfr_map_filename is None:
sendDis(message, self.config, role_id, self.map_file_name)
if self.config.getboolean('TWITTER', 'ENABLE'):
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Distance to centroid: {distance_to_centroid}, Total change: {total_change}"
self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text)
tweet = self.tweet_api.user_timeline(count = 1)[0]
self.latest_tweet_id = self.tweet_api.update_status(status = f"{self.twitter_title} {message}".strip(), in_reply_to_status_id = self.latest_tweet_id, media_ids=[twitter_media_map_obj.media_id]).id
media_ids = [twitter_media_map_obj.media_id]
if tfr_map_filename is not None:
twitter_media_tfr_map_obj = self.tweet_api.media_upload(tfr_map_filename)
media_ids.append(twitter_media_tfr_map_obj.media_id)
elif tfr_map_filename is None:
print("No TFR Map")
tweet = f"{self.twitter_title} {message}".strip()
self.tweet_api.update_status(status = tweet, media_ids=media_ids)
#Meta
if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'):
from meta_toolkit import post_to_meta_both
post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN"))
self.circle_history['triggered'] = True
elif abs(total_change) <= 360 and self.circle_history["triggered"]:
print("No Longer Circling, trigger cleared")
@ -600,7 +821,7 @@ class Plane:
hours, remainder = divmod(elapsed_time.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)
print((f"Time Since Take off {int(hours)} Hours : {int(minutes)} Mins : {int(seconds)} Secs"))
self.printheader("foot")
self.print_header("END")
def check_new_ras(self, ras):
for ra in ras:
if self.recent_ra_types == {} or ra['acas_ra']['advisory'] not in self.recent_ra_types.keys():
@ -627,7 +848,7 @@ class Plane:
from defDiscord import sendDis
dis_message = f"{self.dis_title} {ra_message}"
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
sendDis(dis_message, self.config, self.map_file_name, role_id = role_id)
sendDis(dis_message, self.config, role_id, self.map_file_name)
#if twitter
def expire_ra_types(self):
if self.recent_ra_types != {}:

Loading…
Cancel
Save