├── .gitignore
├── favicon.ico
├── img
├── logo.png
├── blank.png
├── fluidicon.png
├── openhand.cur
├── closedhand.cur
├── hab-spinner.gif
├── marker-you.png
├── markers
│ ├── iss.png
│ ├── nyan.gif
│ ├── shadow.png
│ ├── antenna.png
│ ├── hab_nyan.gif
│ ├── nyan-afro.gif
│ ├── nyan-coin.gif
│ ├── nyan-cool.gif
│ ├── nyan-mon.gif
│ ├── balloon-pop.png
│ ├── marker_hole.png
│ ├── nyan-mummy.gif
│ ├── nyan-pirate.gif
│ ├── antenna-bronze.png
│ ├── antenna-gold.png
│ ├── antenna-silver.png
│ ├── antenna-white.png
│ ├── balloon-xmark.png
│ ├── nyan-gameboy.gif
│ ├── nyan-pumpkin.gif
│ ├── nyan-tothemax.gif
│ ├── payload-recovered.png
│ ├── payload-not-recovered.png
│ ├── target.svg
│ ├── payload.svg
│ ├── balloon.svg
│ ├── car.svg
│ └── parachute.svg
├── sondehub_logo.png
├── apple-touch-icon.png
├── icons
│ ├── icon_x192.png
│ ├── icon_x512.png
│ ├── nyan_icon_x192.png
│ ├── nyan_icon_x512.png
│ ├── maskable_icon_x128.png
│ ├── maskable_icon_x192.png
│ ├── maskable_icon_x384.png
│ ├── maskable_icon_x48.png
│ ├── maskable_icon_x512.png
│ ├── maskable_icon_x72.png
│ └── maskable_icon_x96.png
├── sondehub_au_amateur.png
├── splash
│ ├── splash-wide.png
│ └── splash-narrow.png
└── screenshots
│ ├── screenshot1.png
│ ├── screenshot2.png
│ └── screenshot3.png
├── resources
├── car.psd
├── antenna.ai
├── logo.psd
├── antenna.psd
├── balloon.psd
├── fluid-icon.psd
├── nyan_icon.psd
├── parachute.psd
├── concept-app-tablet.png
├── concept-app-portrait.png
├── mobiletracker-screencap.png
└── antenna.svg
├── css
├── fullscreen.png
├── fullscreen@2x.png
├── images
│ ├── layers.png
│ ├── layers-2x.png
│ ├── marker-icon.png
│ ├── marker-icon-2x.png
│ └── marker-shadow.png
├── leaflet.fullscreen.css
├── habitat-font.css
├── layout.css
├── base.css
├── skeleton.css
├── leaflet.css
└── main.css
├── font
├── HabitatFont.eot
├── HabitatFont.ttf
├── HabitatFont.woff
└── Roboto-regular.woff
├── js
├── pwa.js
├── chasecar.lib.js
├── suncalc.js
├── Leaflet.fullscreen.min.js
├── flight_doc.js
├── L.Terminator.js
├── plot_config.js
├── format.js
├── L.TileLayer.NoGap.js
├── leaflet.antimeridian-src.js
└── rbush.js
├── serve.py
├── opensearchspec.xml
├── DEVELOPER_README.md
├── glyphs
├── icon-balloon.svg
├── icon-clock_simple.svg
├── icon-compass_simple.svg
├── icon-code.svg
├── icon-clock.svg
├── icon-weather.svg
└── icon-compass.svg
├── LICENSE
├── manifest.json
├── README.md
├── service-worker.template.js
└── embed-preview.html
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.pyc
3 | *.log
4 | /index.html
5 | /service-worker.js
6 | tiles/
7 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/favicon.ico
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/logo.png
--------------------------------------------------------------------------------
/img/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/blank.png
--------------------------------------------------------------------------------
/img/fluidicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/fluidicon.png
--------------------------------------------------------------------------------
/img/openhand.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/openhand.cur
--------------------------------------------------------------------------------
/resources/car.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/car.psd
--------------------------------------------------------------------------------
/css/fullscreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/fullscreen.png
--------------------------------------------------------------------------------
/font/HabitatFont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/font/HabitatFont.eot
--------------------------------------------------------------------------------
/font/HabitatFont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/font/HabitatFont.ttf
--------------------------------------------------------------------------------
/img/closedhand.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/closedhand.cur
--------------------------------------------------------------------------------
/img/hab-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/hab-spinner.gif
--------------------------------------------------------------------------------
/img/marker-you.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/marker-you.png
--------------------------------------------------------------------------------
/img/markers/iss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/iss.png
--------------------------------------------------------------------------------
/img/markers/nyan.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan.gif
--------------------------------------------------------------------------------
/js/pwa.js:
--------------------------------------------------------------------------------
1 | if ('serviceWorker' in navigator) {
2 | navigator.serviceWorker.register('/service-worker.js')
3 | }
--------------------------------------------------------------------------------
/resources/antenna.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/antenna.ai
--------------------------------------------------------------------------------
/resources/logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/logo.psd
--------------------------------------------------------------------------------
/css/fullscreen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/fullscreen@2x.png
--------------------------------------------------------------------------------
/css/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/images/layers.png
--------------------------------------------------------------------------------
/font/HabitatFont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/font/HabitatFont.woff
--------------------------------------------------------------------------------
/img/markers/shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/shadow.png
--------------------------------------------------------------------------------
/img/sondehub_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/sondehub_logo.png
--------------------------------------------------------------------------------
/resources/antenna.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/antenna.psd
--------------------------------------------------------------------------------
/resources/balloon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/balloon.psd
--------------------------------------------------------------------------------
/css/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/images/layers-2x.png
--------------------------------------------------------------------------------
/font/Roboto-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/font/Roboto-regular.woff
--------------------------------------------------------------------------------
/img/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/apple-touch-icon.png
--------------------------------------------------------------------------------
/img/icons/icon_x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/icon_x192.png
--------------------------------------------------------------------------------
/img/icons/icon_x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/icon_x512.png
--------------------------------------------------------------------------------
/img/markers/antenna.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/antenna.png
--------------------------------------------------------------------------------
/img/markers/hab_nyan.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/hab_nyan.gif
--------------------------------------------------------------------------------
/img/markers/nyan-afro.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-afro.gif
--------------------------------------------------------------------------------
/img/markers/nyan-coin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-coin.gif
--------------------------------------------------------------------------------
/img/markers/nyan-cool.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-cool.gif
--------------------------------------------------------------------------------
/img/markers/nyan-mon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-mon.gif
--------------------------------------------------------------------------------
/resources/fluid-icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/fluid-icon.psd
--------------------------------------------------------------------------------
/resources/nyan_icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/nyan_icon.psd
--------------------------------------------------------------------------------
/resources/parachute.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/parachute.psd
--------------------------------------------------------------------------------
/css/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/images/marker-icon.png
--------------------------------------------------------------------------------
/img/markers/balloon-pop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/balloon-pop.png
--------------------------------------------------------------------------------
/img/markers/marker_hole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/marker_hole.png
--------------------------------------------------------------------------------
/img/markers/nyan-mummy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-mummy.gif
--------------------------------------------------------------------------------
/img/markers/nyan-pirate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-pirate.gif
--------------------------------------------------------------------------------
/img/sondehub_au_amateur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/sondehub_au_amateur.png
--------------------------------------------------------------------------------
/img/splash/splash-wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/splash/splash-wide.png
--------------------------------------------------------------------------------
/css/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/css/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/css/images/marker-shadow.png
--------------------------------------------------------------------------------
/img/icons/nyan_icon_x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/nyan_icon_x192.png
--------------------------------------------------------------------------------
/img/icons/nyan_icon_x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/nyan_icon_x512.png
--------------------------------------------------------------------------------
/img/markers/antenna-bronze.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/antenna-bronze.png
--------------------------------------------------------------------------------
/img/markers/antenna-gold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/antenna-gold.png
--------------------------------------------------------------------------------
/img/markers/antenna-silver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/antenna-silver.png
--------------------------------------------------------------------------------
/img/markers/antenna-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/antenna-white.png
--------------------------------------------------------------------------------
/img/markers/balloon-xmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/balloon-xmark.png
--------------------------------------------------------------------------------
/img/markers/nyan-gameboy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-gameboy.gif
--------------------------------------------------------------------------------
/img/markers/nyan-pumpkin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-pumpkin.gif
--------------------------------------------------------------------------------
/img/markers/nyan-tothemax.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/nyan-tothemax.gif
--------------------------------------------------------------------------------
/img/splash/splash-narrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/splash/splash-narrow.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x128.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x192.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x384.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x48.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x512.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x72.png
--------------------------------------------------------------------------------
/img/icons/maskable_icon_x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/icons/maskable_icon_x96.png
--------------------------------------------------------------------------------
/img/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/img/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/img/screenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/screenshots/screenshot3.png
--------------------------------------------------------------------------------
/resources/concept-app-tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/concept-app-tablet.png
--------------------------------------------------------------------------------
/img/markers/payload-recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/payload-recovered.png
--------------------------------------------------------------------------------
/resources/concept-app-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/concept-app-portrait.png
--------------------------------------------------------------------------------
/img/markers/payload-not-recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/img/markers/payload-not-recovered.png
--------------------------------------------------------------------------------
/resources/mobiletracker-screencap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projecthorus/sondehub-amateur-tracker/HEAD/resources/mobiletracker-screencap.png
--------------------------------------------------------------------------------
/serve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from http.server import HTTPServer, SimpleHTTPRequestHandler, test
3 | import sys
4 |
5 | class CORSRequestHandler (SimpleHTTPRequestHandler):
6 | def end_headers (self):
7 | self.send_header('Access-Control-Allow-Origin', '*')
8 | SimpleHTTPRequestHandler.end_headers(self)
9 |
10 | if __name__ == '__main__':
11 | test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
12 |
--------------------------------------------------------------------------------
/opensearchspec.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SondeHub Amateur
4 | SondeHub Amateur - Search
5 | UTF-8
6 | hello@rgp.io
7 | https://sondehub.org/favicon.ico
8 | Luke Prior
9 |
10 |
11 |
--------------------------------------------------------------------------------
/DEVELOPER_README.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | To get a copy of the code and run a test web server:
4 |
5 | 1. [Fork the repository](https://github.com/projecthorus/sondehub-amateur-tracker/fork) by visiting [https://github.com/projecthorus/sondehub-amateur-tracker/fork](https://github.com/projecthorus/sondehub-amateur-tracker/fork).
6 | 2. Clone the repository with your git tool of choice.
7 | 3. Run `build.sh` to generate `index.html` and `service-worker.js`.
8 | 4. Run `python serve.py` to run a simple web server to (This requires python 3.x)
9 | 5. Visit [http://localhost:8000](http://localhost:8000) to view the local version of the server!
10 |
--------------------------------------------------------------------------------
/glyphs/icon-balloon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/img/markers/target.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/markers/payload.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2016 Rossen Georgiev
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/css/leaflet.fullscreen.css:
--------------------------------------------------------------------------------
1 | .leaflet-control-fullscreen a {
2 | background:#fff url(fullscreen.png) no-repeat 0 0;
3 | background-size:26px 52px;
4 | }
5 | .leaflet-touch .leaflet-control-fullscreen a {
6 | background-position: 2px 2px;
7 | }
8 | .leaflet-fullscreen-on .leaflet-control-fullscreen a {
9 | background-position:0 -26px;
10 | }
11 | .leaflet-touch.leaflet-fullscreen-on .leaflet-control-fullscreen a {
12 | background-position: 2px -24px;
13 | }
14 |
15 | @media (orientation: landscape) {
16 | .leaflet-control-fullscreen {
17 | position:relative;
18 | top:-25px;
19 | }
20 | .leaflet-fullscreen-on .leaflet-control-fullscreen {
21 | position:relative;
22 | top:0px;
23 | }
24 | }
25 |
26 |
27 | /* Do not combine these two rules; IE will break. */
28 | .leaflet-container:-webkit-full-screen {
29 | width:100%!important;
30 | height:100%!important;
31 | }
32 | .leaflet-container.leaflet-fullscreen-on {
33 | width:100%!important;
34 | height:100%!important;
35 | }
36 |
37 | .leaflet-pseudo-fullscreen {
38 | position:fixed!important;
39 | width:100%!important;
40 | height:100%!important;
41 | top:0!important;
42 | left:0!important;
43 | z-index:99999;
44 | }
45 |
46 | @media
47 | (-webkit-min-device-pixel-ratio:2),
48 | (min-resolution:192dpi) {
49 | .leaflet-control-fullscreen a {
50 | background-image:url(fullscreen@2x.png);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/js/chasecar.lib.js:
--------------------------------------------------------------------------------
1 | /* SondeHub ChaseCar lib
2 | * Uploads geolocation for chase cars to SondeHub
3 | *
4 | * Author: Rossen Gerogiev
5 | * Requires: jQuery
6 | *
7 | * Updated to SondeHub v2 by Mark Jessop
8 | */
9 |
10 | ChaseCar = {
11 | db_uri: "https://api.v2.sondehub.org/amateur/listeners", // Sondehub API
12 | };
13 |
14 | // Updated SondeHub position upload function.
15 | // Refer PUT listeners API here: https://generator.swagger.io/?url=https://raw.githubusercontent.com/projecthorus/sondehub-infra/main/swagger.yaml
16 | // @callsign string
17 | // @position object (geolocation position object)
18 | ChaseCar.updatePosition = function(callsign, position) {
19 | if(!position || !position.coords) return;
20 |
21 | // Set altitude to zero if not provided.
22 | _position_alt = ((!!position.coords.altitude) ? position.coords.altitude : 0);
23 |
24 | var _doc = {
25 | "software_name": "SondeHub-Amateur",
26 | "software_version": document.body.dataset.version,
27 | "uploader_callsign": callsign,
28 | "uploader_position": [position.coords.latitude, position.coords.longitude, _position_alt],
29 | "uploader_antenna": "Mobile Station",
30 | "uploader_contact_email": "none@none.com",
31 | "mobile": true
32 | };
33 |
34 | // push the doc to sondehub
35 | $.ajax({
36 | type: "PUT",
37 | url: ChaseCar.db_uri,
38 | contentType: "application/json; charset=utf-8",
39 | dataType: "json",
40 | data: JSON.stringify(_doc),
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SondeHub Amateur",
3 | "short_name": "SondeHub",
4 | "description": "A webapp for tracking amateur high altitude balloon flights.",
5 | "theme_color": "#00a3d3",
6 | "background_color": "#00a3d3",
7 | "display": "standalone",
8 | "categories": ["utilities"],
9 | "scope": "/",
10 | "start_url": "/",
11 | "icons": [
12 | {
13 | "src": "/img/icons/maskable_icon_x192.png",
14 | "type": "image/png",
15 | "sizes": "192x192",
16 | "purpose": "maskable"
17 | },
18 | {
19 | "src": "/img/icons/maskable_icon_x512.png",
20 | "type": "image/png",
21 | "sizes": "512x512",
22 | "purpose": "maskable"
23 | },
24 | {
25 | "src": "/img/icons/icon_x192.png",
26 | "type": "image/png",
27 | "sizes": "192x192",
28 | "purpose": "any"
29 | },
30 | {
31 | "src": "/img/icons/icon_x512.png",
32 | "type": "image/png",
33 | "sizes": "512x512",
34 | "purpose": "any"
35 | }
36 | ],
37 | "shortcuts": [
38 | {
39 | "name": "Nyan Mode",
40 | "short_name": "Nyan",
41 | "description": "Start the tracker with Nyan Cat mode enabled",
42 | "url": "/#!nyan=1",
43 | "icons": [{ "src": "/img/icons/nyan_icon_x192.png", "sizes": "192x192" }]
44 | }
45 | ],
46 | "screenshots": [
47 | {
48 | "src": "/img/screenshots/screenshot1.png",
49 | "type": "image/png",
50 | "sizes": "1242x2208"
51 | },
52 | {
53 | "src": "/img/screenshots/screenshot2.png",
54 | "type": "image/png",
55 | "sizes": "1242x2208"
56 | },
57 | {
58 | "src": "/img/screenshots/screenshot3.png",
59 | "type": "image/png",
60 | "sizes": "1242x2208"
61 | }
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SondeHub Amateur Tracker
2 |
3 | A fork of [sondehub-tracker](https://github.com/projecthorus/sondehub-tracker) for use with the [SondeHub Amateur ElasticSearch](https://github.com/projecthorus/sondehub-infra/wiki/ElasticSearch-Kibana-access) database.
4 |
5 | A webapp for tracking radiosondes. Works an desktop and mobile devices.
6 | The SondeHub Amateur tracker is a continuation of [tracker.habhub.org](https://tracker.habhub.org/).
7 |
8 | ## Features
9 |
10 | * Radiosonde Tracking using [SondeHub Amateur](https://github.com/projecthorus/sondehub-infra/wiki/ElasticSearch-Kibana-access) data.
11 | * Telemetry graph for each balloon
12 | * Near realtime weather overlays
13 | * Daylight cycle overlay, for long flights
14 | * Map tracker with Leaflet API
15 | * Run the app natively on IOS, Android, or desktop as a Progressive Wep App
16 |
17 | ### Geo position
18 |
19 | The app will ask for permission to use your location.
20 | This is required for some of the features. It is **important** to note that
21 | your location will not be made available or send to anyone.
22 |
23 | ## Browser requirements
24 |
25 | Any modern browser should be able to run the app. Some features are limited to Chromium based browsers.
26 |
27 | ## Contribute
28 |
29 | Don't hesitate to report any issues, or suggest improvements. Just visit the [issues page](https://github.com/projecthorus/sondehub-amateur-tracker/issues).
30 | Pull requests are welcome.
31 |
32 | ## Installation
33 |
34 | $ git clone https://github.com/projecthorus/sondehub-amateur-tracker.git
35 | $ ./build.sh
36 | $ python serve.py
37 |
38 | Visit [http://localhost:8000](http://localhost:8000) to view the local version of the tracker!
39 |
40 | ## Original design
41 |
42 | Author: Daniel Saul [@danielsaul](https://github.com/danielsaul)
43 |
--------------------------------------------------------------------------------
/img/markers/balloon.svg:
--------------------------------------------------------------------------------
1 |
59 |
--------------------------------------------------------------------------------
/glyphs/icon-clock_simple.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
60 |
61 |
--------------------------------------------------------------------------------
/glyphs/icon-compass_simple.svg:
--------------------------------------------------------------------------------
1 |
2 |
30 |
--------------------------------------------------------------------------------
/service-worker.template.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('install', function(event) {
2 | event.waitUntil(
3 | caches.open("{VER}").then(function(cache) {
4 | return cache.addAll(
5 | [
6 | '/css/base.css',
7 | '/css/skeleton.css',
8 | '/css/layout.css',
9 | '/css/habitat-font.css',
10 | '/css/main.css',
11 | '/css/leaflet.css',
12 | '/css/leaflet.fullscreen.css',
13 | '/js/leaflet.js',
14 | '/js/Leaflet.fullscreen.min.js',
15 | '/js/L.Terminator.js',
16 | '/js/L.TileLayer.NoGap.js',
17 | '/js/leaflet.antimeridian-src.js',
18 | '/js/paho-mqtt.js',
19 | '/js/jquery-1.12.4-min.js',
20 | '/js/iscroll.js',
21 | '/js/chasecar.lib.js',
22 | '/js/sondehub.js',
23 | '/js/app.js',
24 | '/js/colour-map.js',
25 | '/js/suncalc.js',
26 | '/js/flight_doc.js',
27 | '/js/format.js',
28 | '/js/rbush.js',
29 | '/js/pwa.js',
30 | '/js/_jquery.flot.js',
31 | '/js/plot_config.js',
32 | '/img/markers/balloon.svg',
33 | '/img/markers/car.svg',
34 | '/img/markers/parachute.svg',
35 | '/img/markers/payload.svg',
36 | '/img/markers/payload-not-recovered.png',
37 | '/img/markers/payload-recovered.png',
38 | '/img/markers/target.svg',
39 | '/img/markers/shadow.png',
40 | '/img/markers/balloon-pop.png',
41 | '/img/hab-spinner.gif',
42 | '/img/sondehub_logo.png',
43 | '/favicon.ico',
44 | '/font/HabitatFont.woff',
45 | '/font/Roboto-regular.woff',
46 | '/index.html'
47 | ]
48 | );
49 | })
50 | );
51 | });
52 |
53 | self.addEventListener('fetch', function (event) {
54 | event.respondWith(
55 | caches.match(event.request).then(function (response) {
56 | return response || fetch(event.request);
57 | }),
58 | );
59 | });
60 |
--------------------------------------------------------------------------------
/js/suncalc.js:
--------------------------------------------------------------------------------
1 | // Sun Position Calculations from https://github.com/mourner/suncalc
2 |
3 | // shortcuts for easier to read formulas
4 |
5 | var PI = Math.PI,
6 | sin = Math.sin,
7 | cos = Math.cos,
8 | tan = Math.tan,
9 | asin = Math.asin,
10 | atan = Math.atan2,
11 | acos = Math.acos,
12 | rad = PI / 180;
13 | // date/time constants and conversions
14 |
15 | var dayMs = 1000 * 60 * 60 * 24,
16 | J1970 = 2440588,
17 | J2000 = 2451545;
18 |
19 | var earthradm = 6371008.8; // earth mean radius in meters
20 |
21 | function suncalc_toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
22 | function suncalc_fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
23 | function suncalc_toDays(date) { return suncalc_toJulian(date) - J2000; }
24 |
25 |
26 | // general calculations for position
27 |
28 | var suncalc_e = rad * 23.4397; // obliquity of the Earth
29 |
30 | function rightAscension(l, b) { return atan(sin(l) * cos(suncalc_e) - tan(b) * sin(suncalc_e), cos(l)); }
31 | function declination(l, b) { return asin(sin(b) * cos(suncalc_e) + cos(b) * sin(suncalc_e) * sin(l)); }
32 |
33 | function suncalc_azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
34 | function suncalc_altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
35 |
36 | function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
37 |
38 | // general sun calculations
39 |
40 | function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
41 |
42 | function eclipticLongitude(M) {
43 |
44 | var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
45 | P = rad * 102.9372; // perihelion of the Earth
46 |
47 | return M + C + P + PI;
48 | }
49 |
50 | function sunCoords(d) {
51 |
52 | var M = solarMeanAnomaly(d),
53 | L = eclipticLongitude(M);
54 |
55 | return {
56 | dec: declination(L, 0),
57 | ra: rightAscension(L, 0)
58 | };
59 | }
60 |
61 | var SunCalc = {};
62 |
63 |
64 | // calculates sun position for a given date and latitude/longitude
65 |
66 | SunCalc.getPosition = function (date, lat, lng, ht) {
67 |
68 | var lw = rad * -lng,
69 | phi = rad * lat,
70 | d = suncalc_toDays(date),
71 |
72 | c = sunCoords(d),
73 | H = siderealTime(d, lw) - c.ra;
74 |
75 | return {
76 | azimuth: suncalc_azimuth(H, phi, c.dec),
77 | altitude: suncalc_altitude(H, phi, c.dec) + acos(earthradm / (earthradm + ht)) // adjust to horizon at altitude - From Steve Randall
78 | };
79 | };
80 |
81 |
--------------------------------------------------------------------------------
/resources/antenna.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/css/habitat-font.css:
--------------------------------------------------------------------------------
1 | /* Habitat Font
2 | * icons specifically created for habitat websites and apps
3 | *
4 | * Inspired from Font Awesome @ https://github.com/FortAwesome
5 | *
6 | * Author: Rossen Georgiev @ http://rossengeorgiev.github.com/
7 | */
8 |
9 | @font-face {
10 | font-family: 'HabitatFont';
11 | src: url('../font/HabitatFont.eot');
12 | src: url('../font/HabitatFont.eot?#iefix') format('embedded-opentype'),
13 | url('../font/HabitatFont.woff') format('woff'),
14 | url('../font/HabitatFont.ttf') format('truetype'),
15 | url('../font/HabitatFont.svg#habitatfontregular') format('svg');
16 | font-weight: normal;
17 | font-style: normal;
18 | }
19 |
20 | [class^="icon-"],
21 | [class*=" icon-"] {
22 | font-family: HabitatFont;
23 | font-weight: normal;
24 | font-style: normal;
25 | text-decoration: inherit;
26 | display: inline;
27 | width: auto;
28 | height: auto;
29 | line-height: normal;
30 | vertical-align: baseline;
31 | background-image: none !important;
32 | background-position: 0% 0%;
33 | background-repeat: repeat;
34 | }
35 | [class^="icon-"]:before,
36 | [class*=" icon-"]:before {
37 | text-decoration: inherit;
38 | display: inline-block;
39 | speak: none;
40 | }
41 | .icon-spin {
42 | display: inline-block;
43 | -moz-animation: spin 2s infinite linear;
44 | -o-animation: spin 2s infinite linear;
45 | -webkit-animation: spin 2s infinite linear;
46 | animation: spin 2s infinite linear;
47 | }
48 | @-moz-keyframes spin {
49 | 0% { -moz-transform: rotate(0deg); }
50 | 100% { -moz-transform: rotate(359deg); }
51 | }
52 | @-webkit-keyframes spin {
53 | 0% { -webkit-transform: rotate(0deg); }
54 | 100% { -webkit-transform: rotate(359deg); }
55 | }
56 | @-o-keyframes spin {
57 | 0% { -o-transform: rotate(0deg); }
58 | 100% { -o-transform: rotate(359deg); }
59 | }
60 | @-ms-keyframes spin {
61 | 0% { -ms-transform: rotate(0deg); }
62 | 100% { -ms-transform: rotate(359deg); }
63 | }
64 | @keyframes spin {
65 | 0% { transform: rotate(0deg); }
66 | 100% { transform: rotate(359deg); }
67 | }
68 |
69 | .icon-habhub:before { content: "\f000"; }
70 | .icon-compass:before { content: "\f001"; }
71 | .icon-locate-me:before { content: "\f002"; }
72 | .icon-car:before { content: "\f003"; }
73 | .icon-question:before { content: "\f004"; }
74 | .icon-location:before { content: "\f005"; }
75 | .icon-target:before { content: "\f006"; }
76 | .icon-earth:before { content: "\f007"; }
77 | .icon-daylight:before { content: "\f008"; }
78 | .icon-settings:before { content: "\f010"; }
--------------------------------------------------------------------------------
/glyphs/icon-code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
73 |
--------------------------------------------------------------------------------
/img/markers/car.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/Leaflet.fullscreen.min.js:
--------------------------------------------------------------------------------
1 | L.Control.Fullscreen=L.Control.extend({options:{position:"topleft",title:{"false":"View Fullscreen","true":"Exit Fullscreen"}},onAdd:function(map){var container=L.DomUtil.create("div","leaflet-control-fullscreen leaflet-bar leaflet-control");this.link=L.DomUtil.create("a","leaflet-control-fullscreen-button leaflet-bar-part",container);this.link.href="#";this._map=map;this._map.on("fullscreenchange",this._toggleTitle,this);this._toggleTitle();L.DomEvent.on(this.link,"click",this._click,this);return container},_click:function(e){L.DomEvent.stopPropagation(e);L.DomEvent.preventDefault(e);this._map.toggleFullscreen(this.options)},_toggleTitle:function(){this.link.title=this.options.title[this._map.isFullscreen()]}});L.Map.include({isFullscreen:function(){return this._isFullscreen||false},toggleFullscreen:function(options){var container=this.getContainer();if(this.isFullscreen()){if(options&&options.pseudoFullscreen){this._disablePseudoFullscreen(container)}else if(document.exitFullscreen){document.exitFullscreen()}else if(document.mozCancelFullScreen){document.mozCancelFullScreen()}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen()}else if(document.msExitFullscreen){document.msExitFullscreen()}else{this._disablePseudoFullscreen(container)}}else{if(options&&options.pseudoFullscreen){this._enablePseudoFullscreen(container)}else if(container.requestFullscreen){container.requestFullscreen()}else if(container.mozRequestFullScreen){container.mozRequestFullScreen()}else if(container.webkitRequestFullscreen){container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}else if(container.msRequestFullscreen){container.msRequestFullscreen()}else{this._enablePseudoFullscreen(container)}}},_enablePseudoFullscreen:function(container){L.DomUtil.addClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(true);this.invalidateSize();this.fire("fullscreenchange")},_disablePseudoFullscreen:function(container){L.DomUtil.removeClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(false);this.invalidateSize();this.fire("fullscreenchange")},_setFullscreen:function(fullscreen){this._isFullscreen=fullscreen;var container=this.getContainer();if(fullscreen){L.DomUtil.addClass(container,"leaflet-fullscreen-on")}else{L.DomUtil.removeClass(container,"leaflet-fullscreen-on")}},_onFullscreenChange:function(e){var fullscreenElement=document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement;if(fullscreenElement===this.getContainer()&&!this._isFullscreen){this._setFullscreen(true);this.fire("fullscreenchange")}else if(fullscreenElement!==this.getContainer()&&this._isFullscreen){this._setFullscreen(false);this.fire("fullscreenchange")}}});L.Map.mergeOptions({fullscreenControl:false});L.Map.addInitHook(function(){if(this.options.fullscreenControl){this.fullscreenControl=new L.Control.Fullscreen(this.options.fullscreenControl);this.addControl(this.fullscreenControl)}var fullscreenchange;if("onfullscreenchange"in document){fullscreenchange="fullscreenchange"}else if("onmozfullscreenchange"in document){fullscreenchange="mozfullscreenchange"}else if("onwebkitfullscreenchange"in document){fullscreenchange="webkitfullscreenchange"}else if("onmsfullscreenchange"in document){fullscreenchange="MSFullscreenChange"}if(fullscreenchange){var onFullscreenChange=L.bind(this._onFullscreenChange,this);this.whenReady(function(){L.DomEvent.on(document,fullscreenchange,onFullscreenChange)});this.on("unload",function(){L.DomEvent.off(document,fullscreenchange,onFullscreenChange)})}});L.control.fullscreen=function(options){return new L.Control.Fullscreen(options)};
--------------------------------------------------------------------------------
/img/markers/parachute.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/layout.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V1.2
3 | * Copyright 2011, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 6/20/2012
8 |
9 | * Edited by Daniel Saul
10 | * For use in the Habitat Webpage Template
11 |
12 | */
13 |
14 | /* Table of Content
15 | ==================================================
16 | #Site Styles
17 | #Page Styles
18 | #Media Queries
19 | #Font-Face */
20 |
21 | /* #Site Styles
22 | ================================================== */
23 |
24 | /* Header */
25 | header{
26 | width: 100%;
27 | min-height: 75px;
28 |
29 | text-align: left;
30 | line-height: 75px;
31 | color: #ffffff;
32 | font-size: 14px;
33 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
34 |
35 | background: #00a3d3;
36 | border-bottom: 1px solid #009bc9;
37 |
38 | }
39 |
40 | header h1{
41 | height: 75px;
42 | float: left;
43 | }
44 |
45 | /* Grey Area */
46 | #grey-section{
47 | width: 100%;
48 | margin-bottom: 50px;
49 | padding: 15px 0 15px 0;
50 |
51 | color: #666666;
52 |
53 | background: #fcfcfc;
54 | border-top: 1px solid #ffffff;
55 | border-bottom: 1px solid #eeeeee;
56 | }
57 |
58 | #grey-section h3{
59 | color: #00a3d3;
60 | }
61 |
62 | #grey-section .badge{
63 | margin-top: -30px;
64 | margin-bottom: -100%;
65 | float: right;
66 | visibility: hidden;
67 | }
68 |
69 | /* Forms */
70 | .form.row label{
71 | padding-top: 3px;
72 | font-weight: normal;
73 | }
74 |
75 | .form.row{
76 | margin-bottom: 44px;
77 | }
78 |
79 | .form.row input, .form.row select {
80 | margin-bottom: 0 !important;
81 | }
82 |
83 | .validated input{
84 | float: left;
85 | }
86 |
87 | .validated img{
88 | padding: 3px 0 0 10px;
89 | float: left;
90 | }
91 |
92 | .form.row .input_extra{
93 | background: #f8f8f8;
94 | color: #999999;
95 | padding: 5px;
96 | -moz-border-radius: 4px;
97 | -webkit-border-radius: 4px;
98 | border-radius: 4px;
99 | position: relative;
100 | min-height: 22px;
101 | width: auto;
102 | }
103 |
104 | @media only screen and (min-width: 768px){
105 | .input_extra:before {
106 | content: "\0020";
107 | width: 0;
108 | height: 0;
109 | border-top: 15px solid transparent;
110 | border-bottom: 15px solid transparent;
111 | border-right: 15px solid #f8f8f8;
112 | position: absolute;
113 | left: -14px;
114 | top: 1px;
115 | display: inline;
116 | }
117 | .form.row input.long {
118 | width: 380px;
119 | }
120 | }
121 |
122 | .long_protection {
123 | white-space: nowrap;
124 | overflow: hidden;
125 | display: block;
126 | }
127 |
128 | /* #Page Styles
129 | ================================================== */
130 |
131 | /* #Media Queries
132 | ================================================== */
133 |
134 | /* Smaller than standard 960 (devices and browsers) */
135 | @media only screen and (max-width: 959px) {}
136 |
137 | /* Tablet Portrait size to standard 960 (devices and browsers) */
138 | @media only screen and (min-width: 768px) and (max-width: 959px) {}
139 |
140 | /* All Mobile Sizes (devices and browser) */
141 | @media only screen and (max-width: 767px) {}
142 |
143 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
144 | @media only screen and (min-width: 480px) and (max-width: 767px) {}
145 |
146 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
147 | @media only screen and (max-width: 479px) {}
148 |
149 |
150 | /* #Font-Face
151 | ================================================== */
152 | /* This is the proper syntax for an @font-face file
153 | Just create a "fonts" folder at the root,
154 | copy your FontName into code below and remove
155 | comment brackets */
156 |
157 | /* @font-face {
158 | font-family: 'FontName';
159 | src: url('../fonts/FontName.eot');
160 | src: url('../fonts/FontName.eot?iefix') format('eot'),
161 | url('../fonts/FontName.woff') format('woff'),
162 | url('../fonts/FontName.ttf') format('truetype'),
163 | url('../fonts/FontName.svg#webfontZam02nTh') format('svg');
164 | font-weight: normal;
165 | font-style: normal; }
166 | */
167 |
--------------------------------------------------------------------------------
/js/flight_doc.js:
--------------------------------------------------------------------------------
1 |
2 | // populate login url
3 | document.getElementById("login_url").href= "https://auth.v2.sondehub.org/oauth2/authorize?client_id=21dpr4kth8lonk2rq803loh5oa&response_type=token&scope=email+openid+phone&redirect_uri=" + window.location.protocol + "//" + window.location.host
4 |
5 | // manage AWS cognito auth
6 | if (window.location.hash.indexOf("id_token") != -1){
7 | console.log("Detected login")
8 | var args = window.location.hash.slice(1)
9 | var parms = new URLSearchParams(args)
10 | var id_token = parms.get("id_token")
11 | sessionStorage.setItem("id_token", id_token)
12 | }
13 |
14 | // do AWS login
15 | AWS.config.region = 'us-east-1';
16 |
17 | AWS.config.credentials = new AWS.CognitoIdentityCredentials({
18 | IdentityPoolId: 'us-east-1:55e43eac-9626-43e1-a7d2-bbc57f5f5aa9',
19 | Logins: {
20 | "cognito-idp.us-east-1.amazonaws.com/us-east-1_G4H7NMniM": sessionStorage.getItem("id_token")
21 | }
22 | });
23 |
24 | AWS.config.credentials.get(function(){
25 | // if this passes we update the login page to say logged in
26 | if (AWS.config.credentials.accessKeyId != undefined){
27 | document.getElementById("login_url").innerText = "Logout"
28 | document.getElementById("login_url").href="javascript:logout()"
29 | document.getElementById("update-flightdocs").style.display = "block"
30 | document.getElementById("prediction_settings_message").innerText = "Use this form to configure predictions for your launch. Please only use this for your own launches. Callsigns must match your payload callsigns exactly (case sensitive)."
31 | }
32 | });
33 | function query_flight_doc(){
34 | var payload_callsign = document.getElementById("flight_doc_payload_callsign").value
35 | fetch("https://api.v2.sondehub.org/amateur/flightdoc/"+payload_callsign).then(
36 | function(response){
37 | if (response.ok) {
38 | response.text().then(function(x) {
39 | var data = JSON.parse(x)
40 | if (data.float_expected) {
41 | document.getElementById("flight_doc_float_expected").checked = true
42 | } else {
43 | document.getElementById("flight_doc_float_expected").checked = false
44 | }
45 | document.getElementById("flight_doc_peak_altitude").value = data.peak_altitude
46 | document.getElementById("flight_doc_descent_rate").value = data.descent_rate
47 | document.getElementById("flight_doc_ascent_rate").value = data.ascent_rate
48 | })
49 | } else {
50 | document.getElementById("payload-update-results").textContent = "Could not load payload data"
51 | }
52 | }
53 | )
54 | }
55 | function logout(){
56 | logout_url = "https://auth.v2.sondehub.org/logout?client_id=21dpr4kth8lonk2rq803loh5oa&response_type=token&logout_uri=" + window.location.protocol + "//" + window.location.host
57 | sessionStorage.removeItem("id_token")
58 | window.location = logout_url
59 | }
60 |
61 | function update_flight_doc(){
62 |
63 | var body = JSON.stringify(
64 | {
65 | "payload_callsign": document.getElementById("flight_doc_payload_callsign").value,
66 | "float_expected": document.getElementById("flight_doc_float_expected").checked == true,
67 | "peak_altitude": parseFloat(document.getElementById("flight_doc_peak_altitude").value),
68 | "descent_rate": parseFloat(document.getElementById("flight_doc_descent_rate").value),
69 | "ascent_rate": parseFloat(document.getElementById("flight_doc_ascent_rate").value),
70 | }
71 | )
72 |
73 |
74 | var httpRequest = new AWS.HttpRequest("https://api-raw.v2.sondehub.org/amateur/flightdoc" , "us-east-1");
75 | var v4signer = new AWS.Signers.V4(httpRequest, "execute-api", true);
76 | httpRequest.method = "PUT";
77 | httpRequest.headers['Host'] = 'api-raw.v2.sondehub.org';
78 | httpRequest.headers['Content-Type'] = 'application/json';
79 | httpRequest.headers['Content-Length'] = body.length;
80 | httpRequest.headers['X-Amz-Content-Sha256'] = v4signer.hexEncodedHash(body)
81 | httpRequest.body = body
82 |
83 |
84 |
85 | v4signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
86 | document.getElementById("payload-update-results").textContent = "Updating..."
87 | fetch(httpRequest.endpoint.href , {
88 | method: httpRequest.method,
89 | headers: httpRequest.headers,
90 | body: httpRequest.body,
91 | }).then(function (response) {
92 | if (!response.ok) {
93 | response.text().then(function(x) {document.getElementById("payload-update-results").textContent =x })
94 | return;
95 | }
96 | response.text().then(function(x) {document.getElementById("payload-update-results").textContent =x })
97 | });
98 |
99 | }
--------------------------------------------------------------------------------
/js/L.Terminator.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('leaflet')) :
3 | typeof define === 'function' && define.amd ? define(['leaflet'], factory) :
4 | (global.L = global.L || {}, global.L.terminator = factory(global.L));
5 | }(this, (function (L) { 'use strict';
6 |
7 | L = L && L.hasOwnProperty('default') ? L['default'] : L;
8 |
9 | /* Terminator.js -- Overlay day/night region on a Leaflet map */
10 |
11 | function julian(date) {
12 | /* Calculate the present UTC Julian Date. Function is valid after
13 | * the beginning of the UNIX epoch 1970-01-01 and ignores leap
14 | * seconds. */
15 | return (date / 86400000) + 2440587.5;
16 | }
17 |
18 | function GMST(julianDay) {
19 | /* Calculate Greenwich Mean Sidereal Time according to
20 | http://aa.usno.navy.mil/faq/docs/GAST.php */
21 | var d = julianDay - 2451545.0;
22 | // Low precision equation is good enough for our purposes.
23 | return (18.697374558 + 24.06570982441908 * d) % 24;
24 | }
25 |
26 | var Terminator = L.Polygon.extend({
27 | options: {
28 | color: '#00',
29 | opacity: 0.5,
30 | fillColor: '#00',
31 | fillOpacity: 0.5,
32 | resolution: 2
33 | },
34 |
35 | initialize: function (options) {
36 | this.version = '0.1.0';
37 | this._R2D = 180 / Math.PI;
38 | this._D2R = Math.PI / 180;
39 | L.Util.setOptions(this, options);
40 | var latLng = this._compute(this.options.time);
41 | this.setLatLngs(latLng);
42 | },
43 |
44 | setTime: function (date) {
45 | this.options.time = date;
46 | var latLng = this._compute(date);
47 | this.setLatLngs(latLng);
48 | },
49 |
50 | _sunEclipticPosition: function (julianDay) {
51 | /* Compute the position of the Sun in ecliptic coordinates at
52 | julianDay. Following
53 | http://en.wikipedia.org/wiki/Position_of_the_Sun */
54 | // Days since start of J2000.0
55 | var n = julianDay - 2451545.0;
56 | // mean longitude of the Sun
57 | var L$$1 = 280.460 + 0.9856474 * n;
58 | L$$1 %= 360;
59 | // mean anomaly of the Sun
60 | var g = 357.528 + 0.9856003 * n;
61 | g %= 360;
62 | // ecliptic longitude of Sun
63 | var lambda = L$$1 + 1.915 * Math.sin(g * this._D2R) +
64 | 0.02 * Math.sin(2 * g * this._D2R);
65 | // distance from Sun in AU
66 | var R = 1.00014 - 0.01671 * Math.cos(g * this._D2R) -
67 | 0.0014 * Math.cos(2 * g * this._D2R);
68 | return {lambda: lambda, R: R};
69 | },
70 |
71 | _eclipticObliquity: function (julianDay) {
72 | // Following the short term expression in
73 | // http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29
74 | var n = julianDay - 2451545.0;
75 | // Julian centuries since J2000.0
76 | var T = n / 36525;
77 | var epsilon = 23.43929111 -
78 | T * (46.836769 / 3600
79 | - T * (0.0001831 / 3600
80 | + T * (0.00200340 / 3600
81 | - T * (0.576e-6 / 3600
82 | - T * 4.34e-8 / 3600))));
83 | return epsilon;
84 | },
85 |
86 | _sunEquatorialPosition: function (sunEclLng, eclObliq) {
87 | /* Compute the Sun's equatorial position from its ecliptic
88 | * position. Inputs are expected in degrees. Outputs are in
89 | * degrees as well. */
90 | var alpha = Math.atan(Math.cos(eclObliq * this._D2R)
91 | * Math.tan(sunEclLng * this._D2R)) * this._R2D;
92 | var delta = Math.asin(Math.sin(eclObliq * this._D2R)
93 | * Math.sin(sunEclLng * this._D2R)) * this._R2D;
94 |
95 | var lQuadrant = Math.floor(sunEclLng / 90) * 90;
96 | var raQuadrant = Math.floor(alpha / 90) * 90;
97 | alpha = alpha + (lQuadrant - raQuadrant);
98 |
99 | return {alpha: alpha, delta: delta};
100 | },
101 |
102 | _hourAngle: function (lng, sunPos, gst) {
103 | /* Compute the hour angle of the sun for a longitude on
104 | * Earth. Return the hour angle in degrees. */
105 | var lst = gst + lng / 15;
106 | return lst * 15 - sunPos.alpha;
107 | },
108 |
109 | _latitude: function (ha, sunPos) {
110 | /* For a given hour angle and sun position, compute the
111 | * latitude of the terminator in degrees. */
112 | var lat = Math.atan(-Math.cos(ha * this._D2R) /
113 | Math.tan(sunPos.delta * this._D2R)) * this._R2D;
114 | return lat;
115 | },
116 |
117 | _compute: function (time) {
118 | var today = time ? new Date(time) : new Date();
119 | var julianDay = julian(today);
120 | var gst = GMST(julianDay);
121 | var latLng = [];
122 |
123 | var sunEclPos = this._sunEclipticPosition(julianDay);
124 | var eclObliq = this._eclipticObliquity(julianDay);
125 | var sunEqPos = this._sunEquatorialPosition(sunEclPos.lambda, eclObliq);
126 | for (var i = 0; i <= 720 * this.options.resolution; i++) {
127 | var lng = -360 + i / this.options.resolution;
128 | var ha = this._hourAngle(lng, sunEqPos, gst);
129 | latLng[i + 1] = [this._latitude(ha, sunEqPos), lng];
130 | }
131 | if (sunEqPos.delta < 0) {
132 | latLng[0] = [90, -360];
133 | latLng[latLng.length] = [90, 360];
134 | } else {
135 | latLng[0] = [-90, -360];
136 | latLng[latLng.length] = [-90, 360];
137 | }
138 | return latLng;
139 | }
140 | });
141 |
142 | function terminator(options) {
143 | return new Terminator(options);
144 | }
145 |
146 | return terminator;
147 |
148 | })));
149 |
--------------------------------------------------------------------------------
/glyphs/icon-clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
145 |
--------------------------------------------------------------------------------
/glyphs/icon-weather.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
75 |
--------------------------------------------------------------------------------
/js/plot_config.js:
--------------------------------------------------------------------------------
1 | // init plot
2 | plot = $.plot(plot_holder, {}, plot_options);
3 | var updateLegendTimeout = null;
4 | var polyMarker = null;
5 |
6 | // updates legend with extrapolated values under the mouse position
7 | function updateLegend(pos) {
8 | var legend = $(plot_holder + " .legendLabel");
9 | $(plot_holder + " .legend table").css({'background-color':"rgba(255,255,255,0.9)","pointer-events":"none"});
10 | legend.each(function() {
11 | $(this).css({'padding-left':'3px'});
12 | });
13 |
14 |
15 | var i, j, ij, pij, dataset = plot.getData();
16 | var outside = false;
17 | //var axes = plot.getAxes();
18 |
19 | if(dataset.length === 0) return;
20 |
21 | // this loop find the value for each series
22 | // and updates the legend
23 | //
24 | // here we don't snap to existing data point
25 | for (i = 0; i < dataset.length; ++i) {
26 | var series = dataset[i];
27 | var y;
28 | y = null;
29 |
30 | // Find the nearest points, x-wise
31 |
32 | if(series.data.length < 2 ||
33 | (i===1 && series.data[0][0] > pos.x)) {
34 | y = null;
35 | }
36 | else if (i !== 1 && pos.x > series.data[series.data.length-1][0]) {
37 | outside = true;
38 | }
39 | else {
40 | for (j = 0; j < series.data.length; ++j) {
41 | if (series.data[j][0] > pos.x) {
42 | break;
43 | }
44 | }
45 |
46 | if(i === 0) ij = j;
47 | if(i === 1) {
48 | pij = (j >= series.data.length) ? j-1 : j;
49 | }
50 |
51 | if(series.noInterpolate === true) { y = series.data[((j===0)?j:j-1)][1]; }
52 | else {
53 | var p1 = (j===0) ? null : series.data[j-1];
54 | p2 = series.data[j];
55 |
56 | if (p1 === null) {
57 | y = p2[1];
58 | } else if (p2 === null || p2 === undefined) {
59 | y = p1[1];
60 | } else {
61 | y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
62 | }
63 |
64 | y = ((p1 && p1[1] === null) || (p2 && p2[1] === null)) ? null : y.toFixed(2);
65 | }
66 | }
67 | legend.eq(i).text(series.label.replace(/=.*/, "= " + y));
68 | }
69 |
70 | if(follow_vehicle !== null && vehicles[follow_vehicle].positions.length) {
71 | // adjust index for null data points
72 | var null_count = 0;
73 |
74 | if (!map.hasLayer(polyMarker) && polyMarker) {
75 | map.addLayer(polyMarker);
76 | }
77 |
78 | if(outside && pij !== undefined) {
79 | if(!polyMarker) {
80 | try {polyMarker = new L.Marker(vehicles[follow_vehicle].prediction_polyline[0].getLatLngs()[pij]).addTo(map);} catch (e) {};
81 | try {polyMarker = new L.Marker(vehicles[follow_vehicle].prediction_polyline[1].getLatLngs()[pij]).addTo(map);} catch (e) {};
82 | } else {
83 | try {polyMarker.setLatLng(vehicles[follow_vehicle].prediction_polyline[0].getLatLngs()[pij]);} catch (e) {};
84 | try {polyMarker.setLatLng(vehicles[follow_vehicle].prediction_polyline[1].getLatLngs()[pij]);} catch (e) {};
85 | }
86 |
87 | }
88 | else {
89 | var data_ref = vehicles[follow_vehicle].graph_data[0];
90 |
91 | if(ij > data_ref.data.length / 2) {
92 | for(i = data_ref.data.length - 1; i > ij; i--) null_count += (data_ref.data[i][1] === null) ? 1 : 0;
93 | null_count = data_ref.nulls - null_count * 2;
94 | } else {
95 | for(i = 0; i < ij; i++) null_count += (data_ref.data[i][1] === null) ? 1 : 0;
96 | null_count *= 2;
97 | }
98 |
99 | // update position
100 | ij -= null_count + ((null_count===0||null_count===data_ref.nulls) ? 0 : 1);
101 | if(ij < 0) ij = 0;
102 |
103 | if(!polyMarker) {
104 | try {polyMarker = new L.Marker(vehicles[follow_vehicle].positions[ij]).addTo(map);} catch (e) {};
105 | } else {
106 | try {polyMarker.setLatLng(vehicles[follow_vehicle].positions[ij]);} catch (e) {};
107 | }
108 | }
109 |
110 | // set timebox
111 | var date = new Date(pos.x1);
112 | $('#timebox').removeClass('present').addClass('past');
113 | updateTimebox(date);
114 | }
115 | }
116 |
117 | var plot_crosshair_locked = false;
118 |
119 | $(plot_holder).bind("click", function (event) {
120 | if(plot_crosshair_locked) {
121 | plot_crosshair_locked = false;
122 | } else if(event.ctrlKey) {
123 | plot_crosshair_locked = true;
124 | }
125 | });
126 | // update legend values on mouse hover
127 | $(plot_holder).bind("plothover", function (event, pos, item) {
128 | if(plot_crosshair_locked) return;
129 |
130 | if (!updateLegendTimeout) {
131 | plot.lockCrosshair();
132 | plot.setCrosshair(pos);
133 | updateLegend(pos);
134 | updateLegendTimeout = setTimeout(function() { updateLegendTimeout = null; }, 40);
135 | }
136 | });
137 |
138 | // double click on the plot clears selection
139 | $(plot_holder).bind("dblclick", function () {
140 | if(!follow_vehicle) return;
141 |
142 | if(plot_options.xaxis) {
143 | if(plot_options.xaxis.superzoom == 2) {
144 | delete plot_options.xaxis;
145 | }
146 | else {
147 | if(plot_options.xaxis.superzoom == 1) {
148 | if(!confirm("You are about to zoom out to the entire graph. It may hang your browser. Do you wish to continue?")) return;
149 | }
150 | plot_options.xaxis = {};
151 | }
152 | }
153 |
154 | updateGraph(follow_vehicle, false);
155 | });
156 |
157 | // limit range after selection
158 | $(plot_holder).bind("plotselected", function (event, ranges) {
159 | if(typeof ranges.xaxis == 'undefined') return;
160 |
161 | if(plot_options.xaxis && plot_options.xaxis.superzoom) plot_options.xaxis.superzoom = 2;
162 |
163 | $.extend(true, plot_options, {
164 | xaxis: {
165 | min: ranges.xaxis.from,
166 | max: ranges.xaxis.to
167 | }
168 | });
169 |
170 | updateGraph(follow_vehicle, false);
171 | });
172 |
--------------------------------------------------------------------------------
/glyphs/icon-compass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
193 |
--------------------------------------------------------------------------------
/embed-preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Preview of embedded Sondehub-Amateur tracker
5 |
6 |
7 |
63 |
64 |
215 |
216 |
217 |
253 |
254 |
255 |
--------------------------------------------------------------------------------
/js/format.js:
--------------------------------------------------------------------------------
1 | /* SondeHub Amateur Tracker Format Incoming Data
2 | *
3 | * Author: Luke Prior
4 | */
5 |
6 | var excludedFields = [
7 | "payload_callsign",
8 | "uploader_callsign",
9 | "software_version",
10 | "position",
11 | "user-agent",
12 | "uploaders",
13 | "snr",
14 | "rssi",
15 | "software_name",
16 | "alt",
17 | "lat",
18 | "lon",
19 | "heading",
20 | "datetime",
21 | "payload_callsign",
22 | "path",
23 | "time_received",
24 | "frame",
25 | "uploader_alt",
26 | "uploader_position",
27 | "uploader_radio",
28 | "uploader_antenna",
29 | "raw",
30 | "aprs_tocall",
31 | "telemetry_hidden",
32 | "upload_time",
33 | "raw_payload"
34 | ];
35 |
36 | var uniqueKeys = {
37 | "batt": {"precision": 2},
38 | "frequency": {"precision": 4},
39 | "tx_frequency": {"precision": 4}
40 | }
41 |
42 | function formatData(data) {
43 | var showAprs = offline.get('opt_show_aprs');
44 | var showTesting = offline.get("opt_show_testing");
45 | var response = {};
46 | response.positions = {};
47 | var dataTemp = [];
48 | for (let key in data) {
49 | if (data.hasOwnProperty(key)) {
50 | if (typeof data[key] === 'object') {
51 | for (let i in data[key]) {
52 | var dataTempEntry = {};
53 | var aprsflag = false;
54 | dataTempEntry.callsign = {};
55 | maximumAltitude = 0;
56 | if (vehicles.hasOwnProperty(data[key][i].payload_callsign)) {
57 | maximumAltitude = vehicles[data[key][i].payload_callsign].max_alt;
58 | if (data[key][i].datetime == vehicles[data[key][i].payload_callsign].curr_position.gps_time) {
59 | dataTempEntry = vehicles[data[key][i].payload_callsign].curr_position;
60 | }
61 | }
62 | if (!data[key][i].hasOwnProperty("uploaders")) {
63 | data[key][i].uploaders = [];
64 | data[key][i].uploaders[0] = {}
65 | data[key][i].uploaders[0].uploader_callsign = data[key][i].uploader_callsign;
66 | if (data[key][i].snr && typeof data[key][i].snr === "number") {
67 | data[key][i].uploaders[0].snr = + data[key][i].snr.toFixed(1);
68 | }
69 | if (data[key][i].rssi && typeof data[key][i].rssi === "number") {
70 | data[key][i].uploaders[0].rssi = + data[key][i].rssi.toFixed(1);
71 | }
72 | if (data[key][i].frequency && typeof data[key][i].frequency === "number") {
73 | data[key][i].uploaders[0].frequency = + data[key][i].frequency.toFixed(4);
74 | }
75 | }
76 | for (let entry in data[key][i].uploaders) {
77 | // This check should probably be done using a modulation field, but this still works I guess..
78 | if ("software_name" in data[key][i] && data[key][i].software_name.includes("APRS")) {
79 | aprsflag = true;
80 | var stations = data[key][i].uploaders[entry].uploader_callsign.split(",");
81 | for (let uploader in stations) {
82 | dataTempEntry.callsign[stations[uploader]] = {};
83 | }
84 | } else {
85 | uploader_callsign = data[key][i].uploaders[entry].uploader_callsign
86 | dataTempEntry.callsign[uploader_callsign] = {};
87 |
88 | if (data[key][i].uploaders[entry].snr && typeof data[key][i].uploaders[entry].snr === "number") {
89 | dataTempEntry.callsign[uploader_callsign].snr = + data[key][i].uploaders[entry].snr.toFixed(1);
90 | }
91 | if (data[key][i].uploaders[entry].rssi && typeof data[key][i].uploaders[entry].rssi === "number") {
92 | dataTempEntry.callsign[uploader_callsign].rssi = + data[key][i].uploaders[entry].rssi.toFixed(1);
93 | }
94 | if (data[key][i].uploaders[entry].frequency && typeof data[key][i].uploaders[entry].frequency === "number") {
95 | dataTempEntry.callsign[uploader_callsign].frequency = + data[key][i].uploaders[entry].frequency.toFixed(4);
96 | }
97 |
98 | }
99 | }
100 | dataTempEntry.gps_alt = parseFloat((data[key][i].alt).toPrecision(8));
101 | if (dataTempEntry.gps_alt > maximumAltitude) {
102 | maximumAltitude = dataTempEntry.gps_alt;
103 | }
104 | // APRS Altitude filter.
105 | if (maximumAltitude < 1500 && aprsflag && !showAprs) {
106 | continue;
107 | }
108 | // Testing payload filter.
109 | if (data[key][i].telemetry_hidden && !showTesting){
110 | continue;
111 | }
112 | // No GPS lock filter. Filter out positions with sats = 0, if sats is provided.
113 | // The historical data API will do this already, but we need this to filter out data
114 | // coming in via websockets.
115 | if (data[key][i].hasOwnProperty("sats")){
116 | if (data[key][i].sats == 0){
117 | continue;
118 | }
119 | }
120 | //
121 | dataTempEntry.gps_lat = parseFloat((data[key][i].lat).toPrecision(8));
122 | dataTempEntry.gps_lon = parseFloat((data[key][i].lon).toPrecision(8));
123 | if (dataTempEntry.gps_lat == 0 && dataTempEntry.gps_lon == 0) {
124 | continue;
125 | }
126 | if (data[key][i].heading) {
127 | dataTempEntry.gps_heading = data[key][i].heading;
128 | }
129 | dataTempEntry.gps_time = data[key][i].datetime;
130 | dataTempEntry.server_time = data[key][i].datetime;
131 | dataTempEntry.vehicle = data[key][i].payload_callsign;
132 | dataTempEntry.position_id = data[key][i].payload_callsign + "-" + data[key][i].datetime;
133 | if (!dataTempEntry.hasOwnProperty("data")) {
134 | dataTempEntry.data = {};
135 | }
136 |
137 | // Automatically add all remaining fields as data excluding excluded fields
138 |
139 | for (let field in data[key][i]) {
140 | if (excludedFields.includes(field)) {
141 | continue;
142 | }
143 | if (uniqueKeys.hasOwnProperty(field)) {
144 | dataTempEntry.data[field] = parseFloat(data[key][i][field]).toFixed(uniqueKeys[field].precision);
145 | } else {
146 | dataTempEntry.data[field] = data[key][i][field];
147 | }
148 | }
149 |
150 | // Payload data post-processing, where we can modify / add data elements if needed.
151 |
152 | // Determine if this payload is a WSPR payload
153 | // We determine this through either the modulation field, or comment field.
154 | var wspr_payload = false;
155 | if (data[key][i].hasOwnProperty("modulation")){
156 | if(data[key][i].modulation.includes("WSPR")){
157 | wspr_payload = true;
158 | }
159 | }
160 | if (data[key][i].hasOwnProperty("comment")){
161 | if(data[key][i].comment.includes("WSPR")){
162 | wspr_payload = true;
163 | }
164 | }
165 |
166 | // For WSPR payloads, calculate solar elevation.
167 | if(wspr_payload){
168 | dataTempEntry.data['solar_elevation'] = (SunCalc.getPosition(stringToDateUTC(dataTempEntry.gps_time), dataTempEntry.gps_lat, dataTempEntry.gps_lon, dataTempEntry.gps_alt).altitude/rad).toFixed(1);
169 | }
170 |
171 |
172 | dataTemp.push(dataTempEntry);
173 | }
174 | }
175 | }
176 | }
177 | response.positions.position = dataTemp;
178 | response.fetch_timestamp = Date.now();
179 | return response;
180 | }
--------------------------------------------------------------------------------
/js/L.TileLayer.NoGap.js:
--------------------------------------------------------------------------------
1 | // @class TileLayer
2 |
3 | L.TileLayer.mergeOptions({
4 | // @option keepBuffer
5 | // The amount of tiles outside the visible map area to be kept in the stitched
6 | // `TileLayer`.
7 |
8 | // @option dumpToCanvas: Boolean = true
9 | // Whether to dump loaded tiles to a `