diff --git a/.htaccess b/.htaccess index 6fcc26d..3d763d3 100644 --- a/.htaccess +++ b/.htaccess @@ -1,5 +1,32 @@ - SetOutputFilter DEFLATE + # Compress HTML, CSS, JavaScript, Text, XML and fonts + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/vnd.ms-fontobject + AddOutputFilterByType DEFLATE application/x-font + AddOutputFilterByType DEFLATE application/x-font-opentype + AddOutputFilterByType DEFLATE application/x-font-otf + AddOutputFilterByType DEFLATE application/x-font-truetype + AddOutputFilterByType DEFLATE application/x-font-ttf + AddOutputFilterByType DEFLATE application/x-javascript + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE font/opentype + AddOutputFilterByType DEFLATE font/otf + AddOutputFilterByType DEFLATE font/ttf + AddOutputFilterByType DEFLATE image/svg+xml + AddOutputFilterByType DEFLATE image/x-icon + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE text/html + AddOutputFilterByType DEFLATE text/javascript + AddOutputFilterByType DEFLATE text/plain + AddOutputFilterByType DEFLATE text/xml + + # Remove browser bugs (only needed for ancient browsers) + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + Header append Vary User-Agent AddType text/cache-manifest .manifest diff --git a/README.md b/README.md index 105ff80..55ed4f2 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,30 @@ -# SondeHub Tracker +# SondeHub Tracker Beta (leaflet edition) A fork of [habitat-mobile-tracker](https://github.com/rossengeorgiev/habitat-mobile-tracker) for use with the [SondeHub v2 ElasticSearch](https://github.com/projecthorus/sondehub-infra/wiki/ElasticSearch-Kibana-access) database. Huge credits to [Rossen Georgiev](https://github.com/rossengeorgiev/) for developing the mobile-tracker. ![mobile tracker screenshot](resources/mobiletracker-screencap.png "mobile tracker screenshot") A webapp for tracking radiosondes. Works an desktop and mobile devices. -The habhub tracker is a continuation of [spacenear.us/tracker](http://spacenear.us/tracker). +The SondeHub tracker is a continuation of [spacenear.us/tracker](http://spacenear.us/tracker). ## Features * Radiosonde Tracking using [SondeHub V2](https://github.com/projecthorus/sondehub-infra/wiki/ElasticSearch-Kibana-access) data. * Telemetry graph for each balloon -* Chase Car functionality (TODO for SondeHub) +* Chase Car functionality * Near realtime weather overlays * [Daylight cycle overlay](https://github.com/rossengeorgiev/nite-overlay), for long flights -* Available to embed on any website -* Map tracker with Google Maps API 3 +* Map tracker with Leaflet API * Run the app natively on `iOS` or with Chrome's 'add to screen' on `Android` ### Geo position -Available only on mobile devices. - The app will ask for permission to use your location. This is required for some of the features. It is **important** to note that your location will not be made available or send to anyone. Unless, you enable - the `chase car mode`, which will periodically upload it to habitat. _The app + the `chase car mode`, which will periodically upload it to SondeHub. _The app will always start with `chase car mode` disabled._ -### Offline storage - -The app will ask to use offline storage. You will need to accept, in order to -use the offline capabilities. The app will cache all files making it available -even when there is no network coverage. Latest position data will also be stored -and used when you start up with no network. When you get back online, the app -will fetch the latest position data. - ## Browser requirements Any modern browser should be able to run the app. Including the latest version of IE. @@ -46,7 +35,6 @@ This is also true for mobile, some build-in browsers may not work. Don't hesitate to report any issues, or suggest improvements. Just visit the [issues page](https://github.com/projecthorus/sondehub-tracker/issues). Pull requests are welcome. - ## Installation Requirements: __Java__ and (Linux or Cygwin environment) @@ -61,9 +49,6 @@ For __applicationCache__ to work your server needs to send the correct MIME type Author: Daniel Saul [@danielsaul](https://github.com/danielsaul) -[See concept for phone portrait mode](https://github.com/rossengeorgiev/habitat-mobile-tracker/blob/master/resources/concept-app-portrait.png) -[See concept for tablets](https://github.com/rossengeorgiev/habitat-mobile-tracker/blob/master/resources/concept-app-tablet.png) - diff --git a/build.sh b/build.sh index 778275b..f718a45 100755 --- a/build.sh +++ b/build.sh @@ -20,10 +20,8 @@ cat jquery* >> mobile.js VERSION="`git rev-parse --short HEAD`" # compile the rest -java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge gmaps_extentions.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge iscroll.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge chasecar.lib.js | sed "s/{VER}/$VERSION/" >> mobile.js -java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge nite-overlay.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge tracker.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge app.js >> mobile.js diff --git a/css/fullscreen.png b/css/fullscreen.png new file mode 100644 index 0000000..7384960 Binary files /dev/null and b/css/fullscreen.png differ diff --git a/css/fullscreen@2x.png b/css/fullscreen@2x.png new file mode 100644 index 0000000..9fca7f8 Binary files /dev/null and b/css/fullscreen@2x.png differ diff --git a/css/images/layers-2x.png b/css/images/layers-2x.png new file mode 100644 index 0000000..200c333 Binary files /dev/null and b/css/images/layers-2x.png differ diff --git a/css/images/layers.png b/css/images/layers.png new file mode 100644 index 0000000..1a72e57 Binary files /dev/null and b/css/images/layers.png differ diff --git a/css/images/marker-icon-2x.png b/css/images/marker-icon-2x.png new file mode 100644 index 0000000..88f9e50 Binary files /dev/null and b/css/images/marker-icon-2x.png differ diff --git a/css/images/marker-icon.png b/css/images/marker-icon.png new file mode 100644 index 0000000..950edf2 Binary files /dev/null and b/css/images/marker-icon.png differ diff --git a/css/images/marker-shadow.png b/css/images/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/css/images/marker-shadow.png differ diff --git a/css/leaflet.css b/css/leaflet.css new file mode 100644 index 0000000..017fa0e --- /dev/null +++ b/css/leaflet.css @@ -0,0 +1,640 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/css/leaflet.fullscreen.css b/css/leaflet.fullscreen.css new file mode 100644 index 0000000..0a7c123 --- /dev/null +++ b/css/leaflet.fullscreen.css @@ -0,0 +1,52 @@ +.leaflet-control-fullscreen a { + background:#fff url(fullscreen.png) no-repeat 0 0; + background-size:26px 52px; + } + .leaflet-touch .leaflet-control-fullscreen a { + background-position: 2px 2px; + } + .leaflet-fullscreen-on .leaflet-control-fullscreen a { + background-position:0 -26px; + } + .leaflet-touch.leaflet-fullscreen-on .leaflet-control-fullscreen a { + background-position: 2px -24px; + } + +@media (orientation: landscape) { + .leaflet-control-fullscreen { + position:relative; + top:-25px; + } + .leaflet-fullscreen-on .leaflet-control-fullscreen { + position:relative; + top:0px; + } +} + + +/* Do not combine these two rules; IE will break. */ +.leaflet-container:-webkit-full-screen { + width:100%!important; + height:100%!important; + } +.leaflet-container.leaflet-fullscreen-on { + width:100%!important; + height:100%!important; + } + +.leaflet-pseudo-fullscreen { + position:fixed!important; + width:100%!important; + height:100%!important; + top:0!important; + left:0!important; + z-index:99999; + } + +@media + (-webkit-min-device-pixel-ratio:2), + (min-resolution:192dpi) { + .leaflet-control-fullscreen a { + background-image:url(fullscreen@2x.png); + } + } diff --git a/css/main.css b/css/main.css index 64a676d..28ef1da 100644 --- a/css/main.css +++ b/css/main.css @@ -150,17 +150,14 @@ body { background: #00A3D3; } #loading img { - width: 350px; - height: 250px; -} -#loading > div { position: absolute; - top: 50%; + display: block; + width: 80vw; + max-width: 500px; left: 50%; - width: 350px; - height: 250px; - margin-top: -175px; - margin-left: -125px; + top: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); } #loading .bar { position: relative; @@ -179,7 +176,7 @@ body { header { position: fixed; top: 0; - left; 0; + left: 0; padding: 0; margin: 0; height: 50px; @@ -326,9 +323,6 @@ header .search form input[type='submit'] { border-left: 5px solid #ccc; } -#main .vehicle0 .header { - border-top: 1px solid #33b5e5; -} #main .header.empty { text-align: center; width: 100%; @@ -418,6 +412,7 @@ header .search form input[type='submit'] { #main .row .data .vbutton { position: absolute; background-color: #fff; + width: 25px !important; right: 5px; top: 150px; padding-left: 3px; @@ -427,6 +422,23 @@ header .search form input[type='submit'] { border: 1px solid #ccc; cursor: pointer; z-index: 5; + text-align: center; +} + +#main .row .data .sbutton { + position: absolute; + background-color: #fff; + width: 25px !important; + right: 5px; + top: 170px; + padding-left: 3px; + padding-right: 3px; + font-size: 10px; + border-radius: 5px; + border: 1px solid #ccc; + cursor: pointer; + z-index: 5; + text-align: center; } #main .row .data .vbutton.active { @@ -439,15 +451,10 @@ header .search form input[type='submit'] { border: 1px solid #5E5E5E; } -#main .portrait .row .data .vbutton { - display: none; -} - #main .row .data img { position: absolute; z-index: 2; - bottom: 40%; - right: 30%; + right: 35%; opacity: 0.6; width: 46px; height: 84px; @@ -737,6 +744,16 @@ header .search form input[type='submit'] { display: none; } +.leaflet-tooltip.serialtooltip { + background-color: transparent; + border: 0px; + box-shadow: none; + font-size: 12px; + font-weight: bold; + font-family: 'Roboto'; + text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white; +} + @media only screen and (min-width: 900px) { } @@ -814,7 +831,7 @@ header .search form input[type='submit'] { } #main .data .left { float: left; - width: 160px; + width: 80%; padding-left: 5px; } #main .data dl > dt { @@ -850,13 +867,13 @@ header .search form input[type='submit'] { } #main .data .left { float: left; - width: 65%; + width: 55%; padding-left: 5px; } #main .data .right { float: right; padding-right: 10px; - width: 25%; + width: 30%; } #main .data dl > dt { color: #000; diff --git a/embed-preview.html b/embed-preview.html deleted file mode 100644 index ed7a94e..0000000 --- a/embed-preview.html +++ /dev/null @@ -1,263 +0,0 @@ - - - Preview of embedded habhub tracker - - - - - - - -
-

Embed habhub tracker on your page

-

1. Options

-
- - -
- - -
- - -
- - -
-

2. Style

-
- - - - -
- - - - -
-

3. HTML code

- - -

4. Live preview

-
- -
-
- - - diff --git a/img/icons/icon_x192.png b/img/icons/icon_x192.png new file mode 100644 index 0000000..618ae3a Binary files /dev/null and b/img/icons/icon_x192.png differ diff --git a/img/icons/icon_x512.png b/img/icons/icon_x512.png new file mode 100644 index 0000000..9654aad Binary files /dev/null and b/img/icons/icon_x512.png differ diff --git a/img/icons/maskable_icon_x128.png b/img/icons/maskable_icon_x128.png new file mode 100644 index 0000000..ed395e1 Binary files /dev/null and b/img/icons/maskable_icon_x128.png differ diff --git a/img/icons/maskable_icon_x192.png b/img/icons/maskable_icon_x192.png new file mode 100644 index 0000000..1ec5586 Binary files /dev/null and b/img/icons/maskable_icon_x192.png differ diff --git a/img/icons/maskable_icon_x384.png b/img/icons/maskable_icon_x384.png new file mode 100644 index 0000000..ef558c9 Binary files /dev/null and b/img/icons/maskable_icon_x384.png differ diff --git a/img/icons/maskable_icon_x48.png b/img/icons/maskable_icon_x48.png new file mode 100644 index 0000000..56ae4a7 Binary files /dev/null and b/img/icons/maskable_icon_x48.png differ diff --git a/img/icons/maskable_icon_x512.png b/img/icons/maskable_icon_x512.png new file mode 100644 index 0000000..f00d755 Binary files /dev/null and b/img/icons/maskable_icon_x512.png differ diff --git a/img/icons/maskable_icon_x72.png b/img/icons/maskable_icon_x72.png new file mode 100644 index 0000000..1388186 Binary files /dev/null and b/img/icons/maskable_icon_x72.png differ diff --git a/img/icons/maskable_icon_x96.png b/img/icons/maskable_icon_x96.png new file mode 100644 index 0000000..febd7c0 Binary files /dev/null and b/img/icons/maskable_icon_x96.png differ diff --git a/img/icons/nyan_icon_x192.png b/img/icons/nyan_icon_x192.png new file mode 100644 index 0000000..5514673 Binary files /dev/null and b/img/icons/nyan_icon_x192.png differ diff --git a/img/icons/nyan_icon_x512.png b/img/icons/nyan_icon_x512.png new file mode 100644 index 0000000..5eaeecb Binary files /dev/null and b/img/icons/nyan_icon_x512.png differ diff --git a/img/markers/antenna-green-dark.png b/img/markers/antenna-green-dark.png new file mode 100644 index 0000000..7449eed Binary files /dev/null and b/img/markers/antenna-green-dark.png differ diff --git a/img/screenshots/screenshot1.png b/img/screenshots/screenshot1.png new file mode 100644 index 0000000..bb52e2c Binary files /dev/null and b/img/screenshots/screenshot1.png differ diff --git a/img/screenshots/screenshot2.png b/img/screenshots/screenshot2.png new file mode 100644 index 0000000..2c88ffe Binary files /dev/null and b/img/screenshots/screenshot2.png differ diff --git a/img/screenshots/screenshot3.png b/img/screenshots/screenshot3.png new file mode 100644 index 0000000..b3b6031 Binary files /dev/null and b/img/screenshots/screenshot3.png differ diff --git a/index.html b/index.html index 746e5e3..84eb567 100644 --- a/index.html +++ b/index.html @@ -4,28 +4,24 @@ SondeHub Tracker - + - + + - - + + + @@ -77,52 +73,46 @@

Welcome

Radiosondes?

To learn more check out our - LCA video. + LCA video.

How do I receive?

Set up a receiver station using - auto_rx. + auto_rx or rdzTTGOsonde.
- Note that you must be running at least v1.5.1 to show up on Sondehub v2!

Recovered a Sonde?

Click the 'car' icon on the top-right to submit a recovery notification.

Support Us!

- Google Maps and AWS hosting unfortunately does not come cheap. If you're seeing a greyed out map, that's because we've exceeded our free Google Maps page loads.
- If you use SondeHub regularly, please consider supporting us on Patreon, or with a donation on Paypal. + AWS hosting unfortunately does not come cheap. + If you use SondeHub regularly, please consider supporting us on Patreon, or with a donation on Paypal.

Amateur HAB Launches?

- You can find Amateur High-Altitude Balloon launches on the HabHub Tracker. + You can find Amateur High-Altitude Balloon launches on the HabHub Tracker. -

SondeHub Status Page

- You can find the status of the SondeHub Database on the SondeHub Status Page. +

SondeHub Status Pages

+ You can find the status of the SondeHub Database on the SondeHub Dashboard.
+ You can view outages of the SondeHub Database at SondeHub Status. + +

Info

+ This site uses data from the SondeHub v2 database, which will remove the radiosonde + load from the Habitat tracking database. + +

Chase Cars

+ Chase Cars can show up on the map using this tracker's chase-car features (look for the car icon at top-right) + or upload their position from Chasemapper or rdzTTGOsonde.
-
-

Under Beta!

-
- Welcome to the new SondeHub Tracker v2! -
- This site uses data from the SondeHub v2 database, which will remove the radiosonde - load from the Habitat tracking database. We are still working to being this tracker up to feature-parity with the previous - tracker, so some features like filtering by serial and type are still in-progress. -
- Chase Cars can show up on the map using this tracker's chase-car features (look for the car icon at top-right) - or upload their position from Chasemapper. -
- Please be patient as we work to build up the feature set, and expect some bugs! -

Contribute


Did you know the tracker is open-source? Check it out on - github/sondehub-tracker. + github/sondehub-tracker. Bug reports, suggestions and pull requests are welcome. -
+

A huge thanks to RGP for developing the mobile tracker that this site is based on. -
- You can also find us on IRC in #highaltitude at libera.chat. +

+ You can also find us on IRC in #highaltitude at libera.chat.

@@ -193,6 +183,13 @@

Settings

+
+ Hide Titles +
+ + +
+

Overlays


@@ -205,26 +202,14 @@

Overlays

Other


- Availability offline -
- - -
-
-
- Mobile station -
+ Show Launch Sites +
- +
-
- - Chase car equipped with radio receiver - -
- Force check for new version + Force check for new version
@@ -247,7 +232,7 @@

Weather


-

Here you can access various weather overlays. This an experimental feature. Mobile users be aware that this can quickly eat your data allowance.

+

Here you can access various weather overlays.

- Notice: If you enable this, your location will be uploaded to habitat; making it publicly visible on the map. + Notice: If you enable this, your location will be uploaded to SondeHub; making it publicly visible on the map.

@@ -374,34 +359,24 @@

Report Recovery

- - - + + + + - - + + + + + + + diff --git a/js/L.Terminator.js b/js/L.Terminator.js new file mode 100644 index 0000000..8e60210 --- /dev/null +++ b/js/L.Terminator.js @@ -0,0 +1,148 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('leaflet')) : + typeof define === 'function' && define.amd ? define(['leaflet'], factory) : + (global.L = global.L || {}, global.L.terminator = factory(global.L)); +}(this, (function (L) { 'use strict'; + + L = L && L.hasOwnProperty('default') ? L['default'] : L; + + /* Terminator.js -- Overlay day/night region on a Leaflet map */ + + function julian(date) { + /* Calculate the present UTC Julian Date. Function is valid after + * the beginning of the UNIX epoch 1970-01-01 and ignores leap + * seconds. */ + return (date / 86400000) + 2440587.5; + } + + function GMST(julianDay) { + /* Calculate Greenwich Mean Sidereal Time according to + http://aa.usno.navy.mil/faq/docs/GAST.php */ + var d = julianDay - 2451545.0; + // Low precision equation is good enough for our purposes. + return (18.697374558 + 24.06570982441908 * d) % 24; + } + + var Terminator = L.Polygon.extend({ + options: { + color: '#00', + opacity: 0.5, + fillColor: '#00', + fillOpacity: 0.5, + resolution: 2 + }, + + initialize: function (options) { + this.version = '0.1.0'; + this._R2D = 180 / Math.PI; + this._D2R = Math.PI / 180; + L.Util.setOptions(this, options); + var latLng = this._compute(this.options.time); + this.setLatLngs(latLng); + }, + + setTime: function (date) { + this.options.time = date; + var latLng = this._compute(date); + this.setLatLngs(latLng); + }, + + _sunEclipticPosition: function (julianDay) { + /* Compute the position of the Sun in ecliptic coordinates at + julianDay. Following + http://en.wikipedia.org/wiki/Position_of_the_Sun */ + // Days since start of J2000.0 + var n = julianDay - 2451545.0; + // mean longitude of the Sun + var L$$1 = 280.460 + 0.9856474 * n; + L$$1 %= 360; + // mean anomaly of the Sun + var g = 357.528 + 0.9856003 * n; + g %= 360; + // ecliptic longitude of Sun + var lambda = L$$1 + 1.915 * Math.sin(g * this._D2R) + + 0.02 * Math.sin(2 * g * this._D2R); + // distance from Sun in AU + var R = 1.00014 - 0.01671 * Math.cos(g * this._D2R) - + 0.0014 * Math.cos(2 * g * this._D2R); + return {lambda: lambda, R: R}; + }, + + _eclipticObliquity: function (julianDay) { + // Following the short term expression in + // http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29 + var n = julianDay - 2451545.0; + // Julian centuries since J2000.0 + var T = n / 36525; + var epsilon = 23.43929111 - + T * (46.836769 / 3600 + - T * (0.0001831 / 3600 + + T * (0.00200340 / 3600 + - T * (0.576e-6 / 3600 + - T * 4.34e-8 / 3600)))); + return epsilon; + }, + + _sunEquatorialPosition: function (sunEclLng, eclObliq) { + /* Compute the Sun's equatorial position from its ecliptic + * position. Inputs are expected in degrees. Outputs are in + * degrees as well. */ + var alpha = Math.atan(Math.cos(eclObliq * this._D2R) + * Math.tan(sunEclLng * this._D2R)) * this._R2D; + var delta = Math.asin(Math.sin(eclObliq * this._D2R) + * Math.sin(sunEclLng * this._D2R)) * this._R2D; + + var lQuadrant = Math.floor(sunEclLng / 90) * 90; + var raQuadrant = Math.floor(alpha / 90) * 90; + alpha = alpha + (lQuadrant - raQuadrant); + + return {alpha: alpha, delta: delta}; + }, + + _hourAngle: function (lng, sunPos, gst) { + /* Compute the hour angle of the sun for a longitude on + * Earth. Return the hour angle in degrees. */ + var lst = gst + lng / 15; + return lst * 15 - sunPos.alpha; + }, + + _latitude: function (ha, sunPos) { + /* For a given hour angle and sun position, compute the + * latitude of the terminator in degrees. */ + var lat = Math.atan(-Math.cos(ha * this._D2R) / + Math.tan(sunPos.delta * this._D2R)) * this._R2D; + return lat; + }, + + _compute: function (time) { + var today = time ? new Date(time) : new Date(); + var julianDay = julian(today); + var gst = GMST(julianDay); + var latLng = []; + + var sunEclPos = this._sunEclipticPosition(julianDay); + var eclObliq = this._eclipticObliquity(julianDay); + var sunEqPos = this._sunEquatorialPosition(sunEclPos.lambda, eclObliq); + for (var i = 0; i <= 720 * this.options.resolution; i++) { + var lng = -360 + i / this.options.resolution; + var ha = this._hourAngle(lng, sunEqPos, gst); + latLng[i + 1] = [this._latitude(ha, sunEqPos), lng]; + } + if (sunEqPos.delta < 0) { + latLng[0] = [90, -360]; + latLng[latLng.length] = [90, 360]; + } else { + latLng[0] = [-90, -360]; + latLng[latLng.length] = [-90, 360]; + } + return latLng; + } + }); + + function terminator(options) { + return new Terminator(options); + } + + return terminator; + +}))); diff --git a/js/Leaflet.fullscreen.min.js b/js/Leaflet.fullscreen.min.js new file mode 100644 index 0000000..184cc7f --- /dev/null +++ b/js/Leaflet.fullscreen.min.js @@ -0,0 +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)}; \ No newline at end of file diff --git a/js/app.js b/js/app.js index b82065e..3d7c284 100644 --- a/js/app.js +++ b/js/app.js @@ -13,7 +13,7 @@ function lhash_update(history_step) { var hash = ""; // generate hash - hash += "mt=" + map.getMapTypeId(); + hash += "mt=" + selectedLayer; hash += "&mz=" + map.getZoom(); if(!/^[a-z0-9]{32}$/ig.exec(wvar.query)) { @@ -22,8 +22,8 @@ function lhash_update(history_step) { if(follow_vehicle === null || manual_pan) { var latlng = map.getCenter(); - hash += "&mc=" + roundNumber(latlng.lat(), 5) + - "," + roundNumber(latlng.lng(), 5); + hash += "&mc=" + roundNumber(latlng.lat, 5) + + "," + roundNumber(latlng.lng, 5); } if(follow_vehicle !== null) { @@ -95,7 +95,10 @@ function load_hash(no_refresh) { switch(k) { case "mt": - map.setMapTypeId(v); + if( baseMaps.hasOwnProperty(v) ) { + selectedLayer = v; + map.addLayer(baseMaps[v]); + } break; case "mz": map.setZoom(parseInt(v)); @@ -104,8 +107,8 @@ function load_hash(no_refresh) { def.zoom = false; manual_pan = true; v = v.split(','); - var latlng = new google.maps.LatLng(v[0], v[1]); - map.setCenter(latlng); + var latlng = new L.LatLng(v[0], v[1]); + map.setView(latlng); break; case "f": refocus = (follow_vehicle != v); @@ -177,11 +180,6 @@ for(var idx in params) { } } -if(wvar.enabled) { - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Embed Opts', window.location.search]); -} - $.ajaxSetup({ cache: true }); var force_check_cache = false; @@ -337,7 +335,7 @@ function checkSize() { // this should hide the address bar on mobile phones, when possible window.scrollTo(0,1); - if(map) google.maps.event.trigger(map, 'resize'); + if(map) map.invalidateSize(); } window.onresize = checkSize; @@ -419,8 +417,6 @@ var positionUpdateHandle = function(position) { if(CHASE_enabled) { ChaseCar.updatePosition(callsign, position); CHASE_timer = (new Date()).getTime() + 15000; - - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']); } } else { return; } @@ -552,20 +548,14 @@ $(window).ready(function() { h = $('#map').height() + $('#telemetry_graph').height(); plot_open = false; - - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Collapse', 'Telemetry Graph']); } else { e.addClass('active'); h = $('#map').height() - $('#telemetry_graph').height(); plot_open = true; - - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Expand', 'Telemetry Graph']); } $('#map').stop(null,null).animate({'height': h}, function() { - if(map) google.maps.event.trigger(map, 'resize'); + if(map) map.invalidateSize(); if(plot_open && follow_vehicle !== null && @@ -585,11 +575,11 @@ $(window).ready(function() { if(elm.hasClass("active")) { elm.removeClass('active'); - hysplit[name].setMap(null); + map.removeLayer(hysplit[name]); } else { elm.addClass('active'); - hysplit[name].setMap(map); + map.addLayer(hysplit[name]); } }); @@ -627,10 +617,6 @@ $(window).ready(function() { // confirm dialog when launchnig a native map app with coordinates //$('#main').on('click', '#launch_mapapp', function() { // var answer = confirm("Launch your maps app?"); - - // //analytics - // if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', ((answer)?"Yes":"No"), 'Coord Click']); - // return answer; //}); @@ -707,8 +693,6 @@ $(window).ready(function() { case "chasecar": pretty_name = "Chase Car"; break; default: pretty_name = name[0].toUpperCase() + name.slice(1); } - - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI Menubar', 'Open Page', pretty_name]); } checkSize(); }); @@ -728,13 +712,10 @@ $(window).ready(function() { //CHASE_enabled = false; // blue man reappers :) - if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(true); - - // analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', 'Chase Car']); + if(currentPosition && currentPosition.marker) map.addLayer(currentPosition.marker); // turning the switch on } else { - if(callsign.length < 5) { alert('Please enter a valid callsign, at least 5 characters'); return; } + if(callsign.length == null || callsign.length < 5) { alert('Please enter a valid callsign, at least 5 characters'); return; } if(!callsign.match(/^[a-zA-Z0-9\_\-]+$/)) { alert('Invalid characters in callsign (use only a-z,0-9,-,_)'); return; } field.attr('disabled','disabled'); @@ -752,17 +733,13 @@ $(window).ready(function() { // if already have a position push it to habitat if(GPS_ts) { ChaseCar.updatePosition(callsign, { coords: { latitude: GPS_lat, longitude: GPS_lon, altitude: GPS_alt, speed: GPS_speed }}); - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']); } if(navigator.geolocation) CHASE_enabled = navigator.geolocation.watchPosition(positionUpdateHandle, positionUpdateError); //CHASE_enabled = true; // hide the blue man - if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(false); - - // analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', 'Chase Car']); + if(currentPosition && currentPosition.marker) map.removeLayer(currentPosition.marker); } }); @@ -818,6 +795,8 @@ $(window).ready(function() { "#sw_hide_timebox", "#sw_hilight_vehicle", '#sw_hide_horizon', + '#sw_hide_titles', + '#sw_layers_launches', "#sw_nowelcome", "#sw_interpolate", ]; @@ -831,15 +810,9 @@ $(window).ready(function() { if(e.hasClass('on')) { e.removeClass('on').addClass('off'); on = 0; - - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', name]); } else { e.removeClass('off').addClass('on'); on = 1; - - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', name]); } // remember choice @@ -856,8 +829,15 @@ $(window).ready(function() { refreshUI(); break; case "opt_daylight": - if(on) { nite.show(); } - else { nite.hide(); } + if(on) { + nite.addTo(map); + niteupdate = setInterval(function() { + nite.setTime(); + }, 60000); // Every minute + } else { + nite.remove(); + clearInterval(nite); + } break; case "opt_hide_receivers": if(on) { @@ -895,10 +875,26 @@ $(window).ready(function() { showHorizonRings(); } break; + case "opt_hide_titles": + if(on) { + hideTitles(); + } + else { + showTitles(); + } + break; case "opt_layers_aprs": if(on) map.overlayMapTypes.setAt("1", overlayAPRS); else map.overlayMapTypes.setAt("1", null); break; + case "opt_layers_launches": + showLaunchSites(); + if(on) { + map.addLayer(launches); + } else { + map.removeLayer(launches); + } + break; case "opt_interpolate": if(on) { graph_gap_size = graph_gap_size_max; } else { graph_gap_size = graph_gap_size_default; } @@ -942,16 +938,13 @@ $(window).ready(function() { // Enable the chase-car option for all browsers, not just mobile ones. $(".chasecar").show(); $("#locate-me,#app_name").attr('style','').click(function() { - if(map && currentPosition) { + if(currentPosition) { // disable following of vehicles stopFollow(); // open map $('.nav .home').click(); // pan map to our current location - map.panTo(new google.maps.LatLng(currentPosition.lat, currentPosition.lon)); - - //analytics - if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Locate me']); + map.flyTo(new L.LatLng(currentPosition.lat, currentPosition.lon)); } else { alert("No position available"); } @@ -969,32 +962,14 @@ $(window).ready(function() { // list of overlays var overlayList = [ ['Global', [ - ['google-radar','Google Earth Radar'], - ['nrl-global-cloudtop','NRL Monterey Cloudtop'], - ['nrl-global-ir','NRL Monterey IR'], - ['nrl-global-vapor','NRL Monterey Vapor'] - ]], - ['Europe/Africa', [ - ['meteosat-Odeg-MPE', 'METEOSAT Precip. Estimate'] - ]], - ['Indian Ocean', [ - ['meteosat-iodc-MPE', 'METEOSAT IODC Precip. Est.'] + ['rainviewer', 'RainViewer'], + ['rainviewer-coverage', 'RainViewer Coverage'], ]], ['North America', [ ['nexrad-n0q-900913', 'NEXRAD Base Reflectivity'], ['goes-ir-4km-900913', 'GOES NA Infrared ~4km'], ['goes-wv-4km-900913', 'GOES NA Water Vapor ~4km'], ['goes-vis-1km-900913', 'GOES NA Visible ~1km'], - ['goes-east-ir-4km-900913', 'GOES East CONUS Infrared'], - ['goes-east-wv-4km-900913', 'GOES East CONUS Water Vapor'], - ['goes-east-vis-1km-900913', 'GOES East CONUS Visible'], - ['goes-west-ir-4km-900913', 'GOES West CONUS Infrared'], - ['goes-west-wv-4km-900913', 'GOES West CONUS Water Vapor'], - ['goes-west-vis-1km-900913', 'GOES West CONUS Visible'], - ['hawaii-vis-900913', 'GOES West Hawaii Visible'], - ['alaska-vis-900913', 'GOES West Alaska Visible'], - ['alaska-ir-900913', 'GOES West Alaska IR'], - ['alaska-wv-900913', 'GOES West Alaska Water Vapor'], ['q2-n1p-900913', 'Q2 1 Hour Precipitation'], ['q2-p24h-900913', 'Q2 24 Hour Precipitation'], ['q2-p48h-900913', 'Q2 48 Hour Precipitation'], @@ -1029,6 +1004,8 @@ $(window).ready(function() { } } + var weatherLayer; + // the magic that makes the switches do things elm.find(".switch").click(function() { var e = $(this); @@ -1036,6 +1013,19 @@ $(window).ready(function() { var id = name.replace("opt_weather_",""); var on; + try { + map.removeLayer(weatherLayer); + } catch (err) {}; + + try { + map.removeLayer(RainRadar); + } catch (err) {}; + + try { + map.removeLayer(RainRadarCoverage); + } catch (err) {}; + + if(e.hasClass('on')) { e.removeClass('on').addClass('off'); on = 0; @@ -1046,26 +1036,17 @@ $(window).ready(function() { on = 1; } - weatherImageOverlay.setMap(null); - weatherGoogleRadar.setMap(null); - map.overlayMapTypes.setAt("0", null); - if(on) { - if(id == "google-radar") { - weatherGoogleRadar.setMap(map); - return; - } else if(id in weatherImageOverlayList) { - var o = weatherImageOverlayList[id]; - var sw = new google.maps.LatLng(o[1][0][0], o[1][0][1]); - var ne = new google.maps.LatLng(o[1][1][0], o[1][1][1]); - var bounds = new google.maps.LatLngBounds(sw, ne); - weatherImageOverlay = new google.maps.GroundOverlay(o[0], bounds, {opacity: 0.7}); - weatherImageOverlay.setMap(map); - return; + if (id == "rainviewer") { + RainRadar.addTo(map); + } else if (id == "rainviewer-coverage") { + RainRadarCoverage.addTo(map); + } else { + weatherLayer = L.tileLayer('https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/' + id + '/{z}/{x}/{y}.png?' + (new Date()).getTime(), { + opacity: 0.6, + attribution: '© Iowa Environmental Mesonet' + }).addTo(map); } - - weatherOverlayId = id; - map.overlayMapTypes.setAt("0", weatherOverlay); } }); @@ -1081,9 +1062,12 @@ $(window).ready(function() { wvar.query = text; stopFollow(); zoomed_in = false; - wvar.zoom = true; + if (sondePrefix.indexOf(wvar.query) > -1) { + wvar.zoom = false; + } else { + wvar.zoom = true; + } - if(text === "") { wvar.mode = null; } clean_refresh(wvar.mode, true, true); }); }); diff --git a/js/chasecar.lib.js b/js/chasecar.lib.js index fb89f29..0c7f62f 100644 --- a/js/chasecar.lib.js +++ b/js/chasecar.lib.js @@ -146,7 +146,6 @@ ChaseCar.markRecovered = function(){ dataType: "json", data: JSON.stringify(_doc), }).done(function(data) { - console.log(data); $('#pr_last_report').text("Reported OK!"); }) .fail(function(jqXHR, textStatus, error) { diff --git a/js/gmaps_extentions.js b/js/gmaps_extentions.js deleted file mode 100644 index fdc73e6..0000000 --- a/js/gmaps_extentions.js +++ /dev/null @@ -1,220 +0,0 @@ - -// custom label function - -google.maps.Label = function(opt_options) { - // init default values - this.set('visible', true); - this.set('opacity', 1); - this.set('clickable', false); - this.set('strokeColor', "#00F"); - this.set('text', ""); - this.set('textOnly', false); // true only text, false text within a box - - this.setValues(opt_options); - - var span = this.span_ = document.createElement('span'); - span.style.cssText = 'position: relative; left: -50%;' + - 'white-space: nowrap; color: #000;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;'; - - span.style.cssText += !this.get('textOnly') ? - 'border: 1px solid '+this.get('strokeColor')+'; border-radius: 5px; ' + - 'top:-12px;font-size:9px;padding: 2px; background-color: white' - : - 'top:-8px;font-size:12px;font-weight: bold; text-shadow: 2px 0 0 #fff, -2px 0 0 #fff, 0 2px 0 #fff, 0 -2px 0 #fff, 1px 1px #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff;' - ; - - var div = this.div_ = document.createElement('div'); - div.appendChild(span); - div.style.cssText = 'position: absolute; display: none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;'; -}; - -google.maps.Label.prototype = new google.maps.OverlayView(); - - -// Implement onAdd -google.maps.Label.prototype.onAdd = function() { - var pane = this.getPanes().overlayImage; - pane.appendChild(this.div_); - - // redraw if any option is changed - var ctx = this; - var callback = function() { ctx.draw(); }; - this.listeners_ = [ - google.maps.event.addListener(this, 'opacity_changed', callback), - google.maps.event.addListener(this, 'position_changed', callback), - google.maps.event.addListener(this, 'visible_changed', callback), - google.maps.event.addListener(this, 'clickable_changed', callback), - google.maps.event.addListener(this, 'text_changed', callback), - google.maps.event.addListener(this, 'zindex_changed', callback), - google.maps.event.addDomListener(this.div_, 'click', function() { - if (ctx.get('clickable')) { - google.maps.event.trigger(ctx, 'click'); - } - }) - ]; -}; - - -// Implement onRemove -google.maps.Label.prototype.onRemove = function() { - this.div_.parentNode.removeChild(this.div_); - - // remove all listeners - for (var i = 0, j = this.listeners_.length; i < j; i++) { - google.maps.event.removeListener(this.listeners_[i]); - } -}; - - -// Implement draw -google.maps.Label.prototype.draw = function() { - var projection = this.getProjection(); - var position = projection.fromLatLngToDivPixel(this.get('position')); - - var div = this.div_; - if(position !== null) { - div.style.left = position.x + 'px'; - div.style.top = position.y + 'px'; - } - - div.style.display = this.get('visible') && this.get('opacity') >= 0.6 ? 'block' : 'none'; - this.span_.style.cursor = this.get('clickable') ? 'pointer' : ''; - div.style.zIndex = this.get('zIndex'); - this.span_.innerHTML = this.get('text').toString(); -}; - - -// custom dropdown menu control - -google.maps.DropDownControl = function(options) { - var ctx = this; - this.options = options; - - // generate the controls - this.div_ = document.createElement('div'); - this.div_.className = "gmnoprint"; - this.div_.draggable = false; - this.div_.style.cssText = "margin: 10px; margin-top: 0;z-index: 0; position: absolute; cursor: pointer; text-align: left; width: 85px; right: 0px; top: 0px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none"; - - this.div_head = document.createElement('div'); - this.div_head.style.cssText = "direction: ltr; overflow: hidden; text-align: left; position: relative; color: rgb(0, 0, 0); font-family: Roboto, Arial, sans-serif; -webkit-user-select: none; font-size: 11px; padding: 8px; border-radius: 2px; -webkit-background-clip: padding-box; box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px; font-weight: 500; background-color: rgb(255, 255, 255); background-clip: padding-box;"; - this.div_head.title = options.title; - - google.maps.event.addDomListener(this.div_head, 'mouseover', function(){ - ctx.div_head.style.backgroundColor = "rgb(235,235,235)"; - }); - google.maps.event.addDomListener(this.div_head, 'mouseout', function(){ - ctx.div_head.style.backgroundColor = "rgb(255,255,255)"; - }); - - this.header = document.createElement('span'); - this.header.innerHTML = (options.headerPrefix || "") + options.list[options.listDefault || 0]; - var arrow = document.createElement('img'); - arrow.src = "//maps.gstatic.com/mapfiles/arrow-down.png"; - arrow.style.cssText = "-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select: none; border: 0px none; padding: 0px; margin: -2px 0px 0px; position: absolute; right: 6px; top: 50%; width: 7px; height: 4px;"; - - this.div_head.appendChild(this.header); - this.div_head.appendChild(arrow); - this.div_.appendChild(this.div_head); - - // generate list of dropdown entries - this.div_list = document.createElement('div'); - this.div_list.style.cssText = "z-index: -1; padding: 2px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px; position: absolute; top: 100%; left: 0px; right: 0px; text-align: left; display: none; background-color: white;"; - - var div_list = this.div_list; - - options.list.forEach(function(name) { - var row = document.createElement('div'); - row.style.cssText = "color: rgb(86, 86, 86); font-family: Roboto, Arial, sans-serif; -webkit-user-select: none; font-size: 11px; padding: 6px; background-color: rgb(255, 255, 255);"; - row.innerHTML = name; - - google.maps.event.addDomListener(row, 'click', function(){ - if(ctx.options.callback(row.innerHTML)) { - ctx.header.innerHTML = (ctx.options.headerPrefix || "") + row.innerHTML; - row.style.fontWeight = "800"; - row.style.color = "rgb(0,0,0)"; - } - }); - google.maps.event.addDomListener(row, 'mouseover', function(){ - if(ctx.header.innerHTML == (ctx.options.headerPrefix || "") + row.innerHTML) { - row.style.fontWeight = "800"; - row.style.color = "rgb(0,0,0)"; - } - row.style.backgroundColor = "rgb(235,235,235)"; - }); - google.maps.event.addDomListener(row, 'mouseout', function(){ - row.style.fontWeight = "500"; - row.style.color = "rgb(86,86,86)"; - row.style.backgroundColor = "rgb(255,255,255)"; - }); - - div_list.appendChild(row); - }); - - this.div_.appendChild(this.div_list); - - // add control - options.map.controls[options.position].push(this.div_); - - // event for expanding - - google.maps.event.addDomListener(this.div_head, 'click', function(){ - clearTimeout(ctx.hideTimeout); - div_list.style.display = (div_list.style.display != 'none') ? 'none' : 'block'; - }); - google.maps.event.addDomListener(this.div_, 'mouseout', function(){ - ctx.hideTimeout = setTimeout(function() { div_list.style.display = 'none'; }, 1000); - }); - google.maps.event.addDomListener(this.div_, 'mouseover', function(){ - clearTimeout(ctx.hideTimeout); - }); -}; - -google.maps.DropDownControl.prototype.setVisible = function(isVisible) { - isVisible = !!isVisible; - this.div_.style.display = (isVisible) ? 'block' : 'none'; -}; - -google.maps.DropDownControl.prototype.select = function(text) { - this.header.innerHTML = (this.options.headerPrefix || "") + text; -}; - -// simple status control - -google.maps.StatusTextControl = function(options) { - this.options = options || { - text: "", - map: null, - position: 0, - fontSize: "10px", - }; - - this.div_ = document.createElement('div'); - this.div_.style.cssText = "display: none"; - this.div_.innerHTML = "
" + - "
" + - "
"; - - var div = document.createElement('div'); - div.style.cssText = 'position: relative; padding-right: 6px; padding-left: 6px;' + - ' font-family: Roboto, Arial, sans-serif; color: rgb(68, 68, 68);' + - ' white-space: nowrap; direction: ltr; text-align: right;' + - ' font-size: ' + this.options.fontSize; - - this.span_ = document.createElement('span'); - div.appendChild(this.span_); - this.div_.appendChild(div); - - // update text - this.setText(this.options.text); - - // add control - if(this.options.map) - this.options.map.controls[options.position].push(this.div_); -}; - -google.maps.StatusTextControl.prototype.setText = function(text) { - this.options.text = text; - this.span_.innerHTML = text; - this.div_.style.display = (text === "") ? "none" : "block"; -}; diff --git a/js/iscroll.js b/js/iscroll.js index fbbb42a..37d5c7c 100644 --- a/js/iscroll.js +++ b/js/iscroll.js @@ -821,7 +821,13 @@ IScroll.prototype = { }, scrollToElement: function (el, time, offsetX, offsetY, easing) { - el = el.nodeType ? el : document.querySelectorAll(el)[1]; + el = el.nodeType ? el : document.querySelectorAll(el); + //figure out which el to use (oreintation) + if (el[0].offsetHeight) { + el = el[0]; + } else { + el = el[1]; + } if ( !el ) { return; diff --git a/js/leaflet.canvas-markers.js b/js/leaflet.canvas-markers.js new file mode 100644 index 0000000..ba26b22 --- /dev/null +++ b/js/leaflet.canvas-markers.js @@ -0,0 +1,458 @@ +'use strict'; + +function layerFactory (L) { + + function extend (Parent, props) { + var NewClass = function () { + if (this.init) { this.init.apply(this, arguments); } + }; + var proto = L.Util.create(Parent.prototype); + L.Util.extend(proto, props); + NewClass.prototype = proto; + return NewClass; + } + + var Common = extend(rbush, { + init: function () { + rbush.apply(this, arguments); + this._batch = []; + }, + insert: function (item, batch) { + if (batch) { + this._batch.push(item); + return this; + } + return rbush.prototype.insert.call(this, item); + }, + flush: function () { + this.load(this._batch); + this._batch.length = 0; + return this; + } + }); + + var LatLngsIndex = extend(Common, { + toBBox: function (marker) { + var ll = marker._latlng; + return {minX: ll.lng, minY: ll.lat, maxX: ll.lng, maxY: ll.lat}; + }, + compareMinX: function (a, b) { return a._latlng.lng - b._latlng.lng; }, + compareMinY: function (a, b) { return a._latlng.lat - b._latlng.lat; }, + searchIn: function (bounds) { + return this.search({ + minX: bounds.getWest(), + minY: bounds.getSouth(), + maxX: bounds.getEast(), + maxY: bounds.getNorth() + }); + }, + init: function () { + Common.prototype.init.apply(this, arguments); + this._dirty = 0; + this._total = 0; + }, + // If we are 10% individual inserts\removals, reconstruct lookup for efficiency + cleanup: function () { + if (this._dirty / this._total >= .1) { + var all = this.all(); + this.clear(); + this._dirty = 0; + this.load(all); + } + return this; + }, + insert: function () { + this._dirty++; + this._total++; + return Common.prototype.insert.apply(this, arguments); + }, + remove: function () { + this._total--; + return Common.prototype.remove.apply(this, arguments); + }, + clear: function () { + this._dirty = 0; + this._total = 0; + return rbush.prototype.clear.apply(this); + } + }); + + var PointsIndex = extend(Common, { + toBBox: function (marker) { + var iconSize = marker.options.icon.options.iconSize; + var pos = marker._point; + var adj_x = iconSize[0] / 2; + var adj_y = iconSize[1] / 2; + return { + minX: pos.x - adj_x, + minY: pos.y - adj_y, + maxX: pos.x + adj_x, + maxY: pos.y + adj_y, + }; + }, + compareMinX: function (a, b) { return a._point.x - b._point.x; }, + compareMinY: function (a, b) { return a._point.y - b._point.y; }, + searchBy: function (point) { + return this.search({ + minX: point.x, minY: point.y, maxX: point.x, maxY: point.y + }); + } + }); + + var CanvasIconLayer = L.Layer.extend({ // todo inherit from L.Renderer or L.Canvas + + options: L.Canvas.prototype.options, + + initialize: function (options) { + L.Renderer.prototype.initialize.call(this, options); + // _pointsIdx contains Points of markers currently displaying on map + this._pointsIdx = new PointsIndex(); + // _latlngsIdx contains Lat\Long coordinates of all markers in layer. + this._latlngsIdx = new LatLngsIndex(); + }, + + onAdd: function () { + L.Renderer.prototype.onAdd.call(this); + L.DomUtil.toBack(this._container); + }, + + _initContainer: function () { + L.Canvas.prototype._initContainer.call(this); + this._hideContainer(true); + }, + + onRemove: function () { + L.Renderer.prototype.onRemove.call(this); + }, + + _destroyContainer: function () { + L.Canvas.prototype._destroyContainer.call(this); + this._pointsIdx.clear(); + }, + + getEvents: function () { // todo use L.Renderer.prototype.getEvents + var events = { + viewreset: this._reset, + zoom: this._onZoom, + moveend: this._update, + click: this._onClick, + mousemove: this._onMouseMove, + mouseout: this._handleMouseOut + }; + if (this._zoomAnimated) { + events.zoomanim = this._onAnimZoom; + } + return events; + }, + + _onAnimZoom: function (ev) { + L.Renderer.prototype._onAnimZoom.call(this, ev); + }, + + _onZoom: function () { + L.Renderer.prototype._onZoom.call(this); + }, + + _updateTransform: function (center, zoom) { + L.Renderer.prototype._updateTransform.call(this, center, zoom); + }, + + _updatePaths: L.Util.falseFn, // stub for L.Renderer onAdd/onRemove + + _update: function () { + L.Canvas.prototype._update.call(this); + this._draw(); + }, + + _reset: function () { + this._update(); + this._updateTransform(this._center, this._zoom); + }, + + _redraw: function () { + L.Canvas.prototype._redraw.call(this); + }, + + _clear: function () { + L.Canvas.prototype._clear.call(this); + }, + + _draw: function () { + var bounds = this._redrawBounds; + if (bounds) { + var size = bounds.getSize(); + this._ctx.beginPath(); + this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); + this._ctx.clip(); + } + this._drawing = true; + this._latlngsIdx.cleanup(); + var mapBounds = this._map.getBounds().pad(this.options.padding); + + // Only re-draw what we are showing on the map. + var isEmpty = true; + this._latlngsIdx.searchIn(mapBounds).forEach(function (marker) { + // Readjust Point Map + if (!marker._map) { marker._map = this._map; } // todo ??implement proper handling in (on)add*/remove* + this._drawMarker(marker); + this._pointsIdx.insert(marker,true); + isEmpty = false; + }, this); + this._drawing = false; + // Clear rBush & Bulk Load for performance + this._pointsIdx.clear().flush(); + this._hideContainer(isEmpty); + }, + + _drawMarker: function (marker) { + marker._point = this._map.latLngToLayerPoint(marker.getLatLng()); + this._imageLookup = this._imageLookup || {}; + + var iconUrl = marker.options.icon.options.iconUrl; + var queued = this._imageLookup[iconUrl]; + if (!marker.canvas_img) { + if (queued) { + marker.canvas_img = queued.img; + if (queued.loaded) { + this._drawImage(marker); + } else { + queued.queue.push(marker); + } + } else { + var img = new Image(); + img.src = iconUrl; + marker.canvas_img = img; + queued = { + loaded: false, + img: img, + queue: [marker] + }; + this._imageLookup[iconUrl] = queued; + img.onload = function () { + queued.loaded = true; + queued.queue.forEach(function (_marker) { + if (this.hasLayer(_marker)) { + this._drawImage(_marker); + } + }, this); + }.bind(this); + } + } else if (queued.loaded) { // image may be not loaded / bad url + this._drawImage(marker); + } + }, + + _drawImage: function (marker) { + var options = marker.options.icon.options; + var pos = marker._point.subtract(options.iconAnchor); + this._ctx.drawImage( + marker.canvas_img, + pos.x, + pos.y, + options.iconSize[0], + options.iconSize[1] + ); + }, + + _onClick: function (e) { + var point = e.layerPoint || this._map.mouseEventToLayerPoint(e), clickedLayer; + + var layer_intersect = this._pointsIdx && this._pointsIdx.searchBy(point); + if (layer_intersect) { + layer_intersect.forEach(function (layer) { + if (layer.options.interactive && !this._map._draggableMoved(layer)) { + clickedLayer = layer; + } + }, this); + } + if (clickedLayer) { + L.DomEvent.fakeStop(e); + this._fireEvent([clickedLayer], e); + } + }, + + _onMouseMove: function (e) { + if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } + + var point = e.layerPoint || this._map.mouseEventToLayerPoint(e); + this._handleMouseHover(e, point); + }, + + _handleMouseHover: function (e, point) { + if (this._mouseHoverThrottled) { + return; + } + var candidateHoveredLayer; + var layer_intersect = this._pointsIdx && this._pointsIdx.searchBy(point); + if (layer_intersect) { + layer_intersect.forEach(function (layer) { + if (layer.options.interactive) { + candidateHoveredLayer = layer; + } + }, this); + } + + if (candidateHoveredLayer !== this._hoveredLayer) { + this._handleMouseOut(e); + + if (candidateHoveredLayer) { + L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor + this._fireEvent([candidateHoveredLayer], e, 'mouseover'); + this._hoveredLayer = candidateHoveredLayer; + } + } + + if (this._hoveredLayer) { + this._fireEvent([this._hoveredLayer], e); + } + + this._mouseHoverThrottled = true; + setTimeout(L.bind(function () { + this._mouseHoverThrottled = false; + }, this), 32); + }, + + _handleMouseOut: function (e) { + L.Canvas.prototype._handleMouseOut.call(this,e); + }, + + _fireEvent: function (layers, e, type) { + if (e.layerPoint) { + layers[0].fire(type || e.type, e, true); + return; + } + L.Canvas.prototype._fireEvent.call(this, layers, e, type); + }, + + _addMarker: function (marker, latlng, isDisplaying, batch) { + if (!(marker instanceof L.Marker)) { + throw new Error("Layer isn't a marker"); + } + marker._map = this._map; // Needed for pop-up & tooltip to work + L.Util.stamp(marker); + marker.addEventParent(this); + + if (isDisplaying) { + this._drawMarker(marker); + this._pointsIdx.insert(marker, batch); + this._hideContainer(false); + } + this._latlngsIdx.insert(marker, batch); + }, + + // Adds single layer at a time. Less efficient for rBush + addMarker: function (marker, groupID) { + groupID = groupID ? groupID.toString() : '0'; + this._groupIDs = this._groupIDs || {}; + + var latlng = marker.getLatLng(); + var isDisplaying = this._map && this._map.getBounds().pad(this.options.padding).contains(latlng); + this._addMarker(marker, latlng, isDisplaying); + this._groupIDs[groupID] = (this._groupIDs[groupID] || 0) + 1; + marker._canvasGroupID = groupID; + return this; + }, + + addLayer: function (layer, groupID) { + return this.addMarker(layer,groupID); + }, + + // Multiple layers at a time for rBush performance + addMarkers: function (markers, groupID) { + groupID = groupID ? groupID.toString() : '0'; + this._groupIDs = this._groupIDs || {}; + this._groupIDs[groupID] = this._groupIDs[groupID] || 0; + + var mapBounds = this._map && this._map.getBounds().pad(this.options.padding); + markers.forEach(function (marker) { + var latlng = marker.getLatLng(); + var isDisplaying = mapBounds && mapBounds.contains(latlng); + this._addMarker(marker, latlng, isDisplaying, true); + this._groupIDs[groupID]++; + marker._canvasGroupID = groupID; + }, this); + this._pointsIdx.flush(); + this._latlngsIdx.flush(); + return this; + }, + + addLayers: function (layers, groupID) { + return this.addMarkers(layers,groupID); + }, + + removeGroups: function (groupIDs) { + groupIDs.forEach(function (groupID) { + this._removeGroup(groupID); + }, this); + this._redraw(); + return this; + }, + + removeGroup: function (groupID) { + this._removeGroup(groupID); + this._redraw(); + return this; + }, + + _removeGroup: function (groupID) { + groupID = groupID.toString(); + if (!this._groupIDs[groupID]) { return; } + delete this._groupIDs[groupID]; + this._latlngsIdx.all().filter(function (marker) { + return marker._canvasGroupID === groupID; + }).forEach(function (el) { + this.removeMarker(el, false, true); + }, this); + }, + + removeMarker: function (marker, redraw, hasLayer) { + if (!hasLayer && !this.hasLayer(marker)) { return; } + this._latlngsIdx.remove(marker); + + if (redraw && this._map && + this._map.getBounds().pad(this.options.padding).contains(marker.getLatLng())) { + this._redraw(); + } + marker.removeEventParent(this); + return this; + }, + + removeLayer: function (layer) { + return this.removeMarker(layer, true); + }, + /* + removeLayers: function (layers) { + layers.forEach(function (el) { + this.removeMarker(el, false); + }, this); + this._redraw(); + return this; + }, + */ + clearLayers: function () { + this._latlngsIdx.clear(); + this._pointsIdx.clear(); + this._clear(); + return this; + }, + + hasLayer: function (layer) { + // return this._latlngsIdx.all().indexOf(layer) !== -1; + return layer._eventParents[L.Util.stamp(this)]; // !! to cut corners + }, + + _hideContainer: function (hide) { + if (this._isEmpty === hide) { return; } + this._isEmpty = hide; + this._container.style.display = hide ? 'none' : 'initial'; + } + }); + + L.canvasIconLayer = function (options) { + return new CanvasIconLayer(options); + }; + + return CanvasIconLayer; +} + +module.exports = layerFactory; diff --git a/js/leaflet.js b/js/leaflet.js new file mode 100644 index 0000000..21f499c --- /dev/null +++ b/js/leaflet.js @@ -0,0 +1,6 @@ +/* @preserve + * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";function h(t){for(var i,e,n=1,o=arguments.length;n=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=O(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=O(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=N(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=N(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lati.lng&&n.lng';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}();function kt(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var Bt={ie:tt,ielt9:it,edge:et,webkit:nt,android:ot,android23:st,androidStock:at,opera:ht,chrome:ut,gecko:lt,safari:ct,phantom:_t,opera12:dt,win:pt,ie3d:mt,webkit3d:ft,gecko3d:gt,any3d:vt,mobile:yt,mobileWebkit:xt,mobileWebkit3d:wt,msPointer:Pt,pointer:Lt,touch:bt,mobileOpera:Tt,mobileGecko:Mt,retina:zt,passiveEvents:Ct,canvas:St,svg:Zt,vml:Et},At=Pt?"MSPointerDown":"pointerdown",It=Pt?"MSPointerMove":"pointermove",Ot=Pt?"MSPointerUp":"pointerup",Rt=Pt?"MSPointerCancel":"pointercancel",Nt={},Dt=!1;function jt(t,i,e,n){function o(t){Ut(t,r)}var s,r,a,h,u,l,c,_;function d(t){t.pointerType===(t.MSPOINTER_TYPE_MOUSE||"mouse")&&0===t.buttons||Ut(t,h)}return"touchstart"===i?(u=t,l=e,c=n,_=p(function(t){t.MSPOINTER_TYPE_TOUCH&&t.pointerType===t.MSPOINTER_TYPE_TOUCH&&Ri(t),Ut(t,l)}),u["_leaflet_touchstart"+c]=_,u.addEventListener(At,_,!1),Dt||(document.addEventListener(At,Wt,!0),document.addEventListener(It,Ht,!0),document.addEventListener(Ot,Ft,!0),document.addEventListener(Rt,Ft,!0),Dt=!0)):"touchmove"===i?(h=e,(a=t)["_leaflet_touchmove"+n]=d,a.addEventListener(It,d,!1)):"touchend"===i&&(r=e,(s=t)["_leaflet_touchend"+n]=o,s.addEventListener(Ot,o,!1),s.addEventListener(Rt,o,!1)),this}function Wt(t){Nt[t.pointerId]=t}function Ht(t){Nt[t.pointerId]&&(Nt[t.pointerId]=t)}function Ft(t){delete Nt[t.pointerId]}function Ut(t,i){for(var e in t.touches=[],Nt)t.touches.push(Nt[e]);t.changedTouches=[t],i(t)}var Vt=Pt?"MSPointerDown":Lt?"pointerdown":"touchstart",qt=Pt?"MSPointerUp":Lt?"pointerup":"touchend",Gt="_leaflet_";var Kt,Yt,Xt,Jt,$t,Qt,ti=fi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ii=fi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),ei="webkitTransition"===ii||"OTransition"===ii?ii+"End":"transitionend";function ni(t){return"string"==typeof t?document.getElementById(t):t}function oi(t,i){var e,n=t.style[i]||t.currentStyle&&t.currentStyle[i];return n&&"auto"!==n||!document.defaultView||(n=(e=document.defaultView.getComputedStyle(t,null))?e[i]:null),"auto"===n?null:n}function si(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function ri(t){var i=t.parentNode;i&&i.removeChild(t)}function ai(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function hi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function ui(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function li(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=pi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,N(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e,n,o=A((i=i||{}).paddingTopLeft||i.padding||[0,0]),s=A(i.paddingBottomRight||i.padding||[0,0]),r=this.getCenter(),a=this.project(r),h=this.project(t),u=this.getPixelBounds(),l=u.getSize().divideBy(2),c=O([u.min.add(o),u.max.subtract(s)]);return c.contains(h)||(this._enforcingBounds=!0,e=a.subtract(h),n=A(h.x+e.x,h.y+e.y),(h.xc.max.x)&&(n.x=a.x-e.x,0c.max.y)&&(n.y=a.y-e.y,0=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[m(s)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Vi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n.length||r||o||!Vi(s,t)||(n=[this]),n},_handleDOMEvent:function(t){var i;this._loaded&&!Ui(t)&&("mousedown"!==(i=t.type)&&"keypress"!==i&&"keyup"!==i&&"keydown"!==i||Pi(t.target||t.srcElement),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){var n;if("click"===t.type&&((n=h({},t)).type="preclick",this._fireDOMEvent(n,n.type,e)),!t._stopped&&(e=(e||[]).concat(this._findEventTargets(t,i))).length){var o=e[0];"contextmenu"===i&&o.listens(i,!0)&&Ri(t);var s,r={originalEvent:t};"keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type&&(s=o.getLatLng&&(!o._radius||o._radius<=10),r.containerPoint=s?this.latLngToContainerPoint(o.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=s?o.getLatLng():this.layerPointToLatLng(r.layerPoint));for(var a=0;athis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(M(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,ci(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),setTimeout(p(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&_i(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),M(function(){this._moveEnd(!0)},this))}});function Yi(t){return new Xi(t)}var Xi=S.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return ci(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(ri(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+m(this),n),this._layerControlInputs.push(i),i.layerId=m(t.layer),zi(i,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var s=document.createElement("div");return e.appendChild(s),s.appendChild(i),s.appendChild(o),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),$i=Xi.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=si("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=si("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),Oi(s),zi(s,"click",Ni),zi(s,"click",o,this),zi(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";_i(this._zoomInButton,i),_i(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMinZoom()||ci(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMaxZoom()||ci(this._zoomInButton,i)}});Ki.mergeOptions({zoomControl:!0}),Ki.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new $i,this.addControl(this.zoomControl))});var Qi=Xi.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=si("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=si("div",i,e)),t.imperial&&(this._iScale=si("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;5280Leaflet'},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=si("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});Ki.mergeOptions({attributionControl:!0}),Ki.addInitHook(function(){this.options.attributionControl&&(new te).addTo(this)});Xi.Layers=Ji,Xi.Zoom=$i,Xi.Scale=Qi,Xi.Attribution=te,Yi.layers=function(t,i,e){return new Ji(t,i,e)},Yi.zoom=function(t){return new $i(t)},Yi.scale=function(t){return new Qi(t)},Yi.attribution=function(t){return new te(t)};var ie=S.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}});ie.addTo=function(t,i){return t.addHandler(i,this),this};var ee,ne={Events:Z},oe=bt?"touchstart mousedown":"mousedown",se={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},re={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},ae=E.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(zi(this._dragStartTarget,oe,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(ae._dragging===this&&this.finishDrag(),Si(this._dragStartTarget,oe,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;!t._simulated&&this._enabled&&(this._moved=!1,li(this._element,"leaflet-zoom-anim")||ae._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((ae._dragging=this)._preventOutline&&Pi(this._element),xi(),Xt(),this._moving||(this.fire("down"),i=t.touches?t.touches[0]:t,e=bi(this._element),this._startPoint=new k(i.clientX,i.clientY),this._parentScale=Ti(e),zi(document,re[t.type],this._onMove,this),zi(document,se[t.type],this._onUp,this))))},_onMove:function(t){var i,e;!t._simulated&&this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function de(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||Oe.prototype._containsPoint.call(this,t,!0)}});var Ne=Ce.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=g(t)?t:t.features;if(o){for(i=0,e=o.length;iu.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c]))},_onCloseButtonClick:function(t){this._close(),Ni(t)},_getAnchor:function(){return A(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});Ki.mergeOptions({closePopupOnClick:!0}),Ki.include({openPopup:function(t,i,e){return t instanceof tn||(t=new tn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Me.include({bindPopup:function(t,i){return t instanceof tn?(c(t,i),(this._popup=t)._source=this):(this._popup&&!i||(this._popup=new tn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){return this._popup&&this._map&&(i=this._popup._prepareOpen(this,t,i),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Ni(t),i instanceof Be?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var en=Qe.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){Qe.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){Qe.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=Qe.prototype.getEvents.call(this);return bt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=si("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),s=e.layerPointToContainerPoint(t),r=this.options.direction,a=n.offsetWidth,h=n.offsetHeight,u=A(this.options.offset),l=this._getAnchor(),c="top"===r?(i=a/2,h):"bottom"===r?(i=a/2,0):(i="center"===r?a/2:"right"===r?0:"left"===r?a:s.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return N(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new R(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new k(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(ri(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){ci(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=a,t.onmousemove=a,it&&this.options.opacity<1&&mi(t,this.options.opacity),ot&&!st&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),p(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&M(p(this._tileReady,this,t,null,o)),vi(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(mi(e.el,0),z(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(ci(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),it||!this._map._fadeAnimated?M(this._pruneTiles,this):setTimeout(p(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new k(this._wrapX?o(t.x,this._wrapX):t.x,this._wrapY?o(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new I(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var sn=on.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&zt&&0')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_n={_initContainer:function(){this._container=si("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(hn.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=cn("shape");ci(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=cn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[m(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;ri(i),t.removeInteractiveTarget(i),delete this._layers[m(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=cn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=g(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=cn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){hi(t._container)},_bringToBack:function(t){ui(t._container)}},dn=Et?cn:J,pn=hn.extend({getEvents:function(){var t=hn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=dn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=dn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){ri(this._container),Si(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(hn.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),vi(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=dn("path");t.options.className&&ci(i,t.options.className),t.options.interactive&&ci(i,"leaflet-interactive"),this._updateStyle(t),this._layers[m(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){ri(t._path),t.removeInteractiveTarget(t._path),delete this._layers[m(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,$(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){hi(t._path)},_bringToBack:function(t){ui(t._path)}});function mn(t){return Zt||Et?new pn(t):null}Et&&pn.include(_n),Ki.include({getRenderer:function(t){var i=(i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&ln(t)||mn(t)}});var fn=Re.extend({initialize:function(t,i){Re.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=N(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});pn.create=dn,pn.pointsToPath=$,Ne.geometryToLayer=De,Ne.coordsToLatLng=We,Ne.coordsToLatLngs=He,Ne.latLngToCoords=Fe,Ne.latLngsToCoords=Ue,Ne.getFeature=Ve,Ne.asFeature=qe,Ki.mergeOptions({boxZoom:!0});var gn=ie.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){zi(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){Si(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){ri(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),Xt(),xi(),this._startPoint=this._map.mouseEventToContainerPoint(t),zi(document,{contextmenu:Ni,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=si("div","leaflet-zoom-box",this._container),ci(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new I(this._point,this._startPoint),e=i.getSize();vi(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(ri(this._box),_i(this._container,"leaflet-crosshair")),Jt(),wi(),Si(document,{contextmenu:Ni,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){var i;1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(p(this._resetState,this),0),i=new R(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})))},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});Ki.addInitHook("addHandler","boxZoom",gn),Ki.mergeOptions({doubleClickZoom:!0});var vn=ie.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});Ki.addInitHook("addHandler","doubleClickZoom",vn),Ki.mergeOptions({dragging:!0,inertia:!st,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var yn=ie.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new ae(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),ci(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){_i(this._map._container,"leaflet-grab"),_i(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=N(this._map.options.maxBounds),this._offsetLimit=O(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)i.getMaxZoom()&&1= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } + + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + }, + + // split overflowed node into two + _split: function (insertPath, level) { + + var node = insertPath[level], + M = node.children.length, + m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + var splitIndex = this._chooseSplitIndex(node, m, M); + + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; + + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + }, + + _splitRoot: function (node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + }, + + _chooseSplitIndex: function (node, m, M) { + + var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; + + minOverlap = minArea = Infinity; + + for (i = m; i <= M - m; i++) { + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); + + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); + + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + + minArea = area < minArea ? area : minArea; + + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; + } + } + } + + return index; + }, + + // sorts node children by the best axis for split + _chooseSplitAxis: function (node, m, M) { + + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, + xMargin = this._allDistMargin(node, m, M, compareMinX), + yMargin = this._allDistMargin(node, m, M, compareMinY); + + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + }, + + // total margin of all possible split distributions where each node is at least m full + _allDistMargin: function (node, m, M, compare) { + + node.children.sort(compare); + + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), + i, child; + + for (i = m; i < M - m; i++) { + child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (i = M - m - 1; i >= m; i--) { + child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + }, + + _adjustParentBBoxes: function (bbox, path, level) { + // adjust bboxes along the given tree path + for (var i = level; i >= 0; i--) { + extend(path[i], bbox); + } + }, + + _condense: function (path) { + // go through the path, removing empty nodes and updating bboxes + for (var i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + + } else this.clear(); + + } else calcBBox(path[i], this.toBBox); + } + }, + + _initFormat: function (format) { + // data format (minX, minY, maxX, maxY accessors) + + // uses eval-type function compilation instead of just accepting a toBBox function + // because the algorithms are very sensitive to sorting functions performance, + // so they should be dead simple and without inner calls + + var compareArr = ['return a', ' - b', ';']; + + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + + this.toBBox = new Function('a', + 'return {minX: a' + format[0] + + ', minY: a' + format[1] + + ', maxX: a' + format[2] + + ', maxY: a' + format[3] + '};'); + } +}; + +function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (var i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; +} + +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); +} + +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; +} + +function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; +} + +function compareNodeMinX(a, b) { return a.minX - b.minX; } +function compareNodeMinY(a, b) { return a.minY - b.minY; } + +function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } +function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } + +function enlargedArea(a, b) { + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); +} + +function intersectionArea(a, b) { + var minX = Math.max(a.minX, b.minX), + minY = Math.max(a.minY, b.minY), + maxX = Math.min(a.maxX, b.maxX), + maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} + +function contains(a, b) { + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; +} + +function intersects(a, b) { + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; +} + +function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; +} + +// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +// combines selection algorithm with binary divide & conquer approach + +function multiSelect(arr, left, right, n, compare) { + var stack = [left, right], + mid; + + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + + if (right - left <= n) continue; + + mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); + + stack.push(left, mid, mid, right); + } +} + +},{"quickselect":2}],2:[function(require,module,exports){ +'use strict'; + +module.exports = quickselect; +module.exports.default = quickselect; + +function quickselect(arr, k, left, right, compare) { + quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare); +}; + +function quickselectStep(arr, k, left, right, compare) { + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + quickselectStep(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; +} + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/js/tracker.js b/js/tracker.js index c2d1afc..26eeb9a 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -20,6 +20,11 @@ var receivers = []; var recovery_names = []; var recoveries = []; +var launches = null; +var receiverCanvas = null; + +var sondePrefix = ["RS92", "RS92-SGP", "RS92-NGP", "RS41", "RS41-SG", "RS41-SGP", "RS41-SGM", "DFM", "DFM06", "DFM09", "DFM17", "M10", "M20", "iMet-4", "iMet-54", "LMS6", "LMS6-400", "LMS6-1680", "iMS-100", "MRZ", "chase"]; + var got_positions = false; var zoomed_in = false; var max_positions = 0; // maximum number of positions that ajax request should return (0 means no maximum) @@ -43,13 +48,16 @@ var layer_clouds = null; var notamOverlay = null; +var svgRenderer = L.svg(); + var modeList = [ // "Position", "1 hour", "3 hours", "6 hours", "12 hours", - "1 day" + "1 day", + "3 days" ]; var modeDefault = "3 hours"; var modeDefaultMobile = "1 hour"; @@ -112,41 +120,23 @@ var plot_options = { ] }; -// aprs overlay -var overlayAPRS = new google.maps.ImageMapType({ - getTileUrl: function(coord, zoom) { - var n = Math.pow(2,zoom); - return (coord.y<0 || coord.y>=n || zoom > 6) ? null : "http://" + - ['a','b','c'][Math.abs(coord.x+coord.y)%3] + - ".tiles.tracker.habhub.org/aprs/tile_" + - zoom + "_" + - wrapTiles(coord.x,zoom) + "_" + - coord.y + ".png"; - }, - tileSize: new google.maps.Size(256,256) +// aprs overlay (not used) +var overlayARPS = new L.tileLayer('http://{s}.tiles.tracker.habhub.org/aprs/tile_{z}_{x}_{y}.png', { + subdomains: 'abc', + maxZoom: 6, + attribution: '© HabHub' }); -// weather -var weatherOverlayId = "nexrad-n0q-900913"; -var weatherOverlay = new google.maps.ImageMapType({ - getTileUrl: function(tile, zoom) { - return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/"+weatherOverlayId+"/" + zoom + "/" + tile.x + "/" + tile.y +".png"; - }, - tileSize: new google.maps.Size(256, 256), - opacity:0.8, - isPng: true +//Global Precipitation Weather +var RainRadar = new L.tileLayer('https://tilecache.rainviewer.com/v2/radar/' + (Math.floor(new Date().getTime() / 600000) * 600) + '/512/{z}/{x}/{y}/1/1_0.png', { + opacity: 0.6, + attribution: '© RainViewer sources' }); -var weatherImageOverlayList = { - 'nrl-global-cloudtop': ['http://www.nrlmry.navy.mil/archdat/global/stitched/cloudtop/LATEST.jpg', [[-76, -179.9999], [76, 179.75]]], - 'nrl-global-ir': ['http://www.nrlmry.navy.mil/archdat/global/stitched/ir/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]], - 'nrl-global-vapor': ['http://www.nrlmry.navy.mil/archdat/global/stitched/vapor/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]], - 'meteosat-Odeg-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/MET0D/GE_MET0D_VP-MPE.png', [[-57.492200, -57.492200], [57.492200, 57.492200]]], - 'meteosat-iodc-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/IODC/GE_IODC_VP-MPE.png', [[-59.7901, -2.7919], [59.7901, 116.7913]]] -}; - -var weatherImageOverlay = new google.maps.GroundOverlay(); -var weatherGoogleRadar = new google.maps.KmlLayer({url:'http://mw1.google.com/mw-weather/radar/root.kmz', preserveViewport: true}); +var RainRadarCoverage = new L.tileLayer('https://tilecache.rainviewer.com/v2/coverage/0/512/{z}/{x}/{y}/0/0_0.png', { + opacity: 0.6, + attribution: '© RainViewer' +}); var offline = { get: function(key) { @@ -250,63 +240,65 @@ function makeQuad(x, y, zoom) { return quad; } -// wraps tiles horizontally, returns x -function wrapTiles(x, zoom) { - var n = Math.pow(2,zoom); - return (x<0) ? (n+(x%n))%n : x%n; -} - // map type list -// format: [ name, attr, minZoom, maxZoom, getTileUrl function ] -var maptypes = { - bing_os: [ - 'Ord. Survey', - 'Bing.com & Ordnance Survey', - 10, - 17, - function(xy,z) { return 'http://ecn.t'+((Math.abs(xy.x+xy.y)%3)+1)+'.tiles.virtualearth.net/tiles/r'+makeQuad(xy.x, xy.y, z)+'?g=3483&productSet=mmOS&key=AhN7I60Jff9-gQEnDk6CORUyr66zjb5LFc0zS0KPsEIfaDRAVVIeDvk1H6jUx25l'; } - ], - osm: [ - 'OSM', - 'OpenStreetMaps.org', - 1, - 19, - function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.tile.openstreetmap.org/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; } - ], - dark_matter: [ - 'Dark Matter', - '© OpenStreetMap contributors, © CartoDB', - 1, - 19, - function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.basemaps.cartocdn.com/dark_all/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; } - ], - osm_toner: [ - 'Toner', - 'Stamen.org Toner', - 1, - 18, - function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/toner/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; } - ], - osm_watercolor: [ - 'Watercolor', - 'Stamen.org Watercolor', - 1, - 18, - function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/watercolor/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; } - ], - getlost: [ - 'GetLost', - '© Getlost Maps', - 2, - 16, - function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://live.getlost.com.au/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.jpg'; } - ], -}; -// generate a list of names for the UI -var maptype_ids = ["roadmap","satellite","terrain"]; -for(var i in maptypes) maptype_ids.push(i); +var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap contributors' +}); +osm.id="Mapnik"; +var dark_matter = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap contributors, © CartoDB', + minZoom: 1, + maxZoom: 19, +}); +dark_matter.id="DarkMatter"; + +var worldimagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { + attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' +}); +worldimagery.id="WorldImagery"; + +var stamen_terrain = L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}{r}.png', { + attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors', + subdomains: 'abcd', + minZoom: 0, + maxZoom: 18, +}); +stamen_terrain.id="Terrain"; + +var cartodb_voyager = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap contributors © CARTO', + subdomains: 'abcd', + maxZoom: 19 +}); +cartodb_voyager.id="Voyager"; + +var opentopomap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', { + maxZoom: 17, + attribution: 'Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap (CC-BY-SA)' +}); +opentopomap.id = "OpenTopoMap"; + +//not used +var getlost = L.tileLayer('https://live.getlost.com.au/{z}/{x}/{y}.jpg', { + attribution: '© Getlost Maps', + minZoom: 4, + maxZoom: 16, +}); +getlost.id = "GetLost"; + +var baseMaps = { + "Mapnik": osm, + "DarkMatter": dark_matter, + "WorldImagery": worldimagery, + "Terrain": stamen_terrain, + "Voyager": cartodb_voyager, + "OpenTopoMap": opentopomap, +} + +var selectedLayer = "Mapnik"; // mousemove event throttle hack for smoother maps pan on firefox and IE // taken from: http://stackoverflow.com/questions/22306130/how-to-limit-google-maps-api-lag-when-panning-the-map-with-lots-of-markers-and-p @@ -343,30 +335,28 @@ function clean_refresh(text, force, history_step) { history_step = !!history_step; if(text == wvar.mode && !force) return false; - if(ajax_inprogress) return false; - stopAjax(); // reset mode if, invalid mode is specified if(modeList.indexOf(text) == -1) text = (is_mobile) ? modeDefaultMobile : modeDefault; wvar.mode = text; - tmpC.select(text); + document.getElementById("timeperiod").value = text; + document.getElementById("timeperiod").disabled = true; position_id = 0; - mapInfoBox.close(); + map.removeLayer(mapInfoBox); // clear vehicles var callsign; for(callsign in vehicles) { - removePrediction(callsign); vehicles[callsign].kill(); } // clear hysplit for(callsign in hysplit) { - hysplit[callsign].setMap(null); + map.removeLayer(hysplit[callsign]); } car_index = 0; @@ -385,6 +375,7 @@ function clean_refresh(text, force, history_step) { lhash_update(history_step); clearTimeout(periodical); + clearTimeout(periodical_focus); clearTimeout(periodical_receivers); clearTimeout(periodical_recoveries); @@ -393,115 +384,144 @@ function clean_refresh(text, force, history_step) { return true; } -var tmpC; - function load() { //initialize map object - map = new google.maps.Map(document.getElementById('map'), { + map = new L.map(document.getElementById('map'), { zoom: 5, - center: new google.maps.LatLng(53.467511,-2.2338940), - mapTypeId: google.maps.MapTypeId.ROADMAP, - mapTypeControlOptions: { - mapTypeIds: maptype_ids, - style: google.maps.MapTypeControlStyle.DROPDOWN_MENU - }, - gestureHandling: 'greedy', - keyboardShortcuts: false, - streetViewControl: false, - rotateControl: false, - panControl: false, - scaleControl: true, - zoomControl: true, - zoomControlOptions: { - style: google.maps.ZoomControlStyle.DEFAULT - }, - fullscreenControl: true, - fullscreenControlOptions: { - position: google.maps.ControlPosition.LEFT_BOTTOM - }, - scrollwheel: true + zoomControl: false, + center: [53.467511,-2.2338940], + layers: [osm], + preferCanvas: true, }); - if(window.performance && window.performance.now && window.navigator.userAgent.indexOf("Firefox") != -1) { - document.getElementById('map').addEventListener("mousemove", throttle_events, true); - } + // fullscreen button + map.addControl(new L.Control.Fullscreen({ position: 'bottomleft' })); - // register custom map types - for(var i in maptypes) { - map.mapTypes.set(i, new google.maps.ImageMapType({ - name: maptypes[i][0], - minZoom: maptypes[i][2], - maxZoom: maptypes[i][3], - getTileUrl: maptypes[i][4], - tileSize: new google.maps.Size(256, 256), - })); - } + // scale (would be better if integrated into attirbution bar) + L.control.scale({position:'bottomright', imperial:false}).addTo(map); - var customTileAttr = new google.maps.StatusTextControl({ - text: '', - map: map, - position: google.maps.ControlPosition.BOTTOM_RIGHT, - }); + // zoom controls + new L.Control.Zoom({ position: 'bottomright' }).addTo(map); + + // map selector + layers = L.control.layers(baseMaps, null, {position: "topleft"}).addTo(map); - google.maps.event.addListener(map, 'maptypeid_changed', function() { - var id = this.getMapTypeId(); - customTileAttr.setText((id in maptypes) ? maptypes[id][1] : ''); + // update time div + L.Control.Status = L.Control.extend({ + onAdd: function(map) { + var div = L.DomUtil.create('div'); + + div.innerHTML = " Updated: never"; + div.style = "opacity: 0.7; background-color: rgb(245, 245, 245); padding-right: 6px; padding-left: 6px; font-family: Roboto, Arial, sans-serif; color: rgb(68, 68, 68);"; + + return div; + }, + + onRemove: function(map) { + // Nothing to do here + } }); + + L.control.status = function(opts) { + return new L.Control.Status(opts); + } + + L.control.status({ position: 'bottomright' }).addTo(map); // update current position if we geolocation is available if(currentPosition) updateCurrentPosition(currentPosition.lat, currentPosition.lon); + //Receiver canvas + receiverCanvas = new L.canvasIconLayer(); + receiverCanvas.addTo(map); + // initalize nite overlay - nite.init(map); - if(!offline.get('opt_daylight')) nite.hide(); - setInterval(function() { nite.refresh(); }, 30000); // 30s - - // we need a dummy overlay to access getProjection() - overlay = new google.maps.OverlayView(); - overlay.draw = function() {}; - overlay.setMap(map); - - // status message boxes - var statusElm = new google.maps.StatusTextControl({ - map: map, - position: google.maps.ControlPosition.RIGHT_BOTTOM, - text: " Updated: never" + nite = new L.terminator({ + renderer: svgRenderer, + interactive: false, }); + if (offline.get("opt_daylight")) { + map.addLayer(nite); + } - google.maps.event.addListener(map, 'zoom_changed', function() { + if (offline.get("opt_layers_launches")) { + showLaunchSites(); + map.addLayer(launches); + } + + map.on('baselayerchange', function (e) { + selectedLayer = e.layer.id; + }); + + map.on('zoomend', function() { + //do check for horizon labels + if (!offline.get("opt_hide_horizon")) { + for (key in vehicles) { + if (vehicles[key]["vehicle_type"] == "balloon") { + if (vehicles[key]["horizon_circle"]["_map"]) + { + try { + var zoom = map.getZoom(); + var horizonzoom = (Math.abs(Math.log(vehicles[key]["horizon_circle"].getRadius()/2000000)/0.75)); + var subhorizonzoom = (Math.abs(Math.log(vehicles[key]["subhorizon_circle"].getRadius()/2000000)/0.75)); + if (horizonzoom > zoom) { + map.removeLayer(vehicles[key]["horizon_circle_title"]); + } else { + map.addLayer(vehicles[key]["horizon_circle_title"]); + } + if (subhorizonzoom > zoom) { + map.removeLayer(vehicles[key]["subhorizon_circle_title"]); + } else { + map.addLayer(vehicles[key]["subhorizon_circle_title"]); + } + } catch(e){}; + } + } + } + } updateZoom(); }); - google.maps.event.addListener(map, 'dragstart', function() { + map.on('movestart', function() { if(!wvar.embeded) manual_pan = true; }); // only start population the map, once its completely loaded - google.maps.event.addListenerOnce(map, 'idle', function(){ + var callBack = function() { load_hash(null); - // initialize period menu - tmpC = new google.maps.DropDownControl({ - map: map, - title: "Show activity for given period", - //position: google.maps.ControlPosition.TOP_RIGHT, - position: google.maps.ControlPosition.LEFT_TOP, - headerPrefix: "Last: ", - list: modeList, - listDefault: modeList.indexOf(wvar.mode), - callback: clean_refresh, + L.Control.PeriodControl = L.Control.extend({ + onAdd: function(map) { + var div = L.DomUtil.create('div'); + + div.innerHTML = ''; + + return div; + }, + + onRemove: function(map) { + // Nothing to do here + } }); - google.maps.event.addListener(map, 'idle', function() { + L.control.periodcontrol = function(opts) { + return new L.Control.PeriodControl(opts); + } + + L.control.periodcontrol({ position: 'topleft' }).addTo(map); + + map.on('idle', function() { lhash_update(); }); - google.maps.event.addListener(map, 'maptypeid_changed', function() { + map.on('baselayerchange', function() { lhash_update(); }); startAjax(); - }); + }; + + map.whenReady(callBack); // animate-in the timebox, setTimeout(function() { @@ -539,9 +559,59 @@ function load() { if(!is_mobile && !offline.get('opt_nowelcome') && $(window).width() > 900) $('.nav li.about').click(); }, 500); +} + +function showLaunchSites() { + if (!launches) { + launches = new L.layerGroup([], {attribution: "© rs1729"}); + $.getJSON("launchSites.json", function(json) { + for (var key in json) { + if (json.hasOwnProperty(key)) { + var latlon = [json[key].lat, json[key].lon]; + var sondes = json[key].rs_types.toString(); + sondes = sondes.replace(new RegExp("\\b07\\b"), "iMet-1 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b11\\b"), "LMS6-403 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b13\\b"), "RS92 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b14\\b"), "RS92 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b17\\b"), "DFM-09 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b19\\b"), "MRZ-N1 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b21\\b"), "RS-11G (possible to track)"); + sondes = sondes.replace(new RegExp("\\b22\\b"), "RS-11G (possible to track)"); + sondes = sondes.replace(new RegExp("\\b23\\b"), "RS41 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b24\\b"), "RS41 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b34\\b"), "iMet-4 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b35\\b"), "iMS-100 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b41\\b"), "RS41 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b42\\b"), "RS41 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b52\\b"), "RS92-NGP (possible to track)"); + sondes = sondes.replace(new RegExp("\\b54\\b"), "DFM-17 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b62\\b"), "MRZ-3MK (possible to track)"); + sondes = sondes.replace(new RegExp("\\b63\\b"), "M20 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b77\\b"), "M10 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b82\\b"), "LMS6-1680 (possible to track)"); + sondes = sondes.replace(new RegExp("\\b84\\b"), "iMet-54 (possible to track)"); + var marker = new L.circleMarker(latlon, {color: 'black', radius: 8}); + var popup = new L.popup({ autoClose: false, closeOnClick: false }).setContent("" + json[key].station_name + "

Sondes launched: " + sondes); + marker.bindPopup(popup); + launches.addLayer(marker); + } + } + }); + } +} - // load if aprs layer, if selected - if(offline.get('opt_layers_aprs')) map.overlayMapTypes.setAt("1", overlayAPRS); +function shareVehicle(callsign) { + const shareData = { + title: 'SondeHub: ' + vehicles[callsign].marker.options.title + ' Flight Information', + text: 'You can view the flight path and sensor data for ' + vehicles[callsign].marker.options.title + ' on the SondeHub tracker!', + url: window.location.origin + '/' + callsign, + } + try { + navigator.share(shareData); + } catch (e) { + console.log("Error sharing: " + e); + } + } function panTo(vcallsign) { @@ -551,9 +621,27 @@ function panTo(vcallsign) { update_lookangles(vcallsign); // pan map - //if(vehicles[vcallsign].marker_shadow) map.panTo(vehicles[vcallsign].marker_shadow.getPosition()); - //else map.panTo(vehicles[vcallsign].marker.getPosition()); - map.panTo(vehicles[vcallsign].marker.getPosition()); + if (map.getZoom() > 10) { + map.setView(vehicles[vcallsign].marker.getLatLng()); + } else { + map.setView(vehicles[vcallsign].marker.getLatLng(), 10); + } +} + +function panToRecovery(rcallsign) { + if(offline.get('opt_hide_recoveries')) alert("Recovered Sonde Hidden, enable in settings"); + //if mobile close panel + if (is_mobile) { + $('.flatpage, #homebox').hide(); + } + for (let i = 0; i < recoveries.length; i++) { + if (recoveries[i].hasOwnProperty('serial')) { + if (recoveries[i]['serial'] == rcallsign) { + //pan map + map.setView(recoveries[i]['marker'].getLatLng(), 10); + } + } + } } function title_case(s) { @@ -711,7 +799,7 @@ function updateAltitude(vcallsign) { var position = vehicle.curr_position; if(vehicle.marker.mode == 'landed') { - vehicle.marker.setPosition(vehicle.marker.getPosition()); + vehicle.marker.setLatLng(vehicle.marker.getLatLng()); return; } @@ -724,7 +812,6 @@ function updateAltitude(vcallsign) { } else if(position.gps_alt > 55000) { position.gps_alt = 55000; } - vehicle.marker.setAltitude(pixel_altitude); } function updateZoom() { @@ -734,11 +821,11 @@ function updateZoom() { if(vehicle.vehicle_type == "balloon") { updateAltitude(vcallsign); } else { - vehicle.marker.setPosition(vehicle.marker.getPosition()); + vehicle.marker.setLatLng(vehicle.marker.getLatLng()); } if(vehicle.marker_shadow) - vehicle.marker_shadow.setPosition(vehicle.marker_shadow.getPosition()); + vehicle.marker_shadow.setLatLng(vehicle.marker_shadow.getLatLng()); } } @@ -746,7 +833,7 @@ var los_polylines = []; function drawLOSPaths(vcallsign) { los_polylines.forEach(function(polyline) { - polyline.setMap(null); + polyline.remove(map); }); los_polylines = []; @@ -771,24 +858,20 @@ function drawLOSPaths(vcallsign) { if(r_index === -1) return; var path = [ - vehicle.marker_shadow.getPosition(), - receivers[r_index].marker.getPosition(), + vehicle.marker_shadow.getLatLng(), + receivers[r_index].marker.getLatLng(), ]; - var p = new google.maps.Polyline({ - map: map, - path: path, - zIndex: Z_PATH, - strokeColor: '#0F0', - strokeOpacity: 0.8, - strokeWeight: 3, - clickable: true, - draggable: false, - geodesic: true - }); - p.path_length = google.maps.geometry.spherical.computeDistanceBetween(path[0], path[1]); + var p = new L.Polyline(path, { + color: '#0F0', + opacity: 0.8, + weight: 3, + }).addTo(map); + p.path_length = path[0].distanceTo(path[1]); los_polylines.push(p); - google.maps.event.addListener(p, 'click', mapInfoBox_handle_prediction_path); + p.on('click', function (e) { + mapInfoBox_handle_prediction_path(e); + }); }); } @@ -801,15 +884,21 @@ function focusVehicle(vcallsign, ignoreOpt) { for(var i in vehicles) { var vehicle = vehicles[i], j; - if(i == vcallsign || vcallsign === null) { - if(vehicle.horizon_circle) vehicle.horizon_circle.setOptions({zIndex:Z_RANGE,strokeOpacity:opacityFocused * 0.6}); - if(vehicle.subhorizon_circle) vehicle.subhorizon_circle.setOptions({zIndex:Z_RANGE,strokeOpacity:opacityFocused * 0.8}); - for(j in vehicle.polyline) vehicle.polyline[j].setOptions({zIndex:Z_PATH-j,strokeOpacity:opacityFocused}); - } - else { - if(vehicle.horizon_circle) vehicle.horizon_circle.setOptions({zIndex:1,strokeOpacity:opacityOther * 0.6}); - if(vehicle.subhorizon_circle) vehicle.subhorizon_circle.setOptions({zIndex:1,strokeOpacity:opacityOther * 0.8}); - for(j in vehicle.polyline) vehicle.polyline[j].setOptions({zIndex:1,strokeOpacity:opacityOther}); + if (vehicle.vehicle_type == "balloon") { + if(i == vcallsign || vcallsign === null) { + if(vehicle.horizon_circle) vehicle.horizon_circle.setStyle({opacity:opacityFocused * 0.6}); + if(vehicle.horizon_circle_title) vehicle.horizon_circle_title.setOpacity(opacityFocused * 0.8); + if(vehicle.subhorizon_circle) vehicle.subhorizon_circle.setStyle({opacity:opacityFocused * 0.8}); + if(vehicle.subhorizon_circle_title) vehicle.subhorizon_circle_title.setOpacity(opacityFocused * 0.8); + for(j in vehicle.polyline) vehicle.polyline[j].setStyle({opacity:opacityFocused}); + } + else { + if(vehicle.horizon_circle) vehicle.horizon_circle.setStyle({opacity:opacityOther * 0.6}); + if(vehicle.horizon_circle_title) vehicle.horizon_circle_title.setOpacity(opacityOther * 0.6); + if(vehicle.subhorizon_circle) vehicle.subhorizon_circle.setStyle({opacity:opacityOther * 0.8}); + if(vehicle.subhorizon_circle_title) vehicle.subhorizon_circle_title.setOpacity(opacityOther * 0.8); + for(j in vehicle.polyline) vehicle.polyline[j].setStyle({opacity:opacityOther}); + } } } } @@ -830,6 +919,9 @@ function stopFollow(no_data_reset) { wvar.focus = ""; } + //stop detailed data + clearTimeout(periodical_focus); + // clear graph if(plot) plot = $.plot(plot_holder, {}, plot_options); updateGraph(null, true); @@ -859,6 +951,8 @@ function followVehicle(vcallsign, noPan, force) { } if(follow_vehicle != vcallsign || force) { + clearTimeout(periodical_focus); + refreshSingle(vcallsign, true); focusVehicle(vcallsign); follow_vehicle = vcallsign; @@ -915,7 +1009,9 @@ function formatDate(date,utc) { function updateVehicleInfo(vcallsign, newPosition) { var vehicle = vehicles[vcallsign]; - var latlng = new google.maps.LatLng(newPosition.gps_lat, newPosition.gps_lon); + if (!isNaN(newPosition.gps_lat) && !isNaN(newPosition.gps_lon)){ + var latlng = new L.LatLng(newPosition.gps_lat, newPosition.gps_lon); + } // update market z-index based on latitude, 90 being background and -90 foreground // the first 2 decimal digits are included for added accuracy @@ -923,30 +1019,52 @@ function updateVehicleInfo(vcallsign, newPosition) { // update position if(vehicle.marker_shadow) { - vehicle.marker_shadow.setPosition(latlng); - vehicle.marker_shadow.setZIndex(Z_SHADOW + zIndex); + vehicle.marker_shadow.setLatLng(latlng); + //vehicle.marker_shadow.setZIndex(Z_SHADOW + zIndex); } - vehicle.marker.setPosition(latlng); - vehicle.marker.setZIndex(((vehicle.vehicle_type=="car")? Z_CAR : Z_PAYLOAD) + zIndex); + vehicle.marker.setLatLng(latlng); + //vehicle.marker.setZIndex(((vehicle.vehicle_type=="car")? Z_CAR : Z_PAYLOAD) + zIndex); - if(!!vehicle.marker.setCourse) vehicle.marker.setCourse((vehicle.curr_position.gps_heading !== "") ? parseInt(vehicle.curr_position.gps_heading) : 90); + if(!!vehicle.marker.setCourse) { + if (vehicle.curr_position.gps_heading) { + vehicle.marker.setCourse((vehicle.curr_position.gps_heading !== "") ? parseInt(vehicle.curr_position.gps_heading) : 90); + } + } // update horizon circles and icon if(vehicle.vehicle_type == "balloon") { - updateAltitude(vcallsign); + //updateAltitude(vcallsign); var horizon_km = Math.sqrt(12.756 * newPosition.gps_alt); - vehicle.horizon_circle.setRadius(Math.round(horizon_km)*1000); + if (!isNaN(horizon_km)) { + vehicle.horizon_circle.setRadius(Math.round(horizon_km)*1000); + vehicle.horizon_circle.setLatLng(latlng); + + horizon_circle_title_icon = new L.DivIcon({ + className: "horizon_circle_title", + html: '' + Math.round(horizon_km) + 'km' + }); + + vehicle.horizon_circle_title.setIcon(horizon_circle_title_icon); + } if(vehicle.subhorizon_circle) { - // see: http://ukhas.org.uk/communication:lineofsight - var el = 5.0; // elevation above horizon - var h = parseFloat(newPosition.gps_alt); // height above ground + // see: http://ukhas.org.uk/communication:lineofsight + var el = 5.0; // elevation above horizon + var h = parseFloat(newPosition.gps_alt); // height above ground - var elva = el * DEG_TO_RAD; - var slant = EARTH_RADIUS*(Math.cos(Math.PI/2+elva)+Math.sqrt(Math.pow(Math.cos(Math.PI/2+elva),2)+h*(2*EARTH_RADIUS+h)/Math.pow(EARTH_RADIUS,2))); - var subhorizon_km = Math.acos((Math.pow(EARTH_RADIUS,2)+Math.pow(EARTH_RADIUS+h,2)-Math.pow(slant,2))/(2*EARTH_RADIUS*(EARTH_RADIUS+h)))*EARTH_RADIUS; + var elva = el * DEG_TO_RAD; + var slant = EARTH_RADIUS*(Math.cos(Math.PI/2+elva)+Math.sqrt(Math.pow(Math.cos(Math.PI/2+elva),2)+h*(2*EARTH_RADIUS+h)/Math.pow(EARTH_RADIUS,2))); + var subhorizon_km = Math.acos((Math.pow(EARTH_RADIUS,2)+Math.pow(EARTH_RADIUS+h,2)-Math.pow(slant,2))/(2*EARTH_RADIUS*(EARTH_RADIUS+h)))*EARTH_RADIUS; - vehicle.subhorizon_circle.setRadius(Math.round(subhorizon_km)); + vehicle.subhorizon_circle.setRadius(Math.round(subhorizon_km)); + vehicle.subhorizon_circle.setLatLng(latlng); + + subhorizon_circle_title_icon = new L.DivIcon({ + className: "subhorizon_circle_title", + html: '' + Math.round(subhorizon_km/1000) + 'km' + }); + + vehicle.subhorizon_circle_title.setIcon(subhorizon_circle_title_icon); } // indicates whenever a payload has landed @@ -974,40 +1092,39 @@ function updateVehicleInfo(vcallsign, newPosition) { // We already have a marker initialized. if(newPosition.gps_alt > 350){ // Balloon is still in flight, so update the marker. - vehicle.landing_marker.setPosition(new google.maps.LatLng(newPosition.data.pred_lat, newPosition.data.pred_lon)); + vehicle.landing_marker.setLatLng(new L.LatLng(newPosition.data.pred_lat, newPosition.data.pred_lon)); // Re-add to map if it's been removed previously. if (vehicle.landing_marker.getMap() == null){ - vehicle.landing_marker.setMap(map); + map.addLayer(vehicle.landing_marker); } }else{ // Balloon has landed, so hide the marker. // Should we do this? Can we re-add it safely? - vehicle.landing_marker.setMap(null); + map.removeLayer(vehicle.landing_marker); } } else{ // Landing marker has not been initialised yet. if((newPosition.data.pred_lat !== 0.0) && (newPosition.data.pred_lon !== 0.0)){ landing_image_src = host_url + markers_url + "balloon-xmark.png"; - landing_image_src_size = new google.maps.Size(48,38); - landing_image_src_offset = new google.maps.Point(0,-38); - - landing_marker = new google.maps.Marker({ - icon: { - url: landing_image_src, - size: landing_image_src_size, - scaledSize: landing_image_src_size, - anchor: new google.maps.Point(24,18) - }, - zIndex: Z_CAR, - position: new google.maps.LatLng(position.data.pred_lat, position.data.pred_lon), - map: map, - optimized: false, - title: vcallsign + " Onboard Landing Prediction" + landing_image_src_size = [48,38]; + landing_image_src_offset = [0,-38]; + + latlngs = new L.latLng(position.data.pred_lat, position.data.pred_lon); + + landing_icon = new L.icon({ + iconUrl: landing_image_src, + iconSize: landing_image_src_size, + iconAnchor: [24, 18], }); - // Add the marker to the map, and to the vehicle object. - landing_marker.setMap(map); + landing_marker = new L.Marker(latlngs, { + zIndexOffset: Z_CAR, + icon: landing_icon, + title: vcallsign + " Onboard Landing Prediction", + }).addTo(map); + + // Add the marker to the vehicle object. vehicle.landing_marker = landing_marker; } @@ -1019,9 +1136,18 @@ function updateVehicleInfo(vcallsign, newPosition) { var elm = $('.vehicle' + vehicle.uuid); // if the vehicle doesn't exist in the list + // style="top:80px" + // if (vehicle["vehicle_type"] == "car") { if (elm.length === 0) { - $('.portrait').append('
'); - $('.landscape').append('
'); + if (vehicle.vehicle_type!="car") { + $('.portrait').prepend('
'); + $('.landscape').prepend('
'); + } else { + $('.portrait').append('
'); + $('.landscape').append('
'); + } + + } else if(elm.attr('data-vcallsign') === undefined) { elm.attr('data-vcallsign', vcallsign); @@ -1097,22 +1223,30 @@ function updateVehicleInfo(vcallsign, newPosition) { callsign_list = callsign_list.join(", "); } + //desktop var a = '
' + '' + sonde_type + vcallsign + ' ' + - //'' + vcallsign + ' ' + '' + '
' + '
' + '' + - 'Path' + - ((vcallsign in hysplit) ? 'HYSPLIT' : '') + - ((vcallsign.substr(0, 6) in ssdv) ? 'SSDV' : '') + + 'Path' + + ((vehicle.vehicle_type!="car") ? 'Share' : '') + + ((vehicle.vehicle_type!="car") ? 'Plot' : '') + + '
' + + '
'; + //mobile + var aa = '
' + + '' + sonde_type + vcallsign + ' ' + + '' + + '
' + + '
' + + '' + + 'Path' + + ((vehicle.vehicle_type!="car") ? 'Share' : '') + + ((vehicle.vehicle_type!="car") ? 'Plot' : '') + '
' + '
'; - // end var b = '
' + '
' + // right '
' + // data @@ -1145,13 +1279,17 @@ function updateVehicleInfo(vcallsign, newPosition) { ''; // update html - $('.portrait .vehicle'+vehicle.uuid).html(a + p + b); + $('.portrait .vehicle'+vehicle.uuid).html(aa + p + b); $('.landscape .vehicle'+vehicle.uuid).html(a + l + b); // redraw canvas if(wvar.mode != "Position" && vehicle.graph_data.length) { var can = $('.vehicle'+vehicle.uuid+' .graph'); - drawAltitudeProfile(can.get(0), can.get(1), vehicle.graph_data[0], vehicle.max_alt); + if (vehicle.vehicle_type!="car") { + drawAltitudeProfile(can.get(0), can.get(1), vehicle.graph_data[0], vehicle.max_alt, true); + } else { + drawAltitudeProfile(can.get(0), can.get(1), vehicle.graph_data[0], vehicle.max_alt, true); + } } // mark vehicles as redrawn @@ -1164,22 +1302,29 @@ function set_polyline_visibility(vcallsign, val) { var vehicle = vehicles[vcallsign]; vehicle.polyline_visible = val; - for(var k in vehicle.polyline) vehicle.polyline[k].setVisible(val); + for(var k in vehicle.polyline) { + if (val) { + map.addLayer(vehicle.polyline[k]); + vehicle.polyline[k].bringToBack(); + } else { + map.removeLayer(vehicle.polyline[k]); + } + } - mapInfoBox.close(); + map.removeLayer(mapInfoBox); } function removePrediction(vcallsign) { if(vehicles[vcallsign].prediction_polyline) { - vehicles[vcallsign].prediction_polyline.setMap(null); + map.removeLayer(vehicles[vcallsign].prediction_polyline); vehicles[vcallsign].prediction_polyline = null; } if(vehicles[vcallsign].prediction_target) { - vehicles[vcallsign].prediction_target.setMap(null); + map.removeLayer(vehicles[vcallsign].prediction_target); vehicles[vcallsign].prediction_target = null; } if(vehicles[vcallsign].prediction_burst) { - vehicles[vcallsign].prediction_burst.setMap(null); + map.removeLayer(vehicles[vcallsign].prediction_burst); vehicles[vcallsign].prediction_burst = null; } } @@ -1198,7 +1343,7 @@ function redrawPrediction(vcallsign) { var path_length = 0; for(var i = 0, ii = data.length; i < ii; i++) { - latlng = new google.maps.LatLng(data[i].lat, data[i].lon); + latlng = new L.LatLng(data[i].lat, data[i].lon); line.push(latlng); // pred.alt for graph @@ -1215,7 +1360,7 @@ function redrawPrediction(vcallsign) { latlng_burst = latlng; burst_index = i; } - if(i > 1) path_length += google.maps.geometry.spherical.computeDistanceBetween(line[i-1], line[i]); + if(i > 1) path_length += line[i-1].distanceTo(line[i]); } vehicle.graph_data[1].data = graph_data; @@ -1223,19 +1368,16 @@ function redrawPrediction(vcallsign) { vehicle.prediction_path = line; if(vehicle.prediction_polyline !== null) { - vehicle.prediction_polyline.setPath(line); + vehicle.prediction_polyline.setLatLngs(line); } else { - vehicle.prediction_polyline = new google.maps.Polyline({ - map: map, - zIndex: Z_PATH, - path: line, - strokeColor: balloon_colors[vehicle.color_index], - strokeOpacity: 0.4, - strokeWeight: 3, - clickable: true, - draggable: false, + vehicle.prediction_polyline = new L.Polyline(line, { + color: balloon_colors[vehicle.color_index], + opacity: 0.4, + weight: 3, + }).addTo(map); + vehicle.prediction_polyline.on('click', function (e) { + mapInfoBox_handle_prediction_path(e); }); - google.maps.event.addListener(vehicle.prediction_polyline, 'click', mapInfoBox_handle_prediction_path); } vehicle.prediction_polyline.path_length = path_length; @@ -1244,23 +1386,21 @@ function redrawPrediction(vcallsign) { if(vcallsign != "wb8elk2") { // WhiteStar var html = ""; if(vehicle.prediction_target) { - vehicle.prediction_target.setPosition(latlng); + vehicle.prediction_target.setLatLng(latlng); } else { image_src = host_url + markers_url + "target-" + balloon_colors_name[vehicle.color_index] + ".png"; - vehicle.prediction_target = new google.maps.Marker({ - position: latlng, - optimized: false, - zIndex: Z_SHADOW, - icon: { - url: image_src, - scaledSize: new google.maps.Size(20,20), - size: new google.maps.Size(20,20), - anchor: new google.maps.Point(10, 10) - }, - map: map, - clickable: true + predictionIcon = new L.icon({ + iconUrl: image_src, + iconSize: [20,20], + iconAnchor: [10, 10], + }); + vehicle.prediction_target = new L.Marker(latlng, { + zIndexOffset: Z_SHADOW, + icon: predictionIcon, + }).addTo(map); + vehicle.prediction_target.on('click', function (e) { + mapInfoBox_handle_prediction(e); }); - google.maps.event.addListener(vehicle.prediction_target, 'click', mapInfoBox_handle_prediction); } vehicle.prediction_target.pdata = data[data.length-1]; } else { @@ -1269,23 +1409,21 @@ function redrawPrediction(vcallsign) { if(burst_index !== 0 && vcallsign != "wb8elk2") { if(vehicle.prediction_burst) { - vehicle.prediction_burst.setPosition(latlng_burst); + vehicle.prediction_burst.setLatLng(latlng_burst); } else { image_src = host_url + markers_url + "balloon-pop.png"; - vehicle.prediction_burst = new google.maps.Marker({ - position: latlng_burst, - optimized: false, - zIndex: Z_SHADOW, - icon: { - url: image_src, - scaledSize: new google.maps.Size(20,20), - size: new google.maps.Size(20,20), - anchor: new google.maps.Point(10, 10) - }, - map: map, - clickable: true + burstIcon = new L.icon({ + iconUrl: image_src, + iconSize: [20,20], + iconAnchor: [10, 10], + }); + vehicle.prediction_burst = new L.Marker(latlng_burst, { + zIndexOffset: Z_SHADOW, + icon: burstIcon, + }).addTo(map); + vehicle.prediction_burst.on('click', function (e) { + mapInfoBox_handle_prediction(e); }); - google.maps.event.addListener(vehicle.prediction_burst, 'click', mapInfoBox_handle_prediction); } vehicle.prediction_burst.pdata = data[burst_index]; } else { @@ -1293,13 +1431,16 @@ function redrawPrediction(vcallsign) { } } -function updatePolyline(vcallsign) { +function updatePolyline(vcallsign, flag) { for(var k in vehicles[vcallsign].polyline) { - vehicles[vcallsign].polyline[k].setPath(vehicles[vcallsign].positions); + if (flag) { + vehicles[vcallsign].polyline[k].setLatLngs([]); + } + vehicles[vcallsign].polyline[k].setLatLngs(vehicles[vcallsign].positions); } } -function drawAltitudeProfile(c1, c2, series, alt_max) { +function drawAltitudeProfile(c1, c2, series, alt_max, chase) { alt_max = (alt_max < 2000) ? 2000 : alt_max; var alt_list = series.data; var len = alt_list.length; @@ -1320,12 +1461,21 @@ function drawAltitudeProfile(c1, c2, series, alt_max) { c1.attr('width', cw1).attr('height', ch1); c2.attr('width', cw2).attr('height', ch2); - ctx1.fillStyle = "#d6f0f9"; - ctx1.lineWidth = 2 * ratio; - ctx1.strokeStyle= "#33B5F5"; - ctx2.fillStyle = "#d6f0f9"; - ctx2.lineWidth = 2 * ratio; - ctx2.strokeStyle= "#33B5F5"; + if (chase) { + ctx1.fillStyle = "#d6f0f9"; + ctx1.lineWidth = 2 * ratio; + ctx1.strokeStyle= "#33B5F5"; + ctx2.fillStyle = "#d6f0f9"; + ctx2.lineWidth = 2 * ratio; + ctx2.strokeStyle= "#33B5F5"; + } else { + ctx1.fillStyle = "#f9d6d6"; + ctx1.lineWidth = 2 * ratio; + ctx1.strokeStyle= "#f53333"; + ctx2.fillStyle = "#f9d6d6"; + ctx2.lineWidth = 2 * ratio; + ctx2.strokeStyle= "#f53333"; + } var xt1 = (cw1 - (2 * ratio)) / real_len; var yt1 = (ch1 - (6 * ratio)) / alt_max; @@ -1394,12 +1544,12 @@ function drawAltitudeProfile(c1, c2, series, alt_max) { } // infobox -var mapInfoBox = new google.maps.InfoWindow({ +var mapInfoBox = new L.popup({ maxWidth: 260 }); -var mapInfoBox_handle_prediction_path = function(event) { - var value = this.path_length; +function mapInfoBox_handle_prediction_path(event) { + var value = event.target.path_length; if(offline.get('opt_imperial')) { value = Math.round(value*0.000621371192) + " miles"; @@ -1408,17 +1558,17 @@ var mapInfoBox_handle_prediction_path = function(event) { } mapInfoBox.setContent("
Length: " + value  + "
"); - mapInfoBox.setPosition(event.latLng); - mapInfoBox.open(map); + mapInfoBox.setLatLng(event.latlng); + mapInfoBox.openOn(map); }; -var mapInfoBox_handle_path = function(event) { - var vehicle = this.vehicle || vehicles[follow_vehicle]; - var target = event.latLng; +function mapInfoBox_handle_path(event) { + var vehicle = event.target.vehicle || vehicles[follow_vehicle]; + var target = event.latlng; var p = vehicle.positions; var p1_dist = 0; - var p2_dist = google.maps.geometry.spherical.computeDistanceBetween(p[0], target); + var p2_dist = p[0].distanceTo(target); var mindiff = Number.MAX_VALUE; var minidx = 0; @@ -1427,8 +1577,8 @@ var mapInfoBox_handle_path = function(event) { // find the closest existing point to snap to for(var i = 1, ii = p.length; i < ii; i++ ) { p1_dist = p2_dist; - p2_dist = google.maps.geometry.spherical.computeDistanceBetween(p[i], target); - dist = google.maps.geometry.spherical.computeDistanceBetween(p[i], p[i-1]); + p2_dist = p[i].distanceTo(target); + dist = p[i].distanceTo(p[i-1]); diff = Math.abs(dist - (p1_dist + p2_dist)); if(diff >= 0 && mindiff > diff) { @@ -1437,21 +1587,20 @@ var mapInfoBox_handle_path = function(event) { } } - p1_dist = google.maps.geometry.spherical.computeDistanceBetween(p[minidx-1], target); - p2_dist = google.maps.geometry.spherical.computeDistanceBetween(p[minidx], target); + p1_dist = p[minidx-1].distanceTo(target); + p2_dist = p[minidx].distanceTo(target); var point = (p1_dist < p2_dist) ? p[minidx-1] : p[minidx]; var id = (p1_dist < p2_dist) ? vehicle.positions_ids[minidx-1] : vehicle.positions_ids[minidx]; mapInfoBox.setContent(""); - mapInfoBox.setPosition(point); - mapInfoBox.setMap(map); - mapInfoBox.open(map); + mapInfoBox.setLatLng(point); + mapInfoBox.openOn(map); mapInfoBox_handle_path_fetch(id, vehicle); }; -var mapInfoBox_handle_path_fetch = function(id,vehicle) { +function mapInfoBox_handle_path_fetch(id,vehicle) { var ishabitat = id.length == 64 if(ishabitat) { @@ -1473,7 +1622,7 @@ var mapInfoBox_handle_path_fetch = function(id,vehicle) { if('positions' in data && data.positions.position.length === 0) { mapInfoBox.setContent("not found"); - mapInfoBox.open(map); + mapInfoBox.openOn(map); return; } @@ -1482,9 +1631,6 @@ var mapInfoBox_handle_path_fetch = function(id,vehicle) { div = document.createElement('div'); html = "
"; - html += ""; html += "
"+data.vehicle+"("+data.position_id+")
"; html += "
"; html += "
 "+roundNumber(data.gps_lat, 5) + ', ' + roundNumber(data.gps_lon, 5)+"
"; @@ -1520,7 +1666,6 @@ var mapInfoBox_handle_path_fetch = function(id,vehicle) { var callsign_list = [] for(var rxcall in data.callsign){ if(data.callsign.hasOwnProperty(rxcall)) { - _new_call += "Received via:  "; _new_call = rxcall; if(data.callsign[rxcall].hasOwnProperty('snr')){ if(data.callsign[rxcall].snr){ @@ -1546,7 +1691,7 @@ var mapInfoBox_handle_path_fetch = function(id,vehicle) { div.innerHTML = html; mapInfoBox.setContent(div); - mapInfoBox.open(map); + mapInfoBox.openOn(map); setTimeout(function() { div.parentElement.style.overflow = ""; @@ -1555,8 +1700,8 @@ var mapInfoBox_handle_path_fetch = function(id,vehicle) { }); }; -var mapInfoBox_handle_prediction = function(event) { - var data = this.pdata; +function mapInfoBox_handle_prediction(event) { + var data = event.target.pdata; var altitude; if(offline.get('opt_imperial')) { @@ -1572,23 +1717,22 @@ var mapInfoBox_handle_prediction = function(event) { "Longtitude: " + data.lon + "\n" + "" ); - mapInfoBox.setPosition(event.latLng); - mapInfoBox.open(map); + mapInfoBox.setLatLng(event.latlng); + mapInfoBox.openOn(map); }; -var mapInfoBox_handle_horizons = function(event, obj, title) { +function mapInfoBox_handle_horizons(event, obj, title) { var value = ""; - + if(offline.get('opt_imperial')) { - value = Math.round(obj.getRadius()*0.000621371192) + "miles"; + value = Math.round(event.target.getRadius()*0.000621371192) + "miles"; } else { - value = Math.round(obj.getRadius()/10)/100 + "km"; + value = Math.round(event.target.getRadius()/10)/100 + "km"; } - mapInfoBox.setContent("
" + title + "\nr = "+ value + "
"); - mapInfoBox.setPosition(event.latLng); - mapInfoBox.open(map); + mapInfoBox.setLatLng(event.latlng); + mapInfoBox.openOn(map); }; var mapInfoBox_handle_truehorizon = function(event) { mapInfoBox_handle_horizons(event, this, "True Horizon"); }; @@ -1614,13 +1758,14 @@ var marker_rotate_func = function(deg) { ctx.drawImage(img, -img.width/2, -img.height*0.95); ctx.restore(); - var size = new google.maps.Size(canvas.width*0.5, canvas.height*0.5); - this.setIcon({ - url: canvas.toDataURL(), - size: size, - scaledSize: size, - anchor: new google.maps.Point(canvas.width*0.25, canvas.height*0.25) + var size = [canvas.width*0.5, canvas.height*0.5]; + newIcon = L.icon({ + iconUrl: canvas.toDataURL(), + iconSize: size, + iconAnchor: [canvas.width*0.25, canvas.height*0.25], + tooltipAnchor: [0,-32], }); + this.setIcon(newIcon); }; var marker_rotate_setup = function(marker, image_src) { @@ -1629,14 +1774,14 @@ var marker_rotate_setup = function(marker, image_src) { if(image_src in icon_cache) { marker.iconImg = icon_cache[image_src]; marker.setCourse(90); - marker.setPosition(marker.getPosition()); + marker.setLatLng(marker.getLatLng()); } else { marker.iconImg = new Image(); icon_cache[image_src] = marker.iconImg; marker.iconImg.onload = function() { if(!marker.rotated) marker.setCourse(90); - marker.setPosition(marker.getPosition()); + marker.setLatLng(marker.getLatLng()); }; marker.iconImg.src = image_src; } @@ -1660,121 +1805,120 @@ function addPosition(position) { var vehicle_type = ""; var horizon_circle = null; var subhorizon_circle = null; - var point = new google.maps.LatLng(position.gps_lat, position.gps_lon); + var point = new L.LatLng(position.gps_lat, position.gps_lon); var image_src = "", image_src_size, image_src_offset; var color_index = 0; - var gmaps_elements = []; var polyline = null; var polyline_visible = false; + var horizon_circle_title = null; + var subhorizon_circle_title = null; if(vcallsign.search(/(chase)/i) != -1) { vehicle_type = "car"; color_index = car_index++ % car_colors.length; image_src = host_url + markers_url + "car-" + car_colors[color_index] + ".png"; - image_src_size = new google.maps.Size(55,25); - image_src_offset = new google.maps.Point(0,-25); - - marker = new google.maps.Marker({ - zIndex: Z_CAR, - position: point, - map: map, - clickable: false, - optimized: false, - title: vcallsign - }); + image_src_size = [55,25]; + image_src_offset = [0,-25]; + + marker = new L.Marker(point, { + title: vcallsign, + zIndexOffset: Z_CAR, + }).addTo(map).on('click', onClick); + + // Scroll list stuff here. + function onClick(e) { + _vehicle_id = e.target.options.title; + _vehicle_idname = ".vehicle"+vehicles[_vehicle_id].uuid; + $(_vehicle_idname).addClass('active'); + listScroll.refresh(); + listScroll.scrollToElement(_vehicle_idname); + panTo(vcallsign); + }; if(!!!window.HTMLCanvasElement) { - marker.setIcon({ - url: image_src, - size: image_src_size, - scaledSize: image_src_size, - anchor: new google.maps.Point(27,22) + carIcon = L.icon({ + iconUrl: image_src, + iconSize: image_src_size, + iconAnchor: [27.22], + tooltipAnchor: [0,-32], }); + marker.setIcon(new carIcon); } else { marker_rotate_setup(marker, image_src); } - gmaps_elements.push(marker); + marker.addTo(map); polyline = [ - new google.maps.Polyline({ - map: map, - zIndex: Z_PATH, - strokeColor: car_colors[color_index], - strokeOpacity: 1, - strokeWeight: 3, - clickable: true, - draggable: false, - visible: polyline_visible, - geodesic: true - }), + new L.Polyline(point, { + color: car_colors[color_index], + opacity: 1, + weight: 3, + }) ]; } else if(vcallsign == "XX") { vehicle_type = "xmark"; image_src = host_url + markers_url + "balloon-xmark.png"; - image_src_size = new google.maps.Size(48,38); - image_src_offset = new google.maps.Point(0,-38); - - marker = new google.maps.Marker({ - icon: { - url: image_src, - size: image_src_size, - scaledSize: image_src_size, - anchor: new google.maps.Point(24,18) - }, - zIndex: Z_CAR, - position: point, - map: map, - optimized: false, - title: vcallsign + image_src_size = [48,38]; + image_src_offset = [0,-38]; + + xmarkIcon = new L.icon({ + iconUrl: image_src, + iconSize: image_src_size, + iconAnchor: [24, 18], }); - gmaps_elements.push(marker); + + marker = new L.Marker(point, { + icon: xmarkIcon, + title: vcallsign, + zIndexOffset: Z_CAR, + }); + + marker.addTo(map); } else { vehicle_type = "balloon"; color_index = balloon_index++ % balloon_colors.length; image_src = host_url + markers_url + "balloon-" + ((vcallsign == "PIE") ? "rpi" : balloon_colors_name[color_index]) + ".png"; - image_src_size = new google.maps.Size(46,84); - image_src_offset = new google.maps.Point(-35,-46); - - marker_shadow = new google.maps.Marker({ - map: map, - zIndex: Z_SHADOW, - optimized: false, - position: point, - icon: { - url: host_url + markers_url + "shadow.png", - size: new google.maps.Size(24,16), - scaledSize: new google.maps.Size(24,16), - anchor: new google.maps.Point(12,8) - }, - clickable: false + image_src_size = [46,84]; + image_src_offset = [-35,-46]; + + shadowIcon = new L.icon({ + iconUrl: host_url + markers_url + "shadow.png", + iconSize: [24,16], + iconAnchor: [12, 8], }); - gmaps_elements.push(marker_shadow); - marker = new google.maps.Marker({ - map: map, - optimized: false, - zIndex: Z_PAYLOAD, - position: point, - icon: { - url: image_src, - size: image_src_size, - scaledSize: image_src_size, - }, - title: position.type + ' ' + vcallsign, + + marker_shadow = new L.Marker(point, { + icon: shadowIcon, + zIndexOffset: Z_SHADOW, + }).addTo(map); + + balloonIcon = new L.icon({ + iconUrl: image_src, + iconSize: image_src_size, + tooltipAnchor: [0,-98], + iconAnchor: [23,90], }); + marker = new L.Marker(point, { + icon: balloonIcon, + title: position.type + ' ' + vcallsign, + zIndexOffset: Z_PAYLOAD, + }).addTo(map).on('click', onClick); + // Scroll list stuff here. - google.maps.event.addDomListener(marker, 'click', function() { + function onClick(e) { $(".row.active").removeClass('active'); - _vehicle_id = marker.title.split(' ')[1]; + _vehicle_id = e.target.options.title.split(' ')[1]; _vehicle_idname = ".vehicle"+vehicles[_vehicle_id].uuid; $(_vehicle_idname).addClass('active'); listScroll.refresh(); listScroll.scrollToElement(_vehicle_idname); followVehicle($(_vehicle_idname).attr('data-vcallsign')); - }); + clearTimeout(periodical_focus); + refreshSingle(_vehicle_id, true); + }; - gmaps_elements.push(marker); marker.shadow = marker_shadow; marker.balloonColor = (vcallsign == "PIE") ? "rpi" : balloon_colors_name[color_index]; marker.mode = 'balloon'; @@ -1784,49 +1928,45 @@ function addPosition(position) { this.mode = mode; var img; if(mode == "landed") { - vehicle.marker.shadow.setVisible(false); - vehicle.horizon_circle.setVisible(false); - vehicle.horizon_circle.label.set('visible', false); - vehicle.subhorizon_circle.setVisible(false); - vehicle.subhorizon_circle.label.set('visible', false); - - img = { - url: host_url + markers_url + "payload-" + this.balloonColor + ".png", - size: new google.maps.Size(17,18), - scaledSize: new google.maps.Size(17,18), - anchor: new google.maps.Point(8,14) - }; + map.removeLayer(vehicle.marker.shadow); + map.removeLayer(vehicle.horizon_circle); + map.removeLayer(vehicle.subhorizon_circle); + map.removeLayer(vehicle.horizon_circle_title); + map.removeLayer(vehicle.subhorizon_circle_title); + + img = new L.icon ({ + iconUrl: host_url + markers_url + "payload-" + this.balloonColor + ".png", + iconSize: [17,18], + iconAnchor: [8,14], + tooltipAnchor: [0,-20], + }); } else { - vehicle.marker.shadow.setVisible(true); + map.addLayer(vehicle.marker.shadow); if(offline.get('opt_hide_horizon') == false){ - vehicle.horizon_circle.setVisible(true); - vehicle.horizon_circle.label.set('visible', true); - vehicle.subhorizon_circle.setVisible(true); - vehicle.subhorizon_circle.label.set('visible', true); + map.addLayer(vehicle.horizon_circle); + map.addLayer(vehicle.subhorizon_circle); + map.addLayer(vehicle.horizon_circle_title); + map.addLayer(vehicle.subhorizon_circle_title); } if(mode == "parachute") { - img = { - url: host_url + markers_url + "parachute-" + this.balloonColor + ".png", - size: new google.maps.Size(46,84), - scaledSize: new google.maps.Size(46,84) - }; + img = new L.icon ({ + iconUrl: host_url + markers_url + "parachute-" + this.balloonColor + ".png", + iconSize: [46,84], + tooltipAnchor: [0,-98], + iconAnchor: [23,90], + }); } else { - img = { - url: host_url + markers_url + "balloon-" + this.balloonColor + ".png", - size: new google.maps.Size(46,84), - scaledSize: new google.maps.Size(46,84) - }; + img = new L.icon ({ + iconUrl: host_url + markers_url + "balloon-" + this.balloonColor + ".png", + iconSize: [46,84], + tooltipAnchor: [0,-98], + iconAnchor: [23,90], + }); } } - this.setIcon(img); - this.setPosition(this.getPosition()); - }; - marker.setAltitude = function(alt) { - var pos = overlay.getProjection().fromLatLngToDivPixel(this.shadow.getPosition()); - pos.y -= alt; - this.setPosition(overlay.getProjection().fromDivPixelToLatLng(pos)); + if (!wvar.nyan) {this.setIcon(img);}; }; // Add landing marker if the payload provides a predicted landing position. @@ -1834,23 +1974,22 @@ function addPosition(position) { // Only create the marker if the pred lat/lon are not zero (as will be the case during ascent). if ((position.data.pred_lat !== 0.0) && (position.data.pred_lon !== 0.0)){ landing_image_src = host_url + markers_url + "balloon-xmark.png"; - landing_image_src_size = new google.maps.Size(48,38); - landing_image_src_offset = new google.maps.Point(0,-38); - - landing_marker = new google.maps.Marker({ - icon: { - url: landing_image_src, - size: landing_image_src_size, - scaledSize: landing_image_src_size, - anchor: new google.maps.Point(24,18) - }, - zIndex: Z_CAR, - position: new google.maps.LatLng(position.data.pred_lat, position.data.pred_lon), - map: map, - optimized: false, - title: vcallsign + " Onboard Landing Prediction" + landing_image_src_size = [48,38]; + landing_image_src_offset = [0,-38]; + + landingIcon = new L.icon({ + iconUrl: landing_image_src, + iconSize: landing_image_src_size, + iconAnchor: [24, 18], }); - gmaps_elements.push(landing_marker); + + var latlng = new L.LatLng(position.data.pred_lat, position.data.pred_lon); + + landing_marker = new L.Marker(latlng, { + icon: landingIcon, + title: vcallsign + " Onboard Landing Prediction", + zIndexOffset: Z_CAR, + }).addTo(map); } else { landing_marker = null; } @@ -1858,152 +1997,105 @@ function addPosition(position) { landing_marker = null; } - horizon_circle = new google.maps.Circle({ - map: map, - zIndex: Z_RANGE, + horizon_circle = new L.Circle(point, { + zIndexOffset: Z_RANGE, radius: 1, + color: '#00F', fillColor: '#00F', fillOpacity: 0, - strokeColor: '#00F', - strokeOpacity: 0.6, - strokeWeight: 3, - clickable: false, - editable: false + opacity: 0.6, + interactive: false, }); - gmaps_elements.push(horizon_circle); - horizon_circle.bindTo('center', marker_shadow, 'position'); - - // label - horizon_circle.label = new google.maps.Label({ - map: map, - strokeColor: horizon_circle.get('strokeColor'), - visible: false - }); - gmaps_elements.push(horizon_circle.label); - horizon_circle.label.bindTo('opacity', horizon_circle, 'strokeOpacity'); - horizon_circle.label.bindTo('zIndex', horizon_circle, 'zIndex'); - horizon_circle.label.bindTo('strokeColor', horizon_circle, 'strokeColor'); - - var refresh_func = function() { - if(!this.getVisible()) { - this.label.set('visible', false); - return; - } - - var north = google.maps.geometry.spherical.computeOffset(this.getCenter(), this.getRadius(), 0); - var south = google.maps.geometry.spherical.computeOffset(this.getCenter(), this.getRadius(), 180); - var projection = this.label.getProjection(); - var dist = projection.fromLatLngToDivPixel(south).y - - projection.fromLatLngToDivPixel(north).y; + horizon_circle_title_icon = new L.DivIcon({ + className: "horizon_circle_title", + html: 'km' + }); - var val = this.getRadius() / 1000; - val = offline.get('opt_imperial') ? Math.round(val * 0.621371192) + "mi" : Math.round(val) + "km"; + horizon_circle_title = new L.Marker(point, { + icon: horizon_circle_title_icon, + interactive: false, + }); - this.label.set('visible', (75 < dist)); - this.label.set('position', google.maps.geometry.spherical.computeOffset(this.getCenter(), this.getRadius(), 180)); - this.label.set('text', val); - }; + if (!offline.get("opt_hide_horizon")) { + horizon_circle.addTo(map); + horizon_circle_title.addTo(map); + } - google.maps.event.addListener(horizon_circle, 'center_changed', refresh_func); - google.maps.event.addListener(horizon_circle, 'radius_changed', refresh_func); + horizon_circle.on('move', function (e) { + try { + var latlng = L.latLng(e.target.getBounds()._southWest.lat, ((e.target.getBounds()._northEast.lng + e.target.getBounds()._southWest.lng)/2)); + horizon_circle_title.setLatLng(latlng); + } catch (err) {} + }); - subhorizon_circle = new google.maps.Circle({ - map: map, + subhorizon_circle = new L.Circle(point, { + zIndexOffset: Z_RANGE, radius: 1, - zIndex: Z_RANGE, + color: '#0F0', fillColor: '#0F0', fillOpacity: 0, - strokeColor: '#0F0', - strokeOpacity: 0.8, - strokeWeight: 3, - clickable: false, - editable: false + opacity: 0.8, + interactive: false, }); - subhorizon_circle.bindTo('center', marker_shadow, 'position'); - gmaps_elements.push(subhorizon_circle); - subhorizon_circle.label = new google.maps.Label({ - map: map, - strokeColor: subhorizon_circle.get('strokeColor'), - visible: false + subhorizon_circle_title_icon = new L.DivIcon({ + className: "subhorizon_circle_title", + html: 'km', }); - gmaps_elements.push(subhorizon_circle.label); - subhorizon_circle.label.bindTo('opacity', subhorizon_circle, 'strokeOpacity'); - subhorizon_circle.label.bindTo('zIndex', subhorizon_circle, 'zIndex'); - subhorizon_circle.label.bindTo('strokeColor', subhorizon_circle, 'strokeColor'); - - google.maps.event.addListener(subhorizon_circle, 'center_changed', refresh_func); - google.maps.event.addListener(subhorizon_circle, 'radius_changed', refresh_func); - - if(offline.get("opt_hide_horizon")){ - horizon_circle.setVisible(false); - horizon_circle.label.set('visible', false); - subhorizon_circle.setVisible(false); - subhorizon_circle.label.set('visible', false); + + subhorizon_circle_title = new L.Marker(point, { + icon: subhorizon_circle_title_icon, + interactive: false, + }); + + if (!offline.get("opt_hide_horizon")) { + subhorizon_circle.addTo(map); + subhorizon_circle_title.addTo(map); } - marker.setAltitude(0); + subhorizon_circle.on('move', function (e) { + try { + var latlng = L.latLng(e.target.getBounds()._southWest.lat, ((e.target.getBounds()._northEast.lng + e.target.getBounds()._southWest.lng)/2)); + subhorizon_circle_title.setLatLng(latlng); + } catch (err) {} + }); + polyline_visible = true; polyline = [ - new google.maps.Polyline({ - map: map, - zIndex: Z_PATH, - strokeColor: balloon_colors[color_index], - strokeOpacity: 1, - strokeWeight: 3, - clickable: true, - draggable: false, - visible: polyline_visible, - geodesic: true - }), - new google.maps.Polyline({ - map: map, - zIndex: Z_PATH - 1, - strokeColor: (['cyan','yellow'].indexOf(balloon_colors_name[color_index]) > -1 ? '#888888' : "#ffffff"), - strokeOpacity: 1, - strokeWeight: 5, - clickable: true, - draggable: false, - visible: polyline_visible, - geodesic: true - }), + new L.Polyline(point, { + color: balloon_colors[color_index], + opacity: 1, + weight: 3, + }).addTo(map) ]; } - // add label above every marker - var mlabel = new google.maps.Label({map: map, textOnly: true, position: marker.getPosition() }); - gmaps_elements.push(mlabel); - mlabel.bindTo('text', marker, 'title'); - mlabel.bindTo('zIndex', marker, 'zIndex'); - google.maps.event.addListener(marker, 'position_changed', function() { - if(!!!marker.icon) return; - - var pos = mlabel.getProjection().fromLatLngToDivPixel(marker.getPosition()); - - if(!!marker.iconImg) { - pos.y -= marker.icon.size.height * 0.5 + 5; + if (!offline.get("opt_hide_titles")) { + if (vehicle_type == "car") { + title = marker.bindTooltip(vcallsign, {direction: 'center', permanent: 'true', className: 'serialtooltip'}); } else { - pos.y -= marker.icon.size.height + 10; + title = marker.bindTooltip((position.type + ' ' + vcallsign), {direction: 'center', permanent: 'true', className: 'serialtooltip'}); } - - mlabel.set('position',mlabel.getProjection().fromDivPixelToLatLng(pos)); - }); - marker._label = mlabel; - marker.setPosition(marker.getPosition()); // activates the logic above to reposition the label + } else { + title = null; + } var vehicle_info = { callsign: vcallsign, uuid: elm_uuid++, vehicle_type: vehicle_type, marker: marker, + title: title, marker_shadow: marker_shadow, landing_marker: landing_marker, image_src: image_src, image_src_size: image_src_size, image_src_offset: image_src_offset, horizon_circle: horizon_circle, + horizon_circle_title: horizon_circle_title, subhorizon_circle: subhorizon_circle, + subhorizon_circle_title: subhorizon_circle_title, num_positions: 0, positions: [], positions_ts: [], @@ -2013,21 +2105,15 @@ function addPosition(position) { line: [], polyline_visible: polyline_visible, polyline: polyline !== null ? polyline : [ - new google.maps.Polyline({ - map: map, - zIndex: Z_PATH, - strokeColor: "#ffffff", - strokeOpacity: 1, - strokeWeight: 3, - clickable: true, - draggable: false, - visible: polyline_visible, - geodesic: true + new L.Polyline(point, { + color: "#ffffff", + opacity: 1, + weight: 3, }), ], prediction: null, prediction_polyline: null, - prediction_traget: null, + prediction_target: null, prediction_burst: null, ascent_rate: 0.0, horizontal_rate: 0.0, @@ -2041,105 +2127,104 @@ function addPosition(position) { updated: false, start_time: 2147483647000 }; - + // deep copy yaxes config for graph - plot_options.yaxes.forEach(function(v) { vehicle_info.graph_yaxes.push($.extend({}, v)); }); + plot_options.yaxes.forEach(function(v) { vehicle_info.graph_yaxes.push($.extend({}, v)); }); - // nyan mod + //nyan cat (very important feature) if(wvar.nyan && vehicle_info.vehicle_type == "balloon") { - // form a nyancat - vehicle_info.marker.setMode = function(mode) { this.mode = mode; this.setPosition(this.getPosition()); }; - vehicle_info.marker.setAltitude = function(derp) { this.setPosition(this.getPosition()); }; - - var nyan = nyan_colors[nyan_color_index] + ".gif"; nyan_color_index = (nyan_color_index + 1) % nyan_colors.length; var nyanw = (nyan_color_index == 4) ? 104 : 55; - vehicle_info.marker.setIcon({ - url: host_url + markers_url + nyan, - size: new google.maps.Size(nyanw,39), - scaledSize: new google.maps.Size(nyanw,39), - anchor: new google.maps.Point(26,20) - }); - vehicle_info.marker.iconImg = 1; + nyanIcon = new L.icon ({ + iconUrl: host_url + markers_url + nyan, + iconSize: [nyanw,39], + iconAnchor: [26,20], + tooltipAnchor: [0,-29], + }); + + vehicle_info.marker.setIcon(nyanIcon); vehicle_info.image_src = host_url + markers_url + "hab_nyan.gif"; - vehicle_info.image_src_offset = new google.maps.Point(-34,-70); + vehicle_info.image_src_offset = [-34,-70]; - // remove all polylines var k; for(k in vehicle_info.polyline) { - vehicle_info.polyline[k].setMap(null); + map.removeLayer(vehicle_info.polyline[k]); } vehicle_info.polyline = []; for(k in rainbow) { - vehicle_info.polyline.push(new google.maps.Polyline({ - map: map, - zIndex: (Z_PATH - (k * 1)), - strokeColor: rainbow[k], - strokeOpacity: 1, - strokeWeight: (k*4) + 2, - clickable: true, - draggable: false, - geodesic: true - })); + vehicle_info.polyline.push(new L.Polyline(point, { + zIndexOffset: (Z_PATH - (k * 1)), + color: rainbow[k], + opacity: 1, + weight: (k*4) + 2, + }).addTo(map)); + vehicle_info.polyline[k].bringToBack(); } } - - vehicle_info.gmaps_elements = gmaps_elements.concat(vehicle_info.polyline); + vehicle_info.kill = function() { $(".vehicle"+vehicle_info.uuid).remove(); - vehicle_info.gmaps_elements.forEach(function(elm) { elm.setMap(null); }); + potentialobjects = [marker, marker_shadow, landing_marker, horizon_circle, horizon_circle_title, subhorizon_circle, subhorizon_circle_title, polyline]; + if (map.hasLayer(vehicle_info["prediction_polyline"])) { + map.removeLayer(vehicle_info["prediction_polyline"]); + } + if (map.hasLayer(vehicle_info["prediction_target"])) { + map.removeLayer(vehicle_info["prediction_target"]); + } + if (map.hasLayer(vehicle_info["prediction_burst"])) { + map.removeLayer(vehicle_info["prediction_burst"]); + } + try { + for(var p in vehicle_info.polyline) { + map.removeLayer(vehicle_info.polyline[p]); + } + } catch (e) {}; + for (let i = 0; i < potentialobjects.length; i++) { + if (map.hasLayer(potentialobjects[i])) { + map.removeLayer(potentialobjects[i]); + } + } delete vehicles[vehicle_info.callsign]; }; // polyline for(var pkey in vehicle_info.polyline) { vehicle_info.polyline[pkey].vehicle = vehicle_info; - google.maps.event.addListener(vehicle_info.polyline[pkey], 'click', mapInfoBox_handle_path); + vehicle_info.polyline[pkey].on('click', function (e) { + mapInfoBox_handle_path(e); + }); } - - // horizon circles - if(vehicle_info.horizon_circle) google.maps.event.addListener(vehicle_info.horizon_circle, 'click', mapInfoBox_handle_truehorizon); - if(vehicle_info.subhorizon_circle) google.maps.event.addListener(vehicle_info.subhorizon_circle, 'click', mapInfoBox_handle_horizon); - - // let the nyan free - vehicles[vcallsign] = vehicle_info; + + vehicles[vcallsign] = vehicle_info; } var vehicle = vehicles[vcallsign]; - var new_latlng = new google.maps.LatLng(position.gps_lat, position.gps_lon); + var new_latlng = new L.LatLng(position.gps_lat, position.gps_lon); var new_ts = convert_time(position.gps_time); var curr_ts = convert_time(vehicle.curr_position.gps_time); var dt = (new_ts - curr_ts) / 1000; // convert to seconds - if(dt === 0 && vehicle.num_positions) { - // Removed the below. Don't think its needed anymore. - // var callsigns = []; - // for (var rxcall in vehicle.curr_position.callsign){ - // if (vehicle.curr_position.callsign.hasOwnProperty(rxcall)){ - // callsigns.push(rxcall); - // } - // } - // console.log(position); - // var newcalls = callsigns.concat(position.callsign.split(', ')); - - // vehicle.curr_position.callsign = array_unique(callsigns).join(', '); - } - else if(dt >= 0) { + if(dt >= 0) { if(vehicle.num_positions > 0) { // calculate vertical rate // TODO - Make this average over more points rather than use a FIR. var rate = (position.gps_alt - vehicle.curr_position.gps_alt) / dt; - vehicle.ascent_rate = 0.7 * rate + 0.3 * vehicle.ascent_rate; + if (!isNaN(rate)) { + vehicle.ascent_rate = 0.7 * rate + 0.3 * vehicle.ascent_rate; + } + // calculate horizontal rate - vehicle.horizontal_rate = google.maps.geometry.spherical.computeDistanceBetween(new google.maps.LatLng(position.gps_lat, position.gps_lon), - new google.maps.LatLng(vehicle.curr_position.gps_lat, vehicle.curr_position.gps_lon)) / dt; + horizontal_rate_temp = new_latlng.distanceTo(new L.LatLng(vehicle.curr_position.gps_lat, vehicle.curr_position.gps_lon)) / dt; + if (!isNaN(horizontal_rate_temp)) { + vehicle.horizontal_rate = horizontal_rate_temp; + } } // add the new position @@ -2156,13 +2241,46 @@ function addPosition(position) { // increment length var poslen = vehicle.num_positions; - if(poslen > 1) vehicle.path_length += google.maps.geometry.spherical.computeDistanceBetween(vehicle.positions[poslen-2], vehicle.positions[poslen-1]); + if(poslen > 1) vehicle.path_length += vehicle.positions[poslen-2].distanceTo(vehicle.positions[poslen-1]); + + L.LatLng.prototype.bearingTo = function(other) { + var d2r = L.LatLng.DEG_TO_RAD; + var r2d = L.LatLng.RAD_TO_DEG; + var lat1 = this.lat * d2r; + var lat2 = other.lat * d2r; + var dLon = (other.lng-this.lng) * d2r; + var y = Math.sin(dLon) * Math.cos(lat2); + var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon); + var brng = Math.atan2(y, x); + brng = parseInt( brng * r2d ); + brng = (brng + 360) % 360; + return brng; + }; + + function toDegrees(radians) { + return radians * 180 / Math.PI; + }; + + function toRadians(degrees) { + return degrees * Math.PI / 180; + }; // if car doesn't report heading, we calculate it from the last position if(vehicle.num_positions > 1 && vehicle.vehicle_type == 'car' && 'gps_heading' in position && position.gps_heading === "") { - var latlng = new google.maps.LatLng(position.gps_lat, position.gps_lon); - var old_latlng = new google.maps.LatLng(vehicle.curr_position.gps_lat, vehicle.curr_position.gps_lon); - position.gps_heading = google.maps.geometry.spherical.computeHeading(old_latlng, latlng); + + // Source + var startLat = toRadians(vehicle.curr_position.gps_lat); + var startLng = toRadians(vehicle.curr_position.gps_lon); + + // destination + var destLat = toRadians(position.gps_lat); + var destLng = toRadians(position.gps_lon); + + y = Math.sin(destLng - startLng) * Math.cos(destLat); + x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng); + brng = Math.atan2(y, x); + brng = toDegrees(brng); + if (brng != 0) { position.gps_heading = brng; }; } vehicle.curr_position = position; @@ -2186,14 +2304,14 @@ function addPosition(position) { // recalculate the distance in the section where we insert if(idx === 0) { - vehicle.path_length += google.maps.geometry.spherical.computeDistanceBetween(vehicle.positions[0], new_latlng); + vehicle.path_length += vehicle.positions[0].distanceTo(new_latlng); } else { // subtracked the distance between the two points where we gonna insert the new one - vehicle.path_length -= google.maps.geometry.spherical.computeDistanceBetween(vehicle.positions[idx-1], vehicle.positions[idx]); + vehicle.path_length -= vehicle.positions[idx-1].distanceTo(vehicle.positions[idx]); // calculate the distance with the new point in place - vehicle.path_length += google.maps.geometry.spherical.computeDistanceBetween(vehicle.positions[idx-1], new_latlng); - vehicle.path_length += google.maps.geometry.spherical.computeDistanceBetween(vehicle.positions[idx], new_latlng); + vehicle.path_length += vehicle.positions[idx-1].distanceTo(new_latlng); + vehicle.path_length += vehicle.positions[idx].distanceTo(new_latlng); } // insert the new position into our arrays @@ -2230,13 +2348,9 @@ function updateGraph(vcallsign, reset_selection) { if(reset_selection) { if(vcallsign !== null) delete plot_options.xaxis; - if(polyMarker) polyMarker.setPosition(null); + if(polyMarker) map.removeLayer(polyMarker); plot_crosshair_locked = false; - // reset nite overlay - nite.setDate(null); - nite.refresh(); - $("#timebox").removeClass('past').addClass('present'); updateTimebox(new Date()); } @@ -2491,7 +2605,11 @@ function graphAddPosition(vcallsign, new_data) { } var ajax_positions = null; +var ajax_positions_single = null; +var ajax_positions_old = null; var ajax_inprogress = false; +var ajax_inprogress_single = false; +var ajax_inprogress_old = "none"; function refresh() { if(ajax_inprogress) { @@ -2500,22 +2618,37 @@ function refresh() { return; } + if (ajax_inprogress_old == wvar.query) { + if (vehicles.hasOwnProperty(wvar.query)) { + return; + } + } + + if (ajax_inprogress_old != wvar.query) { + document.getElementById("timeperiod").disabled = false; + } + ajax_inprogress = true; $("#stText").text("checking |"); if(/[a-z0-9]{32}/ig.exec(wvar.query)) { - tmpC.setVisible(false); initHabitat(); return; - } else { - tmpC.setVisible(true); } var mode = wvar.mode.toLowerCase(); mode = (mode == "position") ? "latest" : mode.replace(/ /g,""); - var data_str = "mode="+mode+"&type=positions&format=json&max_positions=" + max_positions + "&position_id=" + position_id + "&vehicles=" + encodeURIComponent(wvar.query); + if (wvar.query) { + if (sondePrefix.indexOf(wvar.query) > -1) { + var data_str = "mode="+mode+"&type=positions&format=json&max_positions=" + max_positions + "&position_id=" + position_id + "&vehicles="; + } else { + var data_str = "mode=3days&type=positions&format=json&max_positions=" + max_positions + "&position_id=0&vehicles=" + encodeURIComponent(wvar.query); + } + } else { + var data_str = "mode="+mode+"&type=positions&format=json&max_positions=" + max_positions + "&position_id=" + position_id + "&vehicles=" + encodeURIComponent(wvar.query); + } ajax_positions = $.ajax({ type: "GET", @@ -2525,7 +2658,22 @@ function refresh() { success: function(response, textStatus) { $("#stText").text("loading |"); response.fetch_timestamp = Date.now(); - update(response); + if (sondePrefix.indexOf(wvar.query) > -1) { + ajax_inprogress_old = "none"; + update(response); + } else if (wvar.query != null) { + if (JSON.stringify(response).indexOf(wvar.query) == -1) { + //check using new API + ajax_inprogress = false; + refreshSingleOld(wvar.query); + } else { + ajax_inprogress_old = wvar.query; + update(response); + } + } else { + ajax_inprogress_old = "none"; + update(response); + } $("#stText").text(""); $("#stTimer").attr("data-timestamp", response.fetch_timestamp); }, @@ -2538,20 +2686,178 @@ function refresh() { $("#stText").text("no connection |"); $("#stTimer").attr("data-timestamp", data.fetch_timestamp); } + + ajax_inprogress = false; }, complete: function(request, textStatus) { + if (ajax_inprogress_old != wvar.query) { + document.getElementById("timeperiod").disabled = false; + } clearTimeout(periodical); periodical = setTimeout(refresh, timer_seconds * 1000); } }); } +function refreshSingle(serial, first) { + if(ajax_inprogress_single) { + clearTimeout(periodical_focus); + if (first) { + periodical_focus = setTimeout(refreshSingle, 2000, serial, first); + } else { + periodical_focus = setTimeout(refreshSingle, 2000, serial); + } + return; + } + + if (ajax_inprogress_old == wvar.query) { + if (vehicles.hasOwnProperty(wvar.query)) { + return; + } + } + + if (first === undefined) { + first = false; + } + + ajax_inprogress_single = true; + + var mode = wvar.mode.toLowerCase(); + mode = (mode == "position") ? "latest" : mode.replace(/ /g,""); + + if (first){ + var data_str = "mode="+mode+"&type=positions&format=json&max_positions=" + max_positions + "&position_id=0&vehicles=" + encodeURIComponent(serial); + } else { + var data_str = "mode="+mode+"&type=positions&format=json&max_positions=" + max_positions + "&position_id=" + position_id + "&vehicles=" + encodeURIComponent(serial); + } + + ajax_positions_single = $.ajax({ + type: "GET", + url: data_url, + data: data_str, + dataType: "json", + success: function(response, textStatus) { + response.fetch_timestamp = Date.now(); + if (!first) {update(response, false);} else { + update(response, true); + } + }, + complete: function(request, textStatus) { + clearTimeout(periodical_focus); + periodical_focus = setTimeout(refreshSingle, timer_seconds_focus * 1000, serial); + } + }); +} + +function refreshSingleOld(serial) { + + if (ajax_inprogress_old == wvar.query) { + return; + } + + document.getElementById("timeperiod").disabled = true; + + var data_url = "https://api.v2.sondehub.org/sonde/" + encodeURIComponent(serial); + + ajax_inprogress_old = serial; + + ajax_positions_old = $.ajax({ + type: "GET", + url: data_url, + dataType: "json", + success: function(data, textStatus) { + var response = {}; + response.positions = {}; + var dataTemp = []; + for (var i = data.length - 1; i >= 0; i--) { + if (data[i].hasOwnProperty('subtype')) { + if (data[i].subtype != "SondehubV1") { + var dataTempEntry = {}; + var station = data[i].uploader_callsign; + dataTempEntry.callsign = {}; + dataTempEntry.callsign[station] = {}; + dataTempEntry.callsign[station].snr = data[i].snr; + dataTempEntry.callsign[station].rssi = data[i].rssi; + dataTempEntry.gps_alt = data[i].alt; + dataTempEntry.gps_heading = data[i].heading; + dataTempEntry.gps_lat = data[i].lat; + dataTempEntry.gps_lon = data[i].lon; + dataTempEntry.gps_time = data[i].datetime; + dataTempEntry.server_time = data[i].datetime; + dataTempEntry.vehicle = data[i].serial; + dataTempEntry.position_id = data[i].serial + "-" + data[i].datetime; + dataTempEntry.data = {}; + if (data[i].batt) { + dataTempEntry.data.batt = data[i].batt; + } + if (data[i].burst_timer) { + dataTempEntry.data.burst_timer = data[i].burst_timer; + } + if (data[i].frequency) { + dataTempEntry.data.burst_timer = data[i].frequency; + } + if (data[i].humidity) { + dataTempEntry.data.humidity = data[i].humidity; + } + if (data[i].manufacturer) { + dataTempEntry.data.manufacturer = data[i].manufacturer; + } + if (data[i].sats) { + dataTempEntry.data.sats = data[i].sats; + } + if (data[i].temp) { + dataTempEntry.data.temperature_external = data[i].temp; + } + if (data[i].type) { + dataTempEntry.data.type = data[i].type; + dataTempEntry.type = data[i].type; + } + if (data[i].pressure) { + dataTempEntry.data.pressure = data[i].pressure; + } + if (data[i].xdata) { + dataTempEntry.data.xdata = data[i].xdata; + } + dataTemp.push(dataTempEntry); + } else { + var dataTempEntry = {}; + var station = data[i].uploader_callsign; + dataTempEntry.callsign = {}; + dataTempEntry.callsign[station] = {}; + dataTempEntry.gps_alt = parseFloat(data[i].alt); + dataTempEntry.gps_lat = parseFloat(data[i].lat); + dataTempEntry.gps_lon = parseFloat(data[i].lon); + dataTempEntry.gps_time = data[i].time_received; + dataTempEntry.server_time = data[i].time_received; + dataTempEntry.vehicle = data[i].serial; + dataTempEntry.position_id = data[i].serial + "-" + data[i].time_received; + dataTempEntry.data = {}; + if (data[i].humidity) { + dataTempEntry.data.humidity = parseFloat(data[i].humidity); + } + if (data[i].temp) { + dataTempEntry.data.temperature_external = parseFloat(data[i].temp); + } + dataTemp.push(dataTempEntry); + } + } + } + response.positions.position = dataTemp; + response.fetch_timestamp = Date.now(); + if (response.positions.position.length == 0) { + update(response); + } else { + update(response, "old"); + } + + } + }); +} + function refreshReceivers() { // if options to hide receivers is selected do nothing if(offline.get('opt_hide_receivers')) return; - //if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'ajax', 'refresh', 'Recievers']); - $.ajax({ type: "GET", url: receivers_url, @@ -2616,7 +2922,6 @@ function initRecoveryPane() { var ajax_predictions = null; function refreshPredictions() { - //if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'ajax', 'refresh', 'Predictions']); if(ajax_inprogress) { clearTimeout(periodical_predictions); periodical_predictions = setTimeout(refreshPredictions, 1000); @@ -2808,13 +3113,15 @@ function habitat_doc_step(hab_docs) { } -var periodical, periodical_receivers, periodical_recoveries; +var periodical, periodical_focus, periodical_receivers, periodical_recoveries; var periodical_predictions = null; var timer_seconds = 5; +var timer_seconds_focus = 1; function startAjax() { // prevent insane clicks to start numerous requests clearTimeout(periodical); + clearTimeout(periodical_focus); clearTimeout(periodical_receivers); clearTimeout(periodical_recoveries); clearTimeout(periodical_predictions); @@ -2831,8 +3138,18 @@ function startAjax() { function stopAjax() { // stop our timed ajax clearTimeout(periodical); + periodical = null; + ajax_inprogress = false; if(ajax_positions) ajax_positions.abort(); + clearTimeout(periodical_focus); + periodical_focus = null; + ajax_inprogress_single = false; + if(ajax_positions_single) ajax_positions_single.abort(); + + if(ajax_positions_old) ajax_positions_old.abort(); + ajax_inprogress_old = "none"; + clearTimeout(periodical_predictions); periodical_predictions = null; if(ajax_predictions) ajax_predictions.abort(); @@ -2841,59 +3158,58 @@ function stopAjax() { var currentPosition = null; function updateCurrentPosition(lat, lon) { - var latlng = new google.maps.LatLng(lat, lon); + var latlng = new L.LatLng(lat, lon); if(!currentPosition) { currentPosition = {marker: null, lat: lat, lon: lon}; - currentPosition.marker = new google.maps.Marker({ - icon: { - url: "img/marker-you.png", - size: new google.maps.Size(21,50), - scaledSize: new google.maps.Size(21,50), - anchor: new google.maps.Point(10,50) - }, - zIndex: Z_ME, - position: latlng, - map: map, - optimized: false, + youIcon = new L.icon({ + iconUrl: "img/marker-you.png", + iconSize: [21, 50], + iconAnchor: [10, 50] + }); + currentPosition.marker = new L.Marker(latlng, { + icon: youIcon, title: "Your current position", - animation: google.maps.Animation.DROP + zIndexOffset: Z_ME, }); } else { currentPosition.lat = lat; currentPosition.lon = lon; - currentPosition.marker.setMap(map); - currentPosition.marker.setPosition(latlng); + currentPosition.marker.addTo(map); + currentPosition.marker.setLatLng(latlng); } } function updateReceiverMarker(receiver) { - var latlng = new google.maps.LatLng(receiver.lat, receiver.lon); + var latlng = new L.LatLng(receiver.lat, receiver.lon); // init a marker if the receiver doesn't already have one if(!receiver.marker) { - receiver.marker = new google.maps.Marker({ - icon: { - url: host_url + markers_url + "antenna-green.png", - size: new google.maps.Size(26,34), - scaledSize: new google.maps.Size(26,34), - anchor: new google.maps.Point(13,34), - }, - zIndex: Z_STATION, - position: latlng, - map: map, - optimized: false, + + if (!receiver.description.includes("radiosonde_auto_rx")) { + //future option to show different icon per software + } + + receiverIcon = new L.icon({ + iconUrl: host_url + markers_url + "antenna-green.png", + iconSize: [26, 34], + iconAnchor: [13, 34], + popupAnchor: [0, -34] + }), + + receiver.marker = new L.Marker(latlng, { + icon: receiverIcon, title: receiver.name, - animation: google.maps.Animation.DROP - }); - receiver.infobox = new google.maps.InfoWindow({ - content: receiver.description - }); - receiver.infobox_handle = google.maps.event.addListener(receiver.marker, 'click', function() { - receiver.infobox.open(map, receiver.marker); + zIndexOffset: Z_STATION, }); + + receiver.infobox = new L.popup({ autoClose: false, closeOnClick: false }).setContent(receiver.description); + + receiver.marker.bindPopup(receiver.infobox); + + receiverCanvas.addMarker(receiver.marker); } else { - receiver.marker.setPosition(latlng); + receiver.marker.setLatLng(latlng); } } @@ -2942,10 +3258,8 @@ function updateReceivers(r) { i++; } else { - // close box, remove event handle, and remove marker - e.infobox.close(); - e.infobox_handle.remove(); - e.marker.setMap(null); + map.removeLayer(e.infobox); + receiverCanvas.removeMarker(e.marker); // remove from arrays receivers.splice(i,1); @@ -2957,35 +3271,32 @@ function updateReceivers(r) { } function updateRecoveryMarker(recovery) { - var latlng = new google.maps.LatLng(recovery.lat, recovery.lon); + var latlng = new L.LatLng(recovery.lat, recovery.lon); // init a marker if the recovered payload doesn't already have one if(!recovery.marker) { if(recovery.recovered == true){ - _recovery_icon = "payload-recovered.png"; + _recovery_icon = host_url + markers_url + "payload-recovered.png"; }else{ - _recovery_icon = "payload-not-recovered.png"; + _recovery_icon = host_url + markers_url + "payload-not-recovered.png"; } - recovery.marker = new google.maps.Marker({ - icon: { - url: host_url + markers_url + _recovery_icon, - size: new google.maps.Size(17,19), - scaledSize: new google.maps.Size(17,18), - anchor: new google.maps.Point(8,14) - }, - zIndex: Z_RECOVERY, - position: latlng, - map: map, - optimized: false, - title: recovery.serial, - animation: google.maps.Animation.DROP + recoveryIcon = new L.icon({ + iconUrl: _recovery_icon, + iconSize: [17, 19], + iconAnchor: [8, 14], + popupAnchor: [0, -19] }); - recovery.infobox = new google.maps.InfoWindow({ - content: recovery.description - }); + recovery.marker = new L.Marker(latlng, { + icon: recoveryIcon, + title: recovery.serial, + zIndexOffset: Z_RECOVERY, + }).addTo(map); + recovery.infobox = new L.popup({ autoClose: false, closeOnClick: false }).setContent(recovery.description); + + recovery.marker.bindPopup(recovery.infobox); div = document.createElement('div'); @@ -3001,7 +3312,7 @@ function updateRecoveryMarker(recovery) { html += "
Time: "+formatDate(stringToDateUTC(recovery.datetime))+"
"; html += "
Reported by: "+recovery.recovered_by+"
"; html += "
Notes: "+$('
').text(recovery.description).html()+"
"; - html += "
Flight Path: "+recovery.serial+"
"; + html += "
Flight Path: "+recovery.serial+"
"; html += "
"; @@ -3009,11 +3320,8 @@ function updateRecoveryMarker(recovery) { recovery.infobox.setContent(div); - recovery.infobox_handle = google.maps.event.addListener(recovery.marker, 'click', function() { - recovery.infobox.open(map, recovery.marker); - }); } else { - recovery.marker.setPosition(latlng); + recovery.marker.setLatLng(latlng); } } @@ -3061,9 +3369,9 @@ function updateRecoveryMarker(recovery) { } else { // close box, remove event handle, and remove marker - e.infobox.close(); - e.infobox_handle.remove(); - e.marker.setMap(null); + //e.infobox.close(); + //e.infobox_handle.remove(); + map.removeLayer(e.marker); // remove from arrays recoveries.splice(i,1); @@ -3097,7 +3405,7 @@ function updateRecoveryPane(r){ html += "
"; html += "
"+r[i].serial+(r[i].recovered ? " Recovered by " : " Not Recovered by ")+r[i].recovered_by+"
"; - html += "
 "+roundNumber(lat, 5) + ', ' + roundNumber(lon, 5)+"
"; + html += "
 "+roundNumber(lat, 5) + ', ' + roundNumber(lon, 5)+"
"; var imp = offline.get('opt_imperial'); var text_alt = Number((imp) ? Math.floor(3.2808399 * parseInt(alt)) : parseInt(alt)).toLocaleString("us"); @@ -3106,7 +3414,7 @@ function updateRecoveryPane(r){ html += "
Time: "+formatDate(stringToDateUTC(r[i].datetime))+"
"; html += "
Reported by: "+r[i].recovered_by+"
"; html += "
Notes: "+$('
').text(r[i].description).html()+"
"; - html += "
Flight Path: "+r[i].serial+"
"; + html += "
Flight Path: "+r[i].serial+"
"; html += "
"; html += "
"; } @@ -3152,7 +3460,6 @@ function refreshUI() { updateVehicleInfo(vcallsign, vehicles[vcallsign].curr_position); } - mapInfoBox.close(); if(follow_vehicle !== null) update_lookangles(follow_vehicle); } @@ -3160,20 +3467,35 @@ function refreshUI() { function hideHorizonRings(){ for(var vcallsign in vehicles) { if(vehicles[vcallsign].vehicle_type == "balloon"){ - vehicles[vcallsign].horizon_circle.setVisible(false); - vehicles[vcallsign].horizon_circle.label.set('visible', false); - vehicles[vcallsign].subhorizon_circle.setVisible(false); - vehicles[vcallsign].subhorizon_circle.label.set('visible', false); + map.removeLayer(vehicles[vcallsign].horizon_circle); + map.removeLayer(vehicles[vcallsign].subhorizon_circle); + map.removeLayer(vehicles[vcallsign].horizon_circle_title); + map.removeLayer(vehicles[vcallsign].subhorizon_circle_title); } } } function showHorizonRings(){ for(var vcallsign in vehicles) { if(vehicles[vcallsign].vehicle_type == "balloon"){ - vehicles[vcallsign].horizon_circle.setVisible(true); - vehicles[vcallsign].horizon_circle.label.set('visible', true); - vehicles[vcallsign].subhorizon_circle.setVisible(true); - vehicles[vcallsign].subhorizon_circle.label.set('visible', true); + map.addLayer(vehicles[vcallsign].horizon_circle); + map.addLayer(vehicles[vcallsign].subhorizon_circle); + map.addLayer(vehicles[vcallsign].horizon_circle_title); + map.addLayer(vehicles[vcallsign].subhorizon_circle_title); + } + } +} + +function hideTitles(){ + for(var vcallsign in vehicles) { + if(vehicles[vcallsign].vehicle_type == "balloon" || vehicles[vcallsign].vehicle_type == "car"){ + vehicles[vcallsign].title.unbindTooltip(); + } + } +} +function showTitles(){ + for(var vcallsign in vehicles) { + if(vehicles[vcallsign].vehicle_type == "balloon" || vehicles[vcallsign].vehicle_type == "car"){ + vehicles[vcallsign].title = vehicles[vcallsign].marker.bindTooltip(vehicles[vcallsign]["marker"]["options"]["title"], {direction: 'center', permanent: 'true', className: 'serialtooltip'}); } } } @@ -3182,18 +3504,34 @@ var ssdv = {}; var status = ""; var bs_idx = 0; -function update(response) { +function update(response, flag) { if (response === null || !response.positions || !response.positions.position || !response.positions.position.length) { - // if no vehicles are found, this will remove the spinner and put a friendly message - $("#main .empty").html("No vehicles :("); + if (flag != "old") { + // if no vehicles are found, this will remove the spinner and put a friendly message + $("#main .empty").html("No vehicles :("); - ajax_inprogress = false; + if (flag === undefined) { + ajax_inprogress = false; + } else { + ajax_inprogress_single = false; + } - return; + return; + } + } + + if (sondePrefix.indexOf(wvar.query) > -1) { + for (var i = response.positions.position.length - 1; i >= 0; i--) { + try { + if (!response.positions.position[i].type.includes(wvar.query)) { + response.positions.position.splice(i, 1) + } + } catch (e) {} + } } ssdv = (!response.ssdv) ? {} : response.ssdv; @@ -3253,7 +3591,8 @@ function update(response) { if(vehicle === undefined) return; if(vehicle.updated) { - updatePolyline(vcallsign); + updatePolyline(vcallsign, flag); + updateVehicleInfo(vcallsign, vehicle.curr_position); // remember last position for each vehicle @@ -3288,7 +3627,13 @@ function update(response) { if(periodical_predictions === null) refreshPredictions(); - ajax_inprogress = false; + if (flag != "old") { + if (flag === undefined) { + ajax_inprogress = false; + } else { + ajax_inprogress_single = false; + } + } } }; @@ -3320,7 +3665,7 @@ function zoom_on_payload() { if(fallback) { if(target) { // find the bounds of the ballons first and last positions - var bounds = new google.maps.LatLngBounds(); + var bounds = new L.LatLngBounds(); bounds.extend(target.positions[0]); bounds.extend(target.positions[target.positions.length - 1]); diff --git a/launchSites.json b/launchSites.json new file mode 100644 index 0000000..7f4d085 --- /dev/null +++ b/launchSites.json @@ -0,0 +1,705 @@ +[ +{ "station": "01001", "rs_types": ["77", "23"], "lat": 70.93, "lon": -8.67, "alt": 10, "station_name": "Jan Mayen (Norway)" }, +{ "station": "01004", "rs_types": ["23"], "lat": 78.92, "lon": 11.93, "alt": 8, "station_name": "Ny-Alesund Ii (Norway)" }, +{ "station": "01010", "rs_types": ["24"], "lat": 69.31, "lon": 16.13, "alt": 3, "station_name": "Andoya (Norway)" }, +{ "station": "01028", "rs_types": ["23"], "lat": 74.50, "lon": 19.00, "alt": 20, "station_name": "Bjornoya (Norway)" }, +{ "station": "01241", "rs_types": ["77"], "lat": 63.70, "lon": 9.61, "alt": 30, "station_name": "Orland (Norway)" }, +{ "station": "01400", "rs_types": ["23"], "lat": 56.54, "lon": 3.22, "alt": 70, "station_name": "Ekofisk (Norway)" }, +{ "station": "01415", "rs_types": ["24"], "lat": 58.87, "lon": 5.66, "alt": 37, "station_name": "Sola (Norway)" }, +{ "station": "02836", "rs_types": ["24", "42"], "lat": 67.37, "lon": 26.65, "alt": 179, "station_name": "Sodankyla (Finland)" }, +{ "station": "03005", "rs_types": ["41"], "lat": 60.13, "lon": -1.18, "alt": 82, "station_name": "Lerwick (United Kingdom)" }, +{ "station": "03238", "rs_types": ["42"], "lat": 55.02, "lon": -1.87, "alt": 145, "station_name": "Albemarle (United Kingdom)" }, +{ "station": "03354", "rs_types": ["42"], "lat": 53.00, "lon": -1.25, "alt": 117, "station_name": "Nottingham Weather Centre (United Kingdom)" }, +{ "station": "03502", "rs_types": ["41"], "lat": 52.13, "lon": -4.57, "alt": 133, "station_name": "Aberporth (United Kingdom)" }, +{ "station": "03743", "rs_types": ["41"], "lat": 51.20, "lon": -1.80, "alt": 132, "station_name": "Larkhill (United Kingdom)" }, +{ "station": "03808", "rs_types": ["41"], "lat": 50.22, "lon": -5.32, "alt": 87, "station_name": "Camborne (United Kingdom)" }, +{ "station": "03882", "rs_types": ["42"], "lat": 50.90, "lon": 0.32, "alt": 52, "station_name": "Herstmonceux (United Kingdom)" }, +{ "station": "03918", "rs_types": ["42"], "lat": 54.30, "lon": -6.20, "alt": 18, "station_name": "Castor Bay (United Kingdom)" }, +{ "station": "03953", "rs_types": ["42", "41"], "lat": 51.93, "lon": -10.25, "alt": 9, "station_name": "Valentia Observatory (Ireland)" }, +{ "station": "04018", "rs_types": ["42"], "lat": 63.97, "lon": -22.60, "alt": 52, "station_name": "Keflavikurflugvollur (Iceland)" }, +{ "station": "04220", "rs_types": ["41"], "lat": 68.70, "lon": -52.75, "alt": 43, "station_name": "Egedesminde (Greenland)" }, +{ "station": "04270", "rs_types": ["41"], "lat": 61.17, "lon": -45.42, "alt": 34, "station_name": "Narsarsuaq (Greenland)" }, +{ "station": "04320", "rs_types": ["41"], "lat": 76.77, "lon": -18.67, "alt": 11, "station_name": "Danmarkshavn (Greenland)" }, +{ "station": "04339", "rs_types": ["41"], "lat": 70.48, "lon": -21.95, "alt": 65, "station_name": "Scoresbysund (Greenland)" }, +{ "station": "04360", "rs_types": ["77"], "lat": 65.60, "lon": -37.63, "alt": 50, "station_name": "Tasiilaq (Greenland)" }, +{ "station": "04417", "rs_types": ["23"], "lat": 72.57, "lon": -38.45, "alt": 3255, "station_name": "Geosummit (Greenland)" }, +{ "station": "06011", "rs_types": ["07"], "lat": 62.02, "lon": -6.77, "alt": 54, "station_name": "Thorshavn (Denmark)" }, +{ "station": "06260", "rs_types": ["23"], "lat": 52.10, "lon": 5.18, "alt": 2, "station_name": "De Bilt (Netherlands)" }, +{ "station": "06447", "rs_types": ["23"], "lat": 50.80, "lon": 4.35, "alt": 100, "station_name": "Uccle (Belgium)" }, +{ "station": "06458", "rs_types": ["34"], "lat": 50.75, "lon": 4.77, "alt": 105, "station_name": "Beauvechain (Belgium)" }, +{ "station": "07110", "rs_types": ["77"], "lat": 48.45, "lon": -4.42, "alt": 99, "station_name": "Brest (France)" }, +{ "station": "07145", "rs_types": ["77"], "lat": 48.77, "lon": 2.02, "alt": 168, "station_name": "Trappes (France)" }, +{ "station": "07510", "rs_types": ["77"], "lat": 44.83, "lon": -0.70, "alt": 49, "station_name": "Bordeaux / Merignac (France)" }, +{ "station": "07645", "rs_types": ["77"], "lat": 43.87, "lon": 4.40, "alt": 60, "station_name": "Nimes / Courbessac (France)" }, +{ "station": "07761", "rs_types": ["77"], "lat": 41.92, "lon": 8.80, "alt": 6, "station_name": "Ajaccio (France)" }, +{ "station": "08001", "rs_types": ["24"], "lat": 43.37, "lon": -8.42, "alt": 58, "station_name": "La Coruna (Spain)" }, +{ "station": "08023", "rs_types": ["23"], "lat": 43.47, "lon": -3.82, "alt": 64, "station_name": "Santander (Spain)" }, +{ "station": "08190", "rs_types": ["77"], "lat": 41.38, "lon": 2.12, "alt": 95, "station_name": "Barcelona Servei (Spain)" }, +{ "station": "08221", "rs_types": ["24"], "lat": 40.45, "lon": -3.55, "alt": 609, "station_name": "Madrid / Barajas (Spain)" }, +{ "station": "08302", "rs_types": ["24"], "lat": 39.60, "lon": 2.70, "alt": 41, "station_name": "Mallorca-Son Bonet (Spain)" }, +{ "station": "08383", "rs_types": ["24"], "lat": 37.28, "lon": -6.92, "alt": 20, "station_name": "Huelva (Spain)" }, +{ "station": "08430", "rs_types": ["77"], "lat": 38.00, "lon": -1.17, "alt": 61, "station_name": "Murcia (Spain)" }, +{ "station": "08508", "rs_types": ["23"], "lat": 38.73, "lon": -27.07, "alt": 112, "station_name": "Lajes / Santa Rita Acores (Portugal)" }, +{ "station": "08522", "rs_types": ["23"], "lat": 32.63, "lon": -16.90, "alt": 58, "station_name": "Funchal (Portugal)" }, +{ "station": "08536", "rs_types": ["24"], "lat": 38.77, "lon": -9.13, "alt": 114, "station_name": "Lisboa / Portela (Portugal)" }, +{ "station": "10035", "rs_types": ["24"], "lat": 54.53, "lon": 9.55, "alt": 43, "station_name": "Schleswig (Germany)" }, +{ "station": "10113", "rs_types": ["24"], "lat": 53.72, "lon": 7.15, "alt": 11, "station_name": "Norderney (Germany)" }, +{ "station": "10184", "rs_types": ["24"], "lat": 54.10, "lon": 13.40, "alt": 2, "station_name": "Greifswald (Germany)" }, +{ "station": "10238", "rs_types": ["23"], "lat": 52.82, "lon": 9.93, "alt": 70, "station_name": "Bergen-Hohne (Germany)" }, +{ "station": "10304", "rs_types": ["23"], "lat": 52.72, "lon": 7.32, "alt": 21, "station_name": "Meppen (Germany)" }, +{ "station": "10393", "rs_types": ["23"], "lat": 52.22, "lon": 14.12, "alt": 98, "station_name": "Lindenberg (Germany)" }, +{ "station": "10410", "rs_types": ["24"], "lat": 51.40, "lon": 6.97, "alt": 152, "station_name": "Essen (Germany)" }, +{ "station": "10548", "rs_types": ["24"], "lat": 50.57, "lon": 10.38, "alt": 450, "station_name": "Meiningen (Germany)" }, +{ "station": "10618", "rs_types": ["23"], "lat": 49.70, "lon": 7.33, "alt": 376, "station_name": "Idar-Oberstein (Germany)" }, +{ "station": "10739", "rs_types": ["24"], "lat": 48.83, "lon": 9.20, "alt": 314, "station_name": "Stuttgart / Schnarrenberg (Germany)" }, +{ "station": "10771", "rs_types": ["23"], "lat": 49.43, "lon": 11.90, "alt": 419, "station_name": "Kuemmersbruck (Germany)" }, +{ "station": "10868", "rs_types": ["24"], "lat": 48.25, "lon": 11.55, "alt": 484, "station_name": "Muenchen-Oberschleissheim (Germany)" }, +{ "station": "10954", "rs_types": ["23"], "lat": 47.83, "lon": 10.87, "alt": 739, "station_name": "Altenstadt (Germany)" }, +{ "station": "10962", "rs_types": ["23", "14"], "lat": 47.80, "lon": 11.02, "alt": 977, "station_name": "Hohenpeissenberg (Germany)" }, +{ "station": "11010", "rs_types": ["42"], "lat": 48.23, "lon": 14.18, "alt": 298, "station_name": "Linz / Hoersching-Flughafen (Austria)" }, +{ "station": "11035", "rs_types": ["41", "63"], "lat": 48.25, "lon": 16.37, "alt": 203, "station_name": "Wien / Hohe Warte (Austria)" }, +{ "station": "11120", "rs_types": ["42"], "lat": 47.27, "lon": 11.35, "alt": 581, "station_name": "Innsbruck-Flughafen (Austria)" }, +{ "station": "11240", "rs_types": ["42"], "lat": 47.00, "lon": 15.43, "alt": 340, "station_name": "Graz-Thalerhof-Flughafen (Austria)" }, +{ "station": "11520", "rs_types": ["23", "41"], "lat": 50.02, "lon": 14.45, "alt": 304, "station_name": "Praha-Libus (Czech Republic)" }, +{ "station": "11747", "rs_types": ["23"], "lat": 49.45, "lon": 17.13, "alt": 216, "station_name": "Prostejov (Czech Republic)" }, +{ "station": "11952", "rs_types": ["23"], "lat": 49.05, "lon": 20.53, "alt": 706, "station_name": "Poprad / Ganovce (Slovakia)" }, +{ "station": "12120", "rs_types": ["41"], "lat": 54.75, "lon": 17.53, "alt": 2, "station_name": "Leba (Poland)" }, +{ "station": "12374", "rs_types": ["41"], "lat": 52.40, "lon": 20.97, "alt": 94, "station_name": "Legionowo (Poland)" }, +{ "station": "12425", "rs_types": ["41"], "lat": 51.13, "lon": 16.98, "alt": 116, "station_name": "Wroclaw I (Poland)" }, +{ "station": "12843", "rs_types": ["24"], "lat": 47.43, "lon": 19.18, "alt": 138, "station_name": "Budapest / Lorinc (Hungary)" }, +{ "station": "12982", "rs_types": ["24"], "lat": 46.25, "lon": 20.10, "alt": 82, "station_name": "Szeged (Hungary)" }, +{ "station": "13275", "rs_types": ["77"], "lat": 44.77, "lon": 20.42, "alt": 203, "station_name": "Beograd / Kosutnjak (Serbia)" }, +{ "station": "13388", "rs_types": ["77"], "lat": 43.33, "lon": 21.90, "alt": 202, "station_name": "Nis (Serbia)" }, +{ "station": "14015", "rs_types": ["63"], "lat": 46.07, "lon": 14.52, "alt": 299, "station_name": "Ljubljana / Bezigrad (Slovenia)" }, +{ "station": "14240", "rs_types": ["41"], "lat": 45.82, "lon": 16.03, "alt": 123, "station_name": "Zagreb / Maksimir (Croatia)" }, +{ "station": "14430", "rs_types": ["42"], "lat": 44.10, "lon": 15.35, "alt": 79, "station_name": "Zadar Rs (Croatia)" }, +{ "station": "15420", "rs_types": ["41"], "lat": 44.50, "lon": 26.13, "alt": 90, "station_name": "Bucuresti / Imh (Romania)" }, +{ "station": "15614", "rs_types": ["41"], "lat": 42.65, "lon": 23.38, "alt": 586, "station_name": "Sofia Observ. (Bulgaria)" }, +{ "station": "16045", "rs_types": ["41", "42"], "lat": 45.98, "lon": 13.03, "alt": 51, "station_name": "Udine / Rivolto (Italy)" }, +{ "station": "16064", "rs_types": ["42"], "lat": 45.52, "lon": 8.67, "alt": 178, "station_name": "Novara / Cameri (Italy)" }, +{ "station": "16113", "rs_types": ["42"], "lat": 44.53, "lon": 7.62, "alt": 386, "station_name": "Cuneo-Levaldigi (Italy)" }, +{ "station": "16144", "rs_types": ["24"], "lat": 44.65, "lon": 11.62, "alt": 10, "station_name": "S. Pietro Capofiume Molinella Bologna (Italy)" }, +{ "station": "16245", "rs_types": ["42"], "lat": 41.65, "lon": 12.45, "alt": 6, "station_name": "Pratica Di Mare (Italy)" }, +{ "station": "16332", "rs_types": ["42"], "lat": 40.23, "lon": 18.15, "alt": 48, "station_name": "Lecce (Italy)" }, +{ "station": "16429", "rs_types": ["41"], "lat": 37.92, "lon": 12.50, "alt": 7, "station_name": "Trapani / Birgi (Italy)" }, +{ "station": "16546", "rs_types": ["41"], "lat": 39.35, "lon": 8.97, "alt": 29, "station_name": "Decimomannu (Italy)" }, +{ "station": "16622", "rs_types": ["23"], "lat": 40.52, "lon": 22.97, "alt": 8, "station_name": "Thessaloniki Airport (Greece)" }, +{ "station": "16716", "rs_types": ["23"], "lat": 37.90, "lon": 23.73, "alt": 28, "station_name": "Athens (closed) Airport (Greece)" }, +{ "station": "16754", "rs_types": ["23"], "lat": 35.33, "lon": 25.18, "alt": 37, "station_name": "Heraklion Airport (Greece)" }, +{ "station": "17030", "rs_types": ["54"], "lat": 41.28, "lon": 36.33, "alt": 4, "station_name": "Samsun (Turkey)" }, +{ "station": "17095", "rs_types": ["54", "35"], "lat": 39.90, "lon": 41.28, "alt": 1869, "station_name": "Erzurum Bolge (Turkey)" }, +{ "station": "17130", "rs_types": ["54"], "lat": 39.95, "lon": 32.88, "alt": 891, "station_name": "Ankara / Central (Turkey)" }, +{ "station": "17220", "rs_types": ["54"], "lat": 38.43, "lon": 27.17, "alt": 25, "station_name": "Izmir (Turkey)" }, +{ "station": "17240", "rs_types": ["54"], "lat": 37.75, "lon": 30.55, "alt": 997, "station_name": "Isparta (Turkey)" }, +{ "station": "17351", "rs_types": ["54"], "lat": 37.05, "lon": 35.35, "alt": 28, "station_name": "Adana (Turkey)" }, +{ "station": "17607", "rs_types": ["23"], "lat": 35.15, "lon": 33.40, "alt": 162, "station_name": "Athalassa (Cyprus)" }, +{ "station": "20046", "rs_types": ["19", "62"], "lat": 80.62, "lon": 58.05, "alt": 20, "station_name": "Polargmo Im. E. T. Krenkelja (Russia)" }, +{ "station": "20292", "rs_types": ["90", "62"], "lat": 77.72, "lon": 104.30, "alt": 13, "station_name": "Gmo Im. E. K. Fedorova (Russia)" }, +{ "station": "20674", "rs_types": ["19"], "lat": 73.50, "lon": 80.40, "alt": 47, "station_name": "Ostrov Dikson (Russia)" }, +{ "station": "20744", "rs_types": ["62", "90"], "lat": 72.37, "lon": 52.70, "alt": 16, "station_name": "Malye Karmakuly (Russia)" }, +{ "station": "21432", "rs_types": ["90"], "lat": 76.00, "lon": 137.87, "alt": 10, "station_name": "Ostrov Kotel'Nyj (Russia)" }, +{ "station": "21824", "rs_types": ["62", "90"], "lat": 71.58, "lon": 128.92, "alt": 8, "station_name": "Tiksi (Russia)" }, +{ "station": "21946", "rs_types": ["90", "58"], "lat": 70.62, "lon": 147.88, "alt": 61, "station_name": "Chokurdah (Russia)" }, +{ "station": "22217", "rs_types": ["90"], "lat": 67.15, "lon": 32.35, "alt": 26, "station_name": "Kandalaksa (Russia)" }, +{ "station": "22271", "rs_types": ["90"], "lat": 67.88, "lon": 44.13, "alt": 16, "station_name": "Sojna (Russia)" }, +{ "station": "22522", "rs_types": ["69", "90", "89"], "lat": 64.98, "lon": 34.80, "alt": 10, "station_name": "Kem'- Port (Russia)" }, +{ "station": "22543", "rs_types": ["62", "90"], "lat": 64.62, "lon": 40.50, "alt": 6, "station_name": "Arhangel'sk (Russia)" }, +{ "station": "22820", "rs_types": ["62"], "lat": 61.82, "lon": 34.27, "alt": 112, "station_name": "Petrozavodsk (Russia)" }, +{ "station": "22845", "rs_types": ["90"], "lat": 61.50, "lon": 38.93, "alt": 121, "station_name": "Kargopol (Russia)" }, +{ "station": "23078", "rs_types": ["90"], "lat": 69.33, "lon": 88.10, "alt": 62, "station_name": "Norilsk (Russia)" }, +{ "station": "23205", "rs_types": ["19"], "lat": 67.63, "lon": 53.03, "alt": 7, "station_name": "Nar'Jan-Mar (Russia)" }, +{ "station": "23330", "rs_types": ["68", "62"], "lat": 66.53, "lon": 66.67, "alt": 16, "station_name": "Salehard (Russia)" }, +{ "station": "23415", "rs_types": ["90", "62"], "lat": 65.12, "lon": 57.10, "alt": 56, "station_name": "Pechora (Russia)" }, +{ "station": "23472", "rs_types": ["19"], "lat": 65.78, "lon": 87.93, "alt": 32, "station_name": "Turuhansk (Russia)" }, +{ "station": "23802", "rs_types": ["90"], "lat": 61.67, "lon": 50.77, "alt": 116, "station_name": "Syktyvkar (Russia)" }, +{ "station": "23884", "rs_types": ["19", "90"], "lat": 61.60, "lon": 90.02, "alt": 63, "station_name": "Bor (Russia)" }, +{ "station": "23921", "rs_types": ["62"], "lat": 60.68, "lon": 60.45, "alt": 101, "station_name": "Ivdel (Russia)" }, +{ "station": "23933", "rs_types": ["62", "90"], "lat": 61.02, "lon": 69.03, "alt": 40, "station_name": "Hanty-Mansijsk (Russia)" }, +{ "station": "23955", "rs_types": ["90"], "lat": 60.43, "lon": 77.87, "alt": 47, "station_name": "Aleksandrovskoe (Russia)" }, +{ "station": "24266", "rs_types": ["90"], "lat": 67.55, "lon": 133.38, "alt": 137, "station_name": "Verhojansk (Russia)" }, +{ "station": "24343", "rs_types": ["62", "90", "29", "20"], "lat": 66.77, "lon": 123.40, "alt": 93, "station_name": "Zhigansk (Russia)" }, +{ "station": "24507", "rs_types": ["90"], "lat": 64.27, "lon": 100.23, "alt": 186, "station_name": "Tura (Russia)" }, +{ "station": "24641", "rs_types": ["90"], "lat": 63.77, "lon": 121.62, "alt": 107, "station_name": "Viljujsk (Russia)" }, +{ "station": "24688", "rs_types": ["62", "90"], "lat": 63.24, "lon": 143.18, "alt": 745, "station_name": "Ojmjakon (Russia)" }, +{ "station": "24726", "rs_types": ["90", "89", "58"], "lat": 62.53, "lon": 113.87, "alt": 347, "station_name": "Mirnvy (Russia)" }, +{ "station": "24908", "rs_types": ["19", "90"], "lat": 60.33, "lon": 102.27, "alt": 260, "station_name": "Vanavara (Russia)" }, +{ "station": "24959", "rs_types": ["90", "89"], "lat": 62.02, "lon": 129.72, "alt": 103, "station_name": "Jakutsk (Russia)" }, +{ "station": "25123", "rs_types": ["62", "90"], "lat": 68.75, "lon": 161.28, "alt": 32, "station_name": "Cherskij (Russia)" }, +{ "station": "25428", "rs_types": ["62", "90"], "lat": 65.23, "lon": 160.53, "alt": 265, "station_name": "Omolon (Russia)" }, +{ "station": "25703", "rs_types": ["90", "28"], "lat": 62.92, "lon": 152.42, "alt": 207, "station_name": "Sejmchan (Russia)" }, +{ "station": "25913", "rs_types": ["90"], "lat": 59.55, "lon": 150.78, "alt": 118, "station_name": "Magadan (Russia)" }, +{ "station": "26038", "rs_types": ["42"], "lat": 59.38, "lon": 24.58, "alt": 34, "station_name": "Tallinn (Estonia)" }, +{ "station": "26075", "rs_types": ["90"], "lat": 59.95, "lon": 30.70, "alt": 72, "station_name": "St. Petersburg (Russia)" }, +{ "station": "26298", "rs_types": ["62", "90"], "lat": 57.90, "lon": 34.05, "alt": 178, "station_name": "Bologoe (Russia)" }, +{ "station": "26435", "rs_types": ["41"], "lat": 56.65, "lon": 25.13, "alt": 83, "station_name": "Skriveri (Latvia)" }, +{ "station": "26477", "rs_types": ["62", "26"], "lat": 56.35, "lon": 30.62, "alt": 98, "station_name": "Velikie Luki (Russia)" }, +{ "station": "26629", "rs_types": ["41"], "lat": 54.88, "lon": 23.83, "alt": 76, "station_name": "Kaunas (Lithuania)" }, +{ "station": "26708", "rs_types": ["62", "90", "19"], "lat": 54.70, "lon": 20.77, "alt": 14, "station_name": "Kaliningrad (Russia)" }, +{ "station": "26781", "rs_types": ["90"], "lat": 54.75, "lon": 32.07, "alt": 241, "station_name": "Smolensk (Russia)" }, +{ "station": "26850", "rs_types": ["41"], "lat": 53.93, "lon": 27.63, "alt": 234, "station_name": "Minsk (Belarus)" }, +{ "station": "27038", "rs_types": ["90"], "lat": 59.32, "lon": 39.92, "alt": 133, "station_name": "Vologda (Russia)" }, +{ "station": "27199", "rs_types": ["62", "90"], "lat": 58.60, "lon": 49.63, "alt": 158, "station_name": "Kirov (Russia)" }, +{ "station": "27459", "rs_types": ["19"], "lat": 56.27, "lon": 44.00, "alt": 157, "station_name": "Niznij Novgorod (Russia)" }, +{ "station": "27707", "rs_types": ["19", "58", "90"], "lat": 54.10, "lon": 35.35, "alt": 239, "station_name": "Suhinici (Russia)" }, +{ "station": "27730", "rs_types": ["62", "90"], "lat": 54.63, "lon": 39.70, "alt": 158, "station_name": "Rjazan (Russia)" }, +{ "station": "27962", "rs_types": ["90", "62"], "lat": 53.12, "lon": 45.02, "alt": 174, "station_name": "Penza (Russia)" }, +{ "station": "27995", "rs_types": ["90"], "lat": 52.98, "lon": 49.43, "alt": 45, "station_name": "Samara Bezencuk (Russia)" }, +{ "station": "28225", "rs_types": ["19"], "lat": 57.95, "lon": 56.20, "alt": 170, "station_name": "Perm' (Russia)" }, +{ "station": "28275", "rs_types": ["62"], "lat": 58.15, "lon": 68.25, "alt": 44, "station_name": "Tobol'Sk (Russia)" }, +{ "station": "28445", "rs_types": ["90"], "lat": 56.73, "lon": 61.07, "alt": 290, "station_name": "Verhnee Dubrovo (Russia)" }, +{ "station": "28661", "rs_types": ["62"], "lat": 55.47, "lon": 65.40, "alt": 79, "station_name": "Kurgan (Russia)" }, +{ "station": "28722", "rs_types": ["90"], "lat": 54.72, "lon": 55.83, "alt": 105, "station_name": "Ufa (Russia)" }, +{ "station": "28951", "rs_types": ["77"], "lat": 53.23, "lon": 63.62, "alt": 151, "station_name": "Kostanai (Kazakhstan)" }, +{ "station": "29231", "rs_types": ["90", "89"], "lat": 58.32, "lon": 82.95, "alt": 76, "station_name": "Kolpasevo (Russia)" }, +{ "station": "29263", "rs_types": ["90"], "lat": 58.45, "lon": 92.15, "alt": 78, "station_name": "Enisejsk (Russia)" }, +{ "station": "29282", "rs_types": ["90"], "lat": 58.38, "lon": 97.45, "alt": 134, "station_name": "Bogucany (Russia)" }, +{ "station": "29572", "rs_types": ["09", "90"], "lat": 56.18, "lon": 92.62, "alt": 296, "station_name": "Emel'Janovo (Russia)" }, +{ "station": "29612", "rs_types": ["19"], "lat": 55.33, "lon": 78.37, "alt": 120, "station_name": "Barabinsk (Russia)" }, +{ "station": "29634", "rs_types": ["90"], "lat": 55.08, "lon": 82.90, "alt": 177, "station_name": "Novosibirsk (Russia)" }, +{ "station": "29698", "rs_types": ["90", "62"], "lat": 54.88, "lon": 99.03, "alt": 410, "station_name": "Nizhneudinsk (Russia)" }, +{ "station": "29839", "rs_types": ["90"], "lat": 53.35, "lon": 83.82, "alt": 159, "station_name": "Barnaul (Russia)" }, +{ "station": "29862", "rs_types": ["90"], "lat": 53.77, "lon": 91.32, "alt": 256, "station_name": "Hakasskaja (Russia)" }, +{ "station": "30054", "rs_types": ["90", "58"], "lat": 59.45, "lon": 112.58, "alt": 193, "station_name": "Vitim (Russia)" }, +{ "station": "30230", "rs_types": ["90", "62"], "lat": 57.77, "lon": 108.07, "alt": 258, "station_name": "Kirensk (Russia)" }, +{ "station": "30309", "rs_types": ["90", "69"], "lat": 56.28, "lon": 101.75, "alt": 489, "station_name": "Bratsk (Russia)" }, +{ "station": "30372", "rs_types": ["90"], "lat": 56.90, "lon": 118.27, "alt": 711, "station_name": "Chara (Russia)" }, +{ "station": "30635", "rs_types": ["90"], "lat": 53.42, "lon": 109.02, "alt": 457, "station_name": "Ust'- Barguzin (Russia)" }, +{ "station": "30673", "rs_types": ["90"], "lat": 53.75, "lon": 119.73, "alt": 619, "station_name": "Mogoca (Russia)" }, +{ "station": "30715", "rs_types": ["90", "62"], "lat": 52.48, "lon": 103.85, "alt": 450, "station_name": "Angarsk (Russia)" }, +{ "station": "30758", "rs_types": ["90"], "lat": 52.08, "lon": 113.48, "alt": 685, "station_name": "Chita (Russia)" }, +{ "station": "30935", "rs_types": ["90", "59", "62"], "lat": 50.37, "lon": 108.75, "alt": 770, "station_name": "Krasnyj Chikoj (Russia)" }, +{ "station": "30965", "rs_types": ["58", "90"], "lat": 50.40, "lon": 116.52, "alt": 684, "station_name": "Borzja (Russia)" }, +{ "station": "31004", "rs_types": ["90"], "lat": 58.62, "lon": 125.37, "alt": 682, "station_name": "Aldan (Russia)" }, +{ "station": "31088", "rs_types": ["90"], "lat": 59.37, "lon": 143.20, "alt": 6, "station_name": "Ohotsk (Russia)" }, +{ "station": "31300", "rs_types": ["90"], "lat": 53.70, "lon": 127.30, "alt": 232, "station_name": "Zeja (Russia)" }, +{ "station": "31369", "rs_types": ["62", "90"], "lat": 53.15, "lon": 140.70, "alt": 68, "station_name": "Nikolaevsk-Na-Amure (Russia)" }, +{ "station": "31510", "rs_types": ["90"], "lat": 50.25, "lon": 127.57, "alt": 137, "station_name": "Blagovescensk (Russia)" }, +{ "station": "31538", "rs_types": ["90", "58"], "lat": 50.07, "lon": 132.13, "alt": 349, "station_name": "Sutur (Russia)" }, +{ "station": "31736", "rs_types": ["90", "58", "62"], "lat": 48.53, "lon": 135.23, "alt": 72, "station_name": "Habarovsk (Russia)" }, +{ "station": "31770", "rs_types": ["90"], "lat": 48.97, "lon": 140.30, "alt": 22, "station_name": "Sovetskaja Gavan (Russia)" }, +{ "station": "31873", "rs_types": ["90"], "lat": 45.87, "lon": 133.73, "alt": 107, "station_name": "Dal'Nerechensk (Russia)" }, +{ "station": "31977", "rs_types": ["90", "62"], "lat": 43.27, "lon": 132.05, "alt": 82, "station_name": "Vladivostok Sad Gorod (Russia)" }, +{ "station": "32061", "rs_types": ["90", "30"], "lat": 50.90, "lon": 142.17, "alt": 31, "station_name": "Aleksandrovsk-Sahalinskij (Russia)" }, +{ "station": "32098", "rs_types": ["90"], "lat": 49.22, "lon": 143.10, "alt": 4, "station_name": "Poronajsk (Russia)" }, +{ "station": "32150", "rs_types": ["90"], "lat": 46.95, "lon": 142.72, "alt": 31, "station_name": "Yuzhno-Sakhalinsk (Russia)" }, +{ "station": "32215", "rs_types": ["62"], "lat": 50.68, "lon": 156.13, "alt": 23, "station_name": "Severo-Kuril'Sk (Russia)" }, +{ "station": "32389", "rs_types": ["62", "90"], "lat": 56.32, "lon": 160.83, "alt": 28, "station_name": "Kljuchi (Russia)" }, +{ "station": "32477", "rs_types": ["62"], "lat": 54.30, "lon": 155.93, "alt": 25, "station_name": "Sobolevo (Russia)" }, +{ "station": "32540", "rs_types": ["62", "90"], "lat": 53.08, "lon": 158.58, "alt": 24, "station_name": "Petropavlovsk-Kamchatskij (Russia)" }, +{ "station": "32618", "rs_types": ["62", "90"], "lat": 55.20, "lon": 165.98, "alt": 6, "station_name": "Nikol'Skoe (Russia)" }, +{ "station": "33041", "rs_types": ["41"], "lat": 52.40, "lon": 30.95, "alt": 127, "station_name": "Gomel' (Belarus)" }, +{ "station": "33317", "rs_types": ["15"], "lat": 50.17, "lon": 27.03, "alt": 277, "station_name": "Shepetivka (Ukraine)" }, +{ "station": "33345", "rs_types": ["15", "16"], "lat": 50.40, "lon": 30.57, "alt": 166, "station_name": "Kyiv (Ukraine)" }, +{ "station": "33393", "rs_types": ["15"], "lat": 49.82, "lon": 23.95, "alt": 319, "station_name": "L'Viv (Ukraine)" }, +{ "station": "33791", "rs_types": ["15"], "lat": 48.03, "lon": 33.22, "alt": 123, "station_name": "Kryvyi Rih (Ukraine)" }, +{ "station": "33837", "rs_types": ["15"], "lat": 46.43, "lon": 30.77, "alt": 42, "station_name": "Odesa (Ukraine)" }, +{ "station": "34009", "rs_types": ["89"], "lat": 51.77, "lon": 36.17, "alt": 247, "station_name": "Kursk (Russia)" }, +{ "station": "34172", "rs_types": ["90"], "lat": 51.57, "lon": 46.03, "alt": 156, "station_name": "Saratov (Russia)" }, +{ "station": "34247", "rs_types": ["89"], "lat": 50.42, "lon": 41.05, "alt": 93, "station_name": "Kalac (Russia)" }, +{ "station": "34300", "rs_types": ["16", "15"], "lat": 49.97, "lon": 36.13, "alt": 154, "station_name": "Kharkiv (Ukraine)" }, +{ "station": "34467", "rs_types": ["90"], "lat": 48.78, "lon": 44.33, "alt": 141, "station_name": "Volgograd (Russia)" }, +{ "station": "34731", "rs_types": ["90"], "lat": 47.27, "lon": 39.82, "alt": 77, "station_name": "Rostov-Na-Donu (Russia)" }, +{ "station": "34858", "rs_types": ["69", "90", "28"], "lat": 45.92, "lon": 43.35, "alt": 77, "station_name": "Divnoe (Russia)" }, +{ "station": "34882", "rs_types": ["90", "62", "11"], "lat": 46.28, "lon": 47.98, "alt": -17, "station_name": "Astrahan' (Russia)" }, +{ "station": "35121", "rs_types": ["90"], "lat": 51.68, "lon": 55.10, "alt": 109, "station_name": "Orenburg (Russia)" }, +{ "station": "35229", "rs_types": ["27"], "lat": 50.28, "lon": 57.15, "alt": 227, "station_name": "Aktjubinsk (Kazakhstan)" }, +{ "station": "35394", "rs_types": ["77"], "lat": 49.80, "lon": 73.15, "alt": 553, "station_name": "Karaganda (Kazakhstan)" }, +{ "station": "35671", "rs_types": ["27"], "lat": 47.80, "lon": 67.72, "alt": 345, "station_name": "Zhezkazgan (Kazakhstan)" }, +{ "station": "35700", "rs_types": ["73", "88"], "lat": 47.10, "lon": 51.72, "alt": -15, "station_name": "Atyray (Kazakhstan)" }, +{ "station": "36003", "rs_types": ["73"], "lat": 52.30, "lon": 76.93, "alt": 123, "station_name": "Pavlodar (Kazakhstan)" }, +{ "station": "36096", "rs_types": ["90"], "lat": 51.72, "lon": 94.50, "alt": 629, "station_name": "Kyzyl (Russia)" }, +{ "station": "36872", "rs_types": ["62", "73"], "lat": 43.35, "lon": 77.00, "alt": 850, "station_name": "Almaty (Kazakhstan)" }, +{ "station": "37011", "rs_types": ["62", "90"], "lat": 44.10, "lon": 39.03, "alt": 94, "station_name": "Tuapse (Russia)" }, +{ "station": "37055", "rs_types": ["90"], "lat": 44.22, "lon": 43.10, "alt": 312, "station_name": "Mineral'nye Vody (Russia)" }, +{ "station": "37259", "rs_types": ["90"], "lat": 43.02, "lon": 47.48, "alt": -18, "station_name": "Mahachkala (Russia)" }, +{ "station": "37789", "rs_types": ["77"], "lat": 40.13, "lon": 44.47, "alt": 890, "station_name": "Yerevan (Armenia)" }, +{ "station": "38064", "rs_types": ["73"], "lat": 44.77, "lon": 65.52, "alt": 133, "station_name": "Kyzylorda (Kazakhstan)" }, +{ "station": "38341", "rs_types": ["73"], "lat": 42.85, "lon": 71.38, "alt": 653, "station_name": "Zhambyl (Kazakhstan)" }, +{ "station": "40179", "rs_types": ["23"], "lat": 32.00, "lon": 34.82, "alt": 30, "station_name": "Bet Dagan (Israel)" }, +{ "station": "40186", "rs_types": ["34", "07"], "lat": 31.87, "lon": 34.68, "alt": 20, "station_name": "Ashdod North (Israel)" }, +{ "station": "40265", "rs_types": ["17"], "lat": 32.37, "lon": 36.25, "alt": 686, "station_name": "Mafraq (Jordan)" }, +{ "station": "40373", "rs_types": ["41"], "lat": 28.32, "lon": 46.12, "alt": 358, "station_name": "Al Qaysumah (Saudi Arabia)" }, +{ "station": "40375", "rs_types": ["41"], "lat": 28.37, "lon": 36.60, "alt": 778, "station_name": "Tabuk (Saudi Arabia)" }, +{ "station": "40394", "rs_types": ["41"], "lat": 27.43, "lon": 41.68, "alt": 1015, "station_name": "Hail (Saudi Arabia)" }, +{ "station": "40417", "rs_types": ["41"], "lat": 26.45, "lon": 49.82, "alt": 22, "station_name": "K.f.i.a. (King Fahad Int. Airport) Dammam (Saudi Arabia)" }, +{ "station": "40430", "rs_types": ["41"], "lat": 24.55, "lon": 39.70, "alt": 654, "station_name": "Madinah (Saudi Arabia)" }, +{ "station": "40437", "rs_types": ["41"], "lat": 24.93, "lon": 46.72, "alt": 614, "station_name": "King Khaled International Airport (Saudi Arabia)" }, +{ "station": "40706", "rs_types": ["77"], "lat": 38.08, "lon": 46.28, "alt": 1367, "station_name": "Tabriz (Iran)" }, +{ "station": "40745", "rs_types": ["77"], "lat": 36.27, "lon": 59.63, "alt": 989, "station_name": "Mashhad (Iran)" }, +{ "station": "40754", "rs_types": ["77"], "lat": 35.68, "lon": 51.35, "alt": 1204, "station_name": "Tehran-Mehrabad (Iran)" }, +{ "station": "40766", "rs_types": ["77"], "lat": 34.27, "lon": 47.12, "alt": 1320, "station_name": "Kermanshah (Iran)" }, +{ "station": "40800", "rs_types": ["77"], "lat": 32.47, "lon": 51.72, "alt": 1590, "station_name": "Esfahan (Iran)" }, +{ "station": "40809", "rs_types": ["77"], "lat": 32.87, "lon": 59.20, "alt": 1491, "station_name": "Birjand (Iran)" }, +{ "station": "40811", "rs_types": ["77"], "lat": 31.33, "lon": 48.67, "alt": 20, "station_name": "Ahwaz (Iran)" }, +{ "station": "40841", "rs_types": ["77"], "lat": 30.25, "lon": 56.97, "alt": 1748, "station_name": "Kerman (Iran)" }, +{ "station": "40848", "rs_types": ["77"], "lat": 29.53, "lon": 52.58, "alt": 1486, "station_name": "Shiraz (Iran)" }, +{ "station": "40856", "rs_types": ["77"], "lat": 29.47, "lon": 60.88, "alt": 1370, "station_name": "Zahedan (Iran)" }, +{ "station": "40948", "rs_types": ["41"], "lat": 34.63, "lon": 69.20, "alt": 1789, "station_name": "Kabul Airport (Afghanistan)" }, +{ "station": "41024", "rs_types": ["41"], "lat": 21.70, "lon": 39.18, "alt": 15, "station_name": "Jeddah King Abdul Aziz International Airport (Saudi Arabia)" }, +{ "station": "41112", "rs_types": ["41"], "lat": 18.23, "lon": 42.65, "alt": 2090, "station_name": "Abha (Saudi Arabia)" }, +{ "station": "41217", "rs_types": ["41"], "lat": 24.43, "lon": 54.65, "alt": 16, "station_name": "Abu Dhabi International Airport (United Arab Emirates)" }, +{ "station": "41256", "rs_types": ["41"], "lat": 23.58, "lon": 58.28, "alt": 8, "station_name": "Seeb, International Airport (Oman)" }, +{ "station": "41316", "rs_types": ["41"], "lat": 17.03, "lon": 54.08, "alt": 20, "station_name": "Salalah (Oman)" }, +{ "station": "42182", "rs_types": ["17", "21"], "lat": 28.58, "lon": 77.20, "alt": 211, "station_name": "New Delhi / Safdarjung (India)" }, +{ "station": "42314", "rs_types": ["21"], "lat": 27.48, "lon": 95.02, "alt": 110, "station_name": "Dibrugarh / Mohanbari (India)" }, +{ "station": "42339", "rs_types": ["21"], "lat": 26.30, "lon": 73.02, "alt": 217, "station_name": "Jodhpur (India)" }, +{ "station": "42361", "rs_types": ["21"], "lat": 26.23, "lon": 78.25, "alt": 205, "station_name": "Gwalior (India)" }, +{ "station": "42410", "rs_types": ["17"], "lat": 26.10, "lon": 91.58, "alt": 47, "station_name": "Gauhati (India)" }, +{ "station": "42647", "rs_types": ["21"], "lat": 23.07, "lon": 72.63, "alt": 55, "station_name": "Ahmadabad (India)" }, +{ "station": "42667", "rs_types": ["21"], "lat": 23.28, "lon": 77.35, "alt": 523, "station_name": "Bhopal / Bairagarh (India)" }, +{ "station": "42724", "rs_types": ["21"], "lat": 23.88, "lon": 91.25, "alt": 15, "station_name": "Agartala (India)" }, +{ "station": "42809", "rs_types": ["17"], "lat": 22.65, "lon": 88.45, "alt": 4, "station_name": "Calcutta / Dum Dum (India)" }, +{ "station": "42867", "rs_types": ["17"], "lat": 21.10, "lon": 79.05, "alt": 308, "station_name": "Nagpur Sonegaon (India)" }, +{ "station": "42971", "rs_types": ["21"], "lat": 20.25, "lon": 85.83, "alt": 45, "station_name": "Bhubaneswar (India)" }, +{ "station": "43185", "rs_types": ["21"], "lat": 16.20, "lon": 81.15, "alt": 3, "station_name": "Machilipatnam (India)" }, +{ "station": "43192", "rs_types": ["21"], "lat": 15.48, "lon": 73.82, "alt": 58, "station_name": "Goa / Panjim (India)" }, +{ "station": "43346", "rs_types": ["21"], "lat": 10.92, "lon": 79.83, "alt": 7, "station_name": "Karaikal (India)" }, +{ "station": "43353", "rs_types": ["21"], "lat": 9.95, "lon": 76.27, "alt": 1, "station_name": "Cochin / Willingdon (India)" }, +{ "station": "43371", "rs_types": ["21"], "lat": 8.48, "lon": 76.95, "alt": 60, "station_name": "Thiruvananthapuram (India)" }, +{ "station": "43466", "rs_types": ["35"], "lat": 6.90, "lon": 79.87, "alt": 7, "station_name": "Colombo (Sri Lanka)" }, +{ "station": "43599", "rs_types": ["99", "84"], "lat": -0.68, "lon": 73.15, "alt": 2, "station_name": "Gan (Maldives)" }, +{ "station": "44212", "rs_types": ["77"], "lat": 49.98, "lon": 92.08, "alt": 940, "station_name": "Ulaan-Gom (Mongolia)" }, +{ "station": "44231", "rs_types": ["77"], "lat": 49.57, "lon": 100.17, "alt": 1285, "station_name": "Muren (Mongolia)" }, +{ "station": "44292", "rs_types": ["77"], "lat": 47.92, "lon": 106.87, "alt": 1307, "station_name": "Ulaan-Baator (Mongolia)" }, +{ "station": "44373", "rs_types": ["33"], "lat": 43.58, "lon": 104.42, "alt": 1465, "station_name": "Dalanzadgad (Mongolia)" }, +{ "station": "45004", "rs_types": ["41", "42"], "lat": 22.32, "lon": 114.17, "alt": 65, "station_name": "King's Park (Hong Kong)" }, +{ "station": "47102", "rs_types": ["41", "77"], "lat": 37.97, "lon": 124.63, "alt": 144, "station_name": "Baengnyeongdo (Korea, South)" }, +{ "station": "47104", "rs_types": ["41"], "lat": 37.87, "lon": 127.72, "alt": 76, "station_name": "Chunchon Ab (Korea, South)" }, +{ "station": "47122", "rs_types": ["45"], "lat": 37.10, "lon": 127.03, "alt": 11, "station_name": "Osan Ab (Korea, South)" }, +{ "station": "47138", "rs_types": ["41", "77"], "lat": 36.03, "lon": 129.38, "alt": 6, "station_name": "Pohang (Korea, South)" }, +{ "station": "47155", "rs_types": ["42"], "lat": 35.18, "lon": 128.57, "alt": 5, "station_name": "Masan (Korea, South)" }, +{ "station": "47158", "rs_types": ["45"], "lat": 35.12, "lon": 126.82, "alt": 13, "station_name": "Kwangju Ab (Korea, South)" }, +{ "station": "47169", "rs_types": ["41"], "lat": 34.68, "lon": 125.45, "alt": 79, "station_name": "Heuksando (Korea, South)" }, +{ "station": "47401", "rs_types": ["41"], "lat": 45.42, "lon": 141.68, "alt": 3, "station_name": "Wakkanai (Japan)" }, +{ "station": "47412", "rs_types": ["41"], "lat": 43.05, "lon": 141.33, "alt": 17, "station_name": "Sapporo (Japan)" }, +{ "station": "47418", "rs_types": ["42"], "lat": 42.98, "lon": 144.40, "alt": 32, "station_name": "Kushiro (Japan)" }, +{ "station": "47582", "rs_types": ["41"], "lat": 39.72, "lon": 140.10, "alt": 6, "station_name": "Akita (Japan)" }, +{ "station": "47600", "rs_types": ["42"], "lat": 37.38, "lon": 136.90, "alt": 5, "station_name": "Wajima (Japan)" }, +{ "station": "47646", "rs_types": ["41"], "lat": 36.05, "lon": 140.13, "alt": 25, "station_name": "Tateno (Japan)" }, +{ "station": "47678", "rs_types": ["35"], "lat": 33.10, "lon": 139.78, "alt": 79, "station_name": "Hachijojima (Japan)" }, +{ "station": "47741", "rs_types": ["42"], "lat": 35.45, "lon": 133.07, "alt": 17, "station_name": "Matsue (Japan)" }, +{ "station": "47778", "rs_types": ["42"], "lat": 33.45, "lon": 135.77, "alt": 73, "station_name": "Shionomisaki (Japan)" }, +{ "station": "47807", "rs_types": ["41"], "lat": 33.58, "lon": 130.38, "alt": 3, "station_name": "Fukuoka (Japan)" }, +{ "station": "47827", "rs_types": ["41"], "lat": 31.55, "lon": 130.55, "alt": 4, "station_name": "Kagoshima (Japan)" }, +{ "station": "47909", "rs_types": ["35"], "lat": 28.38, "lon": 129.50, "alt": 3, "station_name": "Naze (Japan)" }, +{ "station": "47918", "rs_types": ["35"], "lat": 24.33, "lon": 124.17, "alt": 6, "station_name": "Ishigakijima (Japan)" }, +{ "station": "47945", "rs_types": ["35"], "lat": 25.83, "lon": 131.23, "alt": 16, "station_name": "Minamidaitojima (Japan)" }, +{ "station": "47971", "rs_types": ["35"], "lat": 27.08, "lon": 142.18, "alt": 3, "station_name": "Chichijima (Japan)" }, +{ "station": "47991", "rs_types": ["35"], "lat": 24.30, "lon": 153.97, "alt": 8, "station_name": "Minamitorishima (Japan)" }, +{ "station": "48097", "rs_types": ["41"], "lat": 16.77, "lon": 96.17, "alt": 14, "station_name": "Yangon (Myanmar)" }, +{ "station": "48327", "rs_types": ["78"], "lat": 18.78, "lon": 98.98, "alt": 312, "station_name": "Chiang Mai (Thailand)" }, +{ "station": "48381", "rs_types": ["97"], "lat": 16.43, "lon": 102.83, "alt": 165, "station_name": "Khon Kaen (Thailand)" }, +{ "station": "48407", "rs_types": ["97"], "lat": 15.25, "lon": 104.87, "alt": 123, "station_name": "Ubon Ratchathani (Thailand)" }, +{ "station": "48453", "rs_types": ["41"], "lat": 13.67, "lon": 100.62, "alt": 3, "station_name": "Bang Na Agromet (Thailand)" }, +{ "station": "48565", "rs_types": ["97"], "lat": 8.12, "lon": 98.32, "alt": 6, "station_name": "Phuket Airport (Thailand)" }, +{ "station": "48568", "rs_types": ["97"], "lat": 7.20, "lon": 100.60, "alt": 4, "station_name": "Songkhla (Thailand)" }, +{ "station": "48601", "rs_types": ["54"], "lat": 5.30, "lon": 100.27, "alt": 3, "station_name": "Penang / Bayan Lepas (Malaysia)" }, +{ "station": "48615", "rs_types": ["54"], "lat": 6.17, "lon": 102.28, "alt": 5, "station_name": "Kota Bharu (Malaysia)" }, +{ "station": "48650", "rs_types": ["54"], "lat": 3.10, "lon": 101.65, "alt": 21, "station_name": "Sepang (Malaysia)" }, +{ "station": "48657", "rs_types": ["54"], "lat": 3.62, "lon": 103.22, "alt": 18, "station_name": "Kuantan (Malaysia)" }, +{ "station": "48698", "rs_types": ["41"], "lat": 1.37, "lon": 103.98, "alt": 5, "station_name": "Singapore / Changi Airport (Singapore)" }, +{ "station": "48820", "rs_types": ["41", "80"], "lat": 21.02, "lon": 105.80, "alt": 6, "station_name": "Ha Noi (Vietnam)" }, +{ "station": "48839", "rs_types": ["41", "80"], "lat": 20.13, "lon": 107.72, "alt": 60, "station_name": "Bach Long Vi (Vietnam)" }, +{ "station": "48845", "rs_types": ["41", "80"], "lat": 18.70, "lon": 105.67, "alt": 6, "station_name": "Vinh (Vietnam)" }, +{ "station": "48855", "rs_types": ["41", "80"], "lat": 16.03, "lon": 108.18, "alt": 7, "station_name": "Da Nang (Vietnam)" }, +{ "station": "48900", "rs_types": ["41", "80"], "lat": 10.82, "lon": 106.67, "alt": 19, "station_name": "Ho Chi Minh (Vietnam)" }, +{ "station": "50527", "rs_types": ["00"], "lat": 49.22, "lon": 119.75, "alt": 611, "station_name": "Hailar (China)" }, +{ "station": "50557", "rs_types": ["02", "01"], "lat": 49.17, "lon": 125.23, "alt": 243, "station_name": "Nenjiang (China)" }, +{ "station": "50774", "rs_types": ["02"], "lat": 47.72, "lon": 128.90, "alt": 232, "station_name": "Yichun (China)" }, +{ "station": "50953", "rs_types": ["02", "00"], "lat": 45.75, "lon": 126.77, "alt": 143, "station_name": "Harbin (China)" }, +{ "station": "51076", "rs_types": ["02"], "lat": 47.73, "lon": 88.08, "alt": 737, "station_name": "Altay (China)" }, +{ "station": "51431", "rs_types": ["02"], "lat": 43.95, "lon": 81.33, "alt": 663, "station_name": "Yining (China)" }, +{ "station": "51463", "rs_types": ["02"], "lat": 43.78, "lon": 87.62, "alt": 919, "station_name": "Urumqi (China)" }, +{ "station": "51644", "rs_types": ["01"], "lat": 41.72, "lon": 82.95, "alt": 1100, "station_name": "Kuqa (China)" }, +{ "station": "51709", "rs_types": ["02"], "lat": 39.47, "lon": 75.98, "alt": 1291, "station_name": "Kashi (China)" }, +{ "station": "51777", "rs_types": ["01"], "lat": 39.03, "lon": 88.17, "alt": 889, "station_name": "Ruoqiang (China)" }, +{ "station": "51828", "rs_types": ["01", "00"], "lat": 37.13, "lon": 79.93, "alt": 1375, "station_name": "Hotan (China)" }, +{ "station": "51839", "rs_types": ["00", "01", "02"], "lat": 37.07, "lon": 82.77, "alt": 1410, "station_name": "Minfeng / Niya (China)" }, +{ "station": "52203", "rs_types": ["01"], "lat": 42.82, "lon": 93.52, "alt": 739, "station_name": "Hami (China)" }, +{ "station": "52267", "rs_types": ["02", "01"], "lat": 41.95, "lon": 101.07, "alt": 941, "station_name": "Ejin Qi (China)" }, +{ "station": "52323", "rs_types": ["01"], "lat": 41.80, "lon": 97.03, "alt": 1770, "station_name": "Mazong Shan (China)" }, +{ "station": "52418", "rs_types": ["01", "00"], "lat": 40.15, "lon": 94.68, "alt": 1140, "station_name": "Dunhuang (China)" }, +{ "station": "52533", "rs_types": ["01"], "lat": 39.77, "lon": 98.48, "alt": 1478, "station_name": "Jiuquan (China)" }, +{ "station": "52681", "rs_types": ["01"], "lat": 38.63, "lon": 103.08, "alt": 1367, "station_name": "Minqin (China)" }, +{ "station": "52818", "rs_types": ["02"], "lat": 36.42, "lon": 94.90, "alt": 2809, "station_name": "Golmud (China)" }, +{ "station": "52836", "rs_types": ["02"], "lat": 36.30, "lon": 98.10, "alt": 3192, "station_name": "Dulan (China)" }, +{ "station": "52866", "rs_types": ["01"], "lat": 36.62, "lon": 101.77, "alt": 2262, "station_name": "Xining (China)" }, +{ "station": "52983", "rs_types": ["02"], "lat": 35.87, "lon": 104.15, "alt": 1875, "station_name": "Yu Zhong (China)" }, +{ "station": "53068", "rs_types": ["01", "00"], "lat": 43.65, "lon": 112.00, "alt": 966, "station_name": "Erenhot (China)" }, +{ "station": "53463", "rs_types": ["01", "00"], "lat": 40.82, "lon": 111.68, "alt": 1065, "station_name": "Hohhot (China)" }, +{ "station": "53513", "rs_types": ["00"], "lat": 40.77, "lon": 107.40, "alt": 1041, "station_name": "Linhe (China)" }, +{ "station": "53614", "rs_types": ["01", "02"], "lat": 38.48, "lon": 106.22, "alt": 1112, "station_name": "Yinchuan (China)" }, +{ "station": "53772", "rs_types": ["02", "01"], "lat": 37.78, "lon": 112.55, "alt": 779, "station_name": "Taiyuan (China)" }, +{ "station": "53845", "rs_types": ["01"], "lat": 36.60, "lon": 109.50, "alt": 959, "station_name": "Yan An (China)" }, +{ "station": "53915", "rs_types": ["00", "01"], "lat": 35.55, "lon": 106.67, "alt": 1348, "station_name": "Pingliang (China)" }, +{ "station": "54102", "rs_types": ["00", "02"], "lat": 43.95, "lon": 116.07, "alt": 991, "station_name": "Xilin Hot (China)" }, +{ "station": "54135", "rs_types": ["00"], "lat": 43.60, "lon": 122.27, "alt": 180, "station_name": "Tongliao (China)" }, +{ "station": "54161", "rs_types": ["01"], "lat": 43.90, "lon": 125.22, "alt": 238, "station_name": "Changchun (China)" }, +{ "station": "54218", "rs_types": ["00"], "lat": 42.27, "lon": 118.97, "alt": 572, "station_name": "Chifeng (China)" }, +{ "station": "54292", "rs_types": ["01"], "lat": 42.88, "lon": 129.47, "alt": 178, "station_name": "Yanji (China)" }, +{ "station": "54374", "rs_types": ["01", "00"], "lat": 41.72, "lon": 126.92, "alt": 333, "station_name": "Linjiang (China)" }, +{ "station": "54511", "rs_types": ["00", "02"], "lat": 39.93, "lon": 116.28, "alt": 55, "station_name": "Beijing (China)" }, +{ "station": "54662", "rs_types": ["00"], "lat": 38.90, "lon": 121.63, "alt": 97, "station_name": "Dalian (China)" }, +{ "station": "54727", "rs_types": ["01"], "lat": 36.70, "lon": 117.55, "alt": 123, "station_name": "Zhangqui (China)" }, +{ "station": "54857", "rs_types": ["01"], "lat": 36.07, "lon": 120.33, "alt": 77, "station_name": "Qingdao (China)" }, +{ "station": "55299", "rs_types": ["02"], "lat": 31.48, "lon": 92.07, "alt": 4508, "station_name": "Nagqu (China)" }, +{ "station": "55591", "rs_types": ["02"], "lat": 29.67, "lon": 91.13, "alt": 3650, "station_name": "Lhasa (China)" }, +{ "station": "56029", "rs_types": ["01"], "lat": 33.02, "lon": 97.02, "alt": 3682, "station_name": "Yushu (China)" }, +{ "station": "56080", "rs_types": ["01"], "lat": 35.00, "lon": 102.90, "alt": 2910, "station_name": "Hezuo (China)" }, +{ "station": "56137", "rs_types": ["02", "00"], "lat": 31.15, "lon": 97.17, "alt": 3307, "station_name": "Qamdo (China)" }, +{ "station": "56146", "rs_types": ["01"], "lat": 31.62, "lon": 100.00, "alt": 3394, "station_name": "Garze (China)" }, +{ "station": "56187", "rs_types": ["02", "01"], "lat": 30.70, "lon": 103.83, "alt": 555, "station_name": "Wenjiang (China)" }, +{ "station": "56571", "rs_types": ["01"], "lat": 27.90, "lon": 102.27, "alt": 1599, "station_name": "Xichang (China)" }, +{ "station": "56691", "rs_types": ["01"], "lat": 26.87, "lon": 104.28, "alt": 2236, "station_name": "Weining (China)" }, +{ "station": "56739", "rs_types": ["02", "00"], "lat": 25.12, "lon": 98.48, "alt": 1649, "station_name": "Tengchong (China)" }, +{ "station": "56778", "rs_types": ["01", "00"], "lat": 25.02, "lon": 102.68, "alt": 1892, "station_name": "Kunming (China)" }, +{ "station": "56964", "rs_types": ["00"], "lat": 22.77, "lon": 100.98, "alt": 1303, "station_name": "Simao (China)" }, +{ "station": "56985", "rs_types": ["00"], "lat": 23.38, "lon": 103.38, "alt": 1302, "station_name": "Mengzi (China)" }, +{ "station": "57083", "rs_types": ["01", "00"], "lat": 34.72, "lon": 113.65, "alt": 111, "station_name": "Zhengzhou (China)" }, +{ "station": "57127", "rs_types": ["02"], "lat": 33.07, "lon": 107.03, "alt": 509, "station_name": "Hanzhong (China)" }, +{ "station": "57131", "rs_types": ["02"], "lat": 34.43, "lon": 108.97, "alt": 411, "station_name": "Jinghe (China)" }, +{ "station": "57178", "rs_types": ["00"], "lat": 33.03, "lon": 112.58, "alt": 131, "station_name": "Nanyang (China)" }, +{ "station": "57447", "rs_types": ["00", "01"], "lat": 30.28, "lon": 109.47, "alt": 458, "station_name": "Enshi (China)" }, +{ "station": "57461", "rs_types": ["01"], "lat": 30.70, "lon": 111.30, "alt": 134, "station_name": "Yichang (China)" }, +{ "station": "57494", "rs_types": ["01"], "lat": 30.62, "lon": 114.13, "alt": 23, "station_name": "Wuhan (China)" }, +{ "station": "57516", "rs_types": ["00"], "lat": 29.52, "lon": 106.48, "alt": 351, "station_name": "Chongqing (China)" }, +{ "station": "57687", "rs_types": ["01", "00"], "lat": 28.23, "lon": 112.87, "alt": 68, "station_name": "Changsha (China)" }, +{ "station": "57749", "rs_types": ["00"], "lat": 27.57, "lon": 110.00, "alt": 261, "station_name": "Huaihua (China)" }, +{ "station": "57816", "rs_types": ["01"], "lat": 26.58, "lon": 106.72, "alt": 1074, "station_name": "Guiyang (China)" }, +{ "station": "57957", "rs_types": ["01"], "lat": 25.33, "lon": 110.30, "alt": 166, "station_name": "Guilin (China)" }, +{ "station": "57972", "rs_types": ["01"], "lat": 25.80, "lon": 113.03, "alt": 185, "station_name": "Chenzhou (China)" }, +{ "station": "57993", "rs_types": ["01", "02"], "lat": 25.85, "lon": 114.95, "alt": 125, "station_name": "Ganzhou (China)" }, +{ "station": "58027", "rs_types": ["00"], "lat": 34.28, "lon": 117.15, "alt": 42, "station_name": "Xuzhou (China)" }, +{ "station": "58150", "rs_types": ["00"], "lat": 33.77, "lon": 120.25, "alt": 7, "station_name": "Sheyang (China)" }, +{ "station": "58203", "rs_types": ["01"], "lat": 32.93, "lon": 115.83, "alt": 39, "station_name": "Fuyang (China)" }, +{ "station": "58238", "rs_types": ["00"], "lat": 32.00, "lon": 118.80, "alt": 12, "station_name": "Nanjing (China)" }, +{ "station": "58362", "rs_types": ["01", "00"], "lat": 31.40, "lon": 121.47, "alt": 8, "station_name": "Shanghai (China)" }, +{ "station": "58424", "rs_types": ["02"], "lat": 30.53, "lon": 117.05, "alt": 20, "station_name": "Anqing (China)" }, +{ "station": "58457", "rs_types": ["00"], "lat": 30.23, "lon": 120.17, "alt": 43, "station_name": "Hangzhou (China)" }, +{ "station": "58606", "rs_types": ["02"], "lat": 28.60, "lon": 115.92, "alt": 50, "station_name": "Nanchang (China)" }, +{ "station": "58633", "rs_types": ["00", "01"], "lat": 28.97, "lon": 118.87, "alt": 71, "station_name": "Qu Xian (China)" }, +{ "station": "58665", "rs_types": ["00", "01"], "lat": 28.62, "lon": 121.42, "alt": 9, "station_name": "Hongjia (China)" }, +{ "station": "58725", "rs_types": ["00"], "lat": 27.33, "lon": 117.43, "alt": 192, "station_name": "Shaowu (China)" }, +{ "station": "58847", "rs_types": ["00"], "lat": 26.08, "lon": 119.28, "alt": 85, "station_name": "Fuzhou (China)" }, +{ "station": "59134", "rs_types": ["01", "02", "00"], "lat": 24.48, "lon": 118.08, "alt": 139, "station_name": "Xiamen (China)" }, +{ "station": "59211", "rs_types": ["00", "01", "02"], "lat": 23.90, "lon": 106.60, "alt": 242, "station_name": "Baise (China)" }, +{ "station": "59265", "rs_types": ["01"], "lat": 23.48, "lon": 111.30, "alt": 120, "station_name": "Wuzhou (China)" }, +{ "station": "59280", "rs_types": ["01"], "lat": 23.67, "lon": 113.05, "alt": 19, "station_name": "Qing Yuan (China)" }, +{ "station": "59316", "rs_types": ["01"], "lat": 23.40, "lon": 116.68, "alt": 3, "station_name": "Shantou (China)" }, +{ "station": "59431", "rs_types": ["00"], "lat": 22.82, "lon": 108.35, "alt": 73, "station_name": "Nanning (China)" }, +{ "station": "59758", "rs_types": ["00", "02"], "lat": 20.03, "lon": 110.35, "alt": 15, "station_name": "Haikou (China)" }, +{ "station": "59981", "rs_types": ["00", "02"], "lat": 16.83, "lon": 112.33, "alt": 5, "station_name": "Xisha Dao (China)" }, +{ "station": "60018", "rs_types": ["24"], "lat": 28.32, "lon": -16.38, "alt": 105, "station_name": "Tenerife-Guimar (Spain)" }, +{ "station": "60096", "rs_types": ["77"], "lat": 23.72, "lon": -15.93, "alt": 10, "station_name": "Dakhla (Western Sahara)" }, +{ "station": "60155", "rs_types": ["77"], "lat": 33.57, "lon": -7.67, "alt": 62, "station_name": "Casablanca (Morocco)" }, +{ "station": "60390", "rs_types": ["41"], "lat": 36.72, "lon": 3.25, "alt": 25, "station_name": "Dar-El-Beida (Algeria)" }, +{ "station": "60571", "rs_types": ["41"], "lat": 31.62, "lon": -2.23, "alt": 773, "station_name": "Bechar (Algeria)" }, +{ "station": "60630", "rs_types": ["41"], "lat": 27.20, "lon": 2.47, "alt": 293, "station_name": "In Salah (Algeria)" }, +{ "station": "60656", "rs_types": ["41"], "lat": 27.67, "lon": -8.13, "alt": 431, "station_name": "Tindouf (Algeria)" }, +{ "station": "60680", "rs_types": ["41"], "lat": 22.78, "lon": 5.52, "alt": 1378, "station_name": "Tamanrasset (Algeria)" }, +{ "station": "60715", "rs_types": ["17"], "lat": 36.83, "lon": 10.23, "alt": 3, "station_name": "Tunis-Carthage (Tunisia)" }, +{ "station": "60760", "rs_types": ["17"], "lat": 33.92, "lon": 8.10, "alt": 87, "station_name": "Tozeur (Tunisia)" }, +{ "station": "61024", "rs_types": ["54", "17"], "lat": 16.97, "lon": 7.98, "alt": 501, "station_name": "Agadez (Niger)" }, +{ "station": "61901", "rs_types": ["41"], "lat": -15.93, "lon": -5.67, "alt": 435, "station_name": "Saint Helena Island (Saint Helena, Ascension and Tristan da Cunha)" }, +{ "station": "61980", "rs_types": ["77"], "lat": -20.88, "lon": 55.52, "alt": 21, "station_name": "Saint-Denis / Gillot (Reunion)" }, +{ "station": "61998", "rs_types": ["77"], "lat": -49.35, "lon": 70.25, "alt": 29, "station_name": "Port-Aux-Francais Iles Kerguelen (French Southern Territories)" }, +{ "station": "62306", "rs_types": ["41"], "lat": 31.33, "lon": 27.22, "alt": 25, "station_name": "Mersa Matruh (Egypt)" }, +{ "station": "62378", "rs_types": ["41"], "lat": 29.87, "lon": 31.33, "alt": 139, "station_name": "Helwan (Egypt)" }, +{ "station": "62403", "rs_types": ["63"], "lat": 26.20, "lon": 32.75, "alt": 77, "station_name": "South Of Valley University (Egypt)" }, +{ "station": "62414", "rs_types": ["41"], "lat": 23.97, "lon": 32.78, "alt": 200, "station_name": "Asswan (Egypt)" }, +{ "station": "63450", "rs_types": ["41"], "lat": 8.98, "lon": 38.80, "alt": 2355, "station_name": "Addis Ababa (Ethiopia)" }, +{ "station": "63741", "rs_types": ["77"], "lat": -1.30, "lon": 36.75, "alt": 1798, "station_name": "Nairobi / Dagoretti (Kenya)" }, +{ "station": "63985", "rs_types": ["41"], "lat": -4.68, "lon": 55.53, "alt": 4, "station_name": "Seychelles International Airport Rawinsonde Station (Seychelles)" }, +{ "station": "64400", "rs_types": ["54", "17"], "lat": -4.82, "lon": 11.90, "alt": 17, "station_name": "Pointe-Noire (Congo, Republic of the)" }, +{ "station": "64500", "rs_types": ["54"], "lat": 0.45, "lon": 9.42, "alt": 12, "station_name": "Libreville (Gabon)" }, +{ "station": "64700", "rs_types": ["54"], "lat": 12.13, "lon": 15.03, "alt": 295, "station_name": "Ndjamena (Chad)" }, +{ "station": "65578", "rs_types": ["54"], "lat": 5.25, "lon": -3.93, "alt": 7, "station_name": "Abidjan (Cote d'Ivoire)" }, +{ "station": "67083", "rs_types": ["54"], "lat": -18.80, "lon": 47.48, "alt": 1279, "station_name": "Antananarivo / Ivato (Madagascar)" }, +{ "station": "68110", "rs_types": ["99"], "lat": -22.57, "lon": 17.10, "alt": 1728, "station_name": "Windhoek (Namibia)" }, +{ "station": "68263", "rs_types": ["84"], "lat": -25.92, "lon": 28.22, "alt": 1523, "station_name": "Pretoria Irene (South Africa)" }, +{ "station": "68424", "rs_types": ["84"], "lat": -28.40, "lon": 21.27, "alt": 839, "station_name": "Upington (South Africa)" }, +{ "station": "68442", "rs_types": ["84"], "lat": -29.10, "lon": 26.30, "alt": 1354, "station_name": "Bloemfontein J. B. M. Hertzog (South Africa)" }, +{ "station": "68512", "rs_types": ["84"], "lat": -29.67, "lon": 17.87, "alt": 1006, "station_name": "Springbok (South Africa)" }, +{ "station": "68538", "rs_types": ["84"], "lat": -30.63, "lon": 23.92, "alt": 384, "station_name": "De Aar (South Africa)" }, +{ "station": "68592", "rs_types": ["84"], "lat": -29.60, "lon": 31.12, "alt": 105, "station_name": "King Shaka Int'l Airport (South Africa)" }, +{ "station": "68816", "rs_types": ["84"], "lat": -33.98, "lon": 18.60, "alt": 42, "station_name": "Cape Town D. F. Malan (South Africa)" }, +{ "station": "68842", "rs_types": ["84"], "lat": -33.98, "lon": 25.60, "alt": 61, "station_name": "Port Elizabeth (South Africa)" }, +{ "station": "68906", "rs_types": ["99"], "lat": -40.35, "lon": -9.88, "alt": 54, "station_name": "Gough Island (South Africa)" }, +{ "station": "70026", "rs_types": ["24"], "lat": 71.29, "lon": -156.77, "alt": 13, "station_name": "Barrow, Wiley Post-Will Rogers Memorial Airport (United States)" }, +{ "station": "70133", "rs_types": ["24"], "lat": 66.88, "lon": -162.60, "alt": 3, "station_name": "Kotzebue, Ralph Wien Memorial Airport (United States)" }, +{ "station": "70200", "rs_types": ["24"], "lat": 64.51, "lon": -165.44, "alt": 11, "station_name": "Nome, Nome Airport (United States)" }, +{ "station": "70219", "rs_types": ["24"], "lat": 60.78, "lon": -161.84, "alt": 38, "station_name": "Bethel, Bethel Airport (United States)" }, +{ "station": "70231", "rs_types": ["24"], "lat": 62.97, "lon": -155.62, "alt": 103, "station_name": "McGrath, McGrath Airport (United States)" }, +{ "station": "70261", "rs_types": ["24"], "lat": 64.80, "lon": -147.88, "alt": 132, "station_name": "Fairbanks, Fairbanks International Airport (United States)" }, +{ "station": "70273", "rs_types": ["24"], "lat": 61.17, "lon": -150.00, "alt": 44, "station_name": "Anchorage, Ted Stevens Anchorage International Airport (United States)" }, +{ "station": "70308", "rs_types": ["24"], "lat": 57.17, "lon": -170.22, "alt": 19, "station_name": "St. Paul Island, St. Paul Island Airport (United States)" }, +{ "station": "70316", "rs_types": ["24"], "lat": 55.22, "lon": -162.73, "alt": 30, "station_name": "Cold Bay, Cold Bay Airport (United States)" }, +{ "station": "70326", "rs_types": ["24"], "lat": 58.68, "lon": -156.65, "alt": 17, "station_name": "King Salmon, King Salmon Airport (United States)" }, +{ "station": "70350", "rs_types": ["24"], "lat": 57.75, "lon": -152.50, "alt": 22, "station_name": "Kodiak, Kodiak Airport (United States)" }, +{ "station": "70361", "rs_types": ["24"], "lat": 59.52, "lon": -139.67, "alt": 11, "station_name": "Yakutat (United States)" }, +{ "station": "70398", "rs_types": ["24"], "lat": 55.04, "lon": -131.57, "alt": 36, "station_name": "Annette, Annette Island Airport (United States)" }, +{ "station": "70414", "rs_types": ["41"], "lat": 52.72, "lon": 174.12, "alt": 29, "station_name": "Shemya, Eareckson As (United States)" }, +{ "station": "71033", "rs_types": ["17"], "lat": 57.25, "lon": -105.60, "alt": 513, "station_name": "Key Lake, Sk, Sask (Canada)" }, +{ "station": "71043", "rs_types": ["17"], "lat": 65.28, "lon": -126.80, "alt": 67, "station_name": "Norman Wells, N. W. T. (Canada)" }, +{ "station": "71081", "rs_types": ["17"], "lat": 68.78, "lon": -81.25, "alt": 8, "station_name": "Hall Beach, N. W. T. (Canada)" }, +{ "station": "71082", "rs_types": ["17"], "lat": 82.50, "lon": -62.33, "alt": 63, "station_name": "Alert, N. W. T. (Canada)" }, +{ "station": "71109", "rs_types": ["17"], "lat": 50.68, "lon": -127.37, "alt": 22, "station_name": "Port Hardy, B. C. (Canada)" }, +{ "station": "71119", "rs_types": ["17"], "lat": 53.53, "lon": -114.10, "alt": 766, "station_name": "Edmonton Stony Plain Alta. (Canada)" }, +{ "station": "71603", "rs_types": ["17"], "lat": 43.83, "lon": -66.08, "alt": 43, "station_name": "Yarmouth, N. S. (Canada)" }, +{ "station": "71701", "rs_types": ["41"], "lat": 45.83, "lon": -66.43, "alt": 51, "station_name": "Gagetown Can-Mil (Canada)" }, +{ "station": "71722", "rs_types": ["17"], "lat": 46.38, "lon": -75.97, "alt": 170, "station_name": "Maniwaki, Que. (Canada)" }, +{ "station": "71802", "rs_types": ["17"], "lat": 46.92, "lon": -55.38, "alt": 46, "station_name": "St. Lawrence, Nfld. (Canada)" }, +{ "station": "71811", "rs_types": ["17"], "lat": 50.22, "lon": -66.27, "alt": 55, "station_name": "Sept-Iles, Que. (Canada)" }, +{ "station": "71815", "rs_types": ["17"], "lat": 48.53, "lon": -58.55, "alt": 8, "station_name": "Stephenville, Nfld. (Canada)" }, +{ "station": "71816", "rs_types": ["17"], "lat": 53.32, "lon": -60.42, "alt": 46, "station_name": "Goose, Nfld. (Canada)" }, +{ "station": "71823", "rs_types": ["17"], "lat": 53.75, "lon": -73.67, "alt": 306, "station_name": "La Grande Iv, Que. (Canada)" }, +{ "station": "71836", "rs_types": ["17"], "lat": 51.27, "lon": -80.65, "alt": 10, "station_name": "Moosonee, Ont. (Canada)" }, +{ "station": "71843", "rs_types": ["17"], "lat": 49.88, "lon": -97.13, "alt": 251, "station_name": "Winnipeg Ua, Man (Canada)" }, +{ "station": "71845", "rs_types": ["17"], "lat": 51.45, "lon": -90.20, "alt": 389, "station_name": "Pickle Lake, Ont. (Canada)" }, +{ "station": "71867", "rs_types": ["17"], "lat": 53.97, "lon": -101.10, "alt": 271, "station_name": "The Pas, Man. (Canada)" }, +{ "station": "71906", "rs_types": ["17"], "lat": 58.10, "lon": -68.42, "alt": 37, "station_name": "Kuujjuaq, Que. (Canada)" }, +{ "station": "71907", "rs_types": ["17"], "lat": 58.45, "lon": -78.12, "alt": 3, "station_name": "Inukjuak, Que (Canada)" }, +{ "station": "71908", "rs_types": ["17"], "lat": 53.90, "lon": -122.78, "alt": 601, "station_name": "Prince George Ua, Bc (Canada)" }, +{ "station": "71909", "rs_types": ["17"], "lat": 63.75, "lon": -68.55, "alt": 34, "station_name": "Iqaluit, N. W. T. (Canada)" }, +{ "station": "71913", "rs_types": ["17"], "lat": 58.73, "lon": -94.07, "alt": 28, "station_name": "Churchill, Man. (Canada)" }, +{ "station": "71917", "rs_types": ["17"], "lat": 79.98, "lon": -85.93, "alt": 10, "station_name": "Eureka, N. W. T. (Canada)" }, +{ "station": "71924", "rs_types": ["17"], "lat": 74.72, "lon": -94.98, "alt": 67, "station_name": "Resolute, N. W. T. (Canada)" }, +{ "station": "71925", "rs_types": ["17"], "lat": 69.10, "lon": -105.12, "alt": 23, "station_name": "Cambridge Bay, N. W. T. (Canada)" }, +{ "station": "71926", "rs_types": ["17"], "lat": 64.30, "lon": -96.08, "alt": 18, "station_name": "Baker Lake, N. W. T. (Canada)" }, +{ "station": "71934", "rs_types": ["17"], "lat": 60.02, "lon": -111.95, "alt": 203, "station_name": "Fort Smith, N. W. T. (Canada)" }, +{ "station": "71945", "rs_types": ["17"], "lat": 58.83, "lon": -122.58, "alt": 382, "station_name": "Fort Nelson, B. C. (Canada)" }, +{ "station": "71945", "rs_types": ["17"], "lat": 58.92, "lon": -125.77, "alt": 835, "station_name": "Muncho Lake (Canada)" }, +{ "station": "71957", "rs_types": ["17"], "lat": 68.30, "lon": -133.48, "alt": 59, "station_name": "Inuvik, N. W. T. (Canada)" }, +{ "station": "72201", "rs_types": ["52"], "lat": 24.55, "lon": -81.75, "alt": 1, "station_name": "Key West, Key West International Airport (United States)" }, +{ "station": "72202", "rs_types": ["11"], "lat": 25.79, "lon": -80.32, "alt": 3, "station_name": "Miami, Miami International Airport (United States)" }, +{ "station": "72206", "rs_types": ["52"], "lat": 30.49, "lon": -81.69, "alt": 9, "station_name": "Jacksonville, Jacksonville International Airport (United States)" }, +{ "station": "72208", "rs_types": ["82"], "lat": 32.90, "lon": -80.04, "alt": 14, "station_name": "Charleston, Charleston Air Force Base (United States)" }, +{ "station": "72210", "rs_types": ["82"], "lat": 27.70, "lon": -82.40, "alt": 12, "station_name": "Tampa Bay Area (United States)" }, +{ "station": "72214", "rs_types": ["82"], "lat": 30.39, "lon": -84.35, "alt": 24, "station_name": "Tallahassee, Tallahassee Regional Airport (United States)" }, +{ "station": "72215", "rs_types": ["82"], "lat": 33.37, "lon": -84.57, "alt": 262, "station_name": "Peachtree City, Ga. (United States)" }, +{ "station": "72230", "rs_types": ["82"], "lat": 33.17, "lon": -86.77, "alt": 178, "station_name": "Shelby Country Airport, Al. (United States)" }, +{ "station": "72233", "rs_types": ["52"], "lat": 30.33, "lon": -89.82, "alt": 8, "station_name": "Slidell (United States)" }, +{ "station": "72235", "rs_types": ["82"], "lat": 32.32, "lon": -90.08, "alt": 105, "station_name": "Jackson, Jackson International Airport (United States)" }, +{ "station": "72240", "rs_types": ["52"], "lat": 30.13, "lon": -93.22, "alt": 5, "station_name": "Lake Charles, Lake Charles Regional Airport (United States)" }, +{ "station": "72248", "rs_types": ["82"], "lat": 32.45, "lon": -93.82, "alt": 78, "station_name": "Shreveport, Shreveport Regional Airport (United States)" }, +{ "station": "72249", "rs_types": ["82"], "lat": 32.83, "lon": -97.30, "alt": 196, "station_name": "Fort Worth (United States)" }, +{ "station": "72250", "rs_types": ["52"], "lat": 25.91, "lon": -97.42, "alt": 7, "station_name": "Brownsville, Brownsville / South Padre Island International Airport (United States)" }, +{ "station": "72251", "rs_types": ["52"], "lat": 27.77, "lon": -97.51, "alt": 13, "station_name": "Corpus Christi, Corpus Christi International Airport (United States)" }, +{ "station": "72261", "rs_types": ["24"], "lat": 29.37, "lon": -100.92, "alt": 304, "station_name": "Del Rio, Del Rio International Airport (United States)" }, +{ "station": "72265", "rs_types": ["82"], "lat": 31.95, "lon": -102.21, "alt": 875, "station_name": "Midland, Midland International Airport (United States)" }, +{ "station": "72274", "rs_types": ["52"], "lat": 32.13, "lon": -110.96, "alt": 805, "station_name": "Tucson, Tucson International Airport (United States)" }, +{ "station": "72293", "rs_types": ["24"], "lat": 32.87, "lon": -117.14, "alt": 146, "station_name": "San Diego, Miramar MCAS/Mitscher Field Airport (United States)" }, +{ "station": "72305", "rs_types": ["52"], "lat": 34.78, "lon": -76.88, "alt": 11, "station_name": "Newport (United States)" }, +{ "station": "72317", "rs_types": ["82"], "lat": 36.10, "lon": -79.94, "alt": 282, "station_name": "Greensboro, Piedmont Triad International Airport (United States)" }, +{ "station": "72318", "rs_types": ["11"], "lat": 37.20, "lon": -80.42, "alt": 653, "station_name": "Blacksburg, Va. (United States)" }, +{ "station": "72327", "rs_types": ["82"], "lat": 36.12, "lon": -86.69, "alt": 182, "station_name": "Nashville, Nashville International Airport (United States)" }, +{ "station": "72340", "rs_types": ["82"], "lat": 34.83, "lon": -92.25, "alt": 165, "station_name": "North Little Rock, North Little Rock Airport (United States)" }, +{ "station": "72357", "rs_types": ["11"], "lat": 35.22, "lon": -97.45, "alt": 357, "station_name": "Norman / Max Westheimer (United States)" }, +{ "station": "72363", "rs_types": ["82"], "lat": 35.22, "lon": -101.72, "alt": 1099, "station_name": "Amarillo, Amarillo International Airport (United States)" }, +{ "station": "72364", "rs_types": ["52"], "lat": 31.87, "lon": -106.70, "alt": 1252, "station_name": "Santa Teresa (United States)" }, +{ "station": "72365", "rs_types": ["82"], "lat": 35.04, "lon": -106.61, "alt": 1631, "station_name": "Albuquerque, Albuquerque International Airport (United States)" }, +{ "station": "72376", "rs_types": ["52"], "lat": 35.23, "lon": -111.82, "alt": 2192, "station_name": "Flagstaff, Az. (United States)" }, +{ "station": "72381", "rs_types": ["11"], "lat": 34.92, "lon": -117.90, "alt": 702, "station_name": "Edwards Air Force Base (United States)" }, +{ "station": "72393", "rs_types": ["11", "49"], "lat": 34.75, "lon": -120.57, "alt": 112, "station_name": "Vandenberg Air Force Base (United States)" }, +{ "station": "72402", "rs_types": ["11"], "lat": 37.94, "lon": -75.46, "alt": 12, "station_name": "Wallops Island, Wallops Flight Facility Airport (United States)" }, +{ "station": "72403", "rs_types": ["11"], "lat": 38.93, "lon": -77.45, "alt": 95, "station_name": "Washington DC, Washington-Dulles International Airport (United States)" }, +{ "station": "72426", "rs_types": ["82"], "lat": 39.42, "lon": -83.82, "alt": 317, "station_name": "Wilmington, Oh. (United States)" }, +{ "station": "72440", "rs_types": ["52"], "lat": 37.24, "lon": -93.39, "alt": 386, "station_name": "Springfield, Springfield Regional Airport (United States)" }, +{ "station": "72451", "rs_types": ["82"], "lat": 37.77, "lon": -99.97, "alt": 790, "station_name": "Dodge City, Dodge City Regional Airport (United States)" }, +{ "station": "72456", "rs_types": ["11"], "lat": 39.07, "lon": -95.63, "alt": 268, "station_name": "Topeka, Philip Billard Municipal Airport (United States)" }, +{ "station": "72469", "rs_types": ["11"], "lat": 39.78, "lon": -104.87, "alt": 1626, "station_name": "Denver / Stapleton International, Co. (United States)" }, +{ "station": "72476", "rs_types": ["82"], "lat": 39.13, "lon": -108.54, "alt": 1481, "station_name": "Grand Junction, Walker Field (United States)" }, +{ "station": "72489", "rs_types": ["82"], "lat": 39.57, "lon": -119.80, "alt": 1463, "station_name": "Reno, Nv. (United States)" }, +{ "station": "72493", "rs_types": ["24"], "lat": 37.72, "lon": -122.24, "alt": 1, "station_name": "Oakland, Metro Oakland International Airport (United States)" }, +{ "station": "72501", "rs_types": ["52"], "lat": 40.87, "lon": -72.87, "alt": 20, "station_name": "Upton, Ny. (United States)" }, +{ "station": "72518", "rs_types": ["82"], "lat": 42.75, "lon": -73.80, "alt": 87, "station_name": "Albany, Albany International Airport (United States)" }, +{ "station": "72520", "rs_types": ["82"], "lat": 40.50, "lon": -80.27, "alt": 366, "station_name": "Pittsburgh, Pittsburgh International Airport (United States)" }, +{ "station": "72528", "rs_types": ["82"], "lat": 42.94, "lon": -78.74, "alt": 220, "station_name": "Buffalo, Greater Buffalo International Airport (United States)" }, +{ "station": "72558", "rs_types": ["82"], "lat": 41.32, "lon": -96.37, "alt": 350, "station_name": "Valley (United States)" }, +{ "station": "72562", "rs_types": ["82"], "lat": 41.12, "lon": -100.67, "alt": 846, "station_name": "North Platte, North Platte Regional Airport (United States)" }, +{ "station": "72572", "rs_types": ["82"], "lat": 40.78, "lon": -111.97, "alt": 1288, "station_name": "Salt Lake City, Salt Lake City International Airport (United States)" }, +{ "station": "72582", "rs_types": ["82"], "lat": 40.87, "lon": -115.73, "alt": 1607, "station_name": "Elko, Nv. (United States)" }, +{ "station": "72597", "rs_types": ["52"], "lat": 42.38, "lon": -122.87, "alt": 405, "station_name": "Medford, Rogue Valley International Airport (United States)" }, +{ "station": "72632", "rs_types": ["52"], "lat": 42.70, "lon": -83.47, "alt": 321, "station_name": "White Lake, Mi. (United States)" }, +{ "station": "72634", "rs_types": ["52"], "lat": 44.92, "lon": -84.72, "alt": 446, "station_name": "Gaylord, Mi. (United States)" }, +{ "station": "72645", "rs_types": ["82"], "lat": 44.48, "lon": -88.14, "alt": 211, "station_name": "Green Bay, Austin Straubel International Airport (United States)" }, +{ "station": "72649", "rs_types": ["82"], "lat": 44.85, "lon": -93.57, "alt": 287, "station_name": "Chanhaussen, Mn. (United States)" }, +{ "station": "72659", "rs_types": ["82"], "lat": 45.44, "lon": -98.41, "alt": 396, "station_name": "Aberdeen, Aberdeen Regional Airport (United States)" }, +{ "station": "72662", "rs_types": ["82"], "lat": 44.05, "lon": -103.05, "alt": 976, "station_name": "Rapid City, Rapid City Regional Airport (United States)" }, +{ "station": "72672", "rs_types": ["82"], "lat": 43.06, "lon": -108.45, "alt": 1684, "station_name": "Riverton, Riverton Regional Airport (United States)" }, +{ "station": "72681", "rs_types": ["11"], "lat": 43.57, "lon": -116.24, "alt": 871, "station_name": "Boise, Boise Air Terminal (United States)" }, +{ "station": "72694", "rs_types": ["24"], "lat": 44.91, "lon": -123.00, "alt": 64, "station_name": "Salem, McNary Field (United States)" }, +{ "station": "72712", "rs_types": ["52"], "lat": 46.87, "lon": -68.01, "alt": 191, "station_name": "Caribou, Caribou Municipal Airport (United States)" }, +{ "station": "72747", "rs_types": ["11"], "lat": 48.56, "lon": -93.40, "alt": 361, "station_name": "International Falls, Falls International Airport (United States)" }, +{ "station": "72764", "rs_types": ["82"], "lat": 46.78, "lon": -100.76, "alt": 511, "station_name": "Bismarck, Bismarck Municipal Airport (United States)" }, +{ "station": "72768", "rs_types": ["82"], "lat": 48.21, "lon": -106.62, "alt": 699, "station_name": "Glasgow, Glasgow International Airport (United States)" }, +{ "station": "72776", "rs_types": ["52"], "lat": 47.47, "lon": -111.38, "alt": 1131, "station_name": "Great Falls, Mt. (United States)" }, +{ "station": "72786", "rs_types": ["82"], "lat": 47.68, "lon": -117.63, "alt": 728, "station_name": "Spokane, Wa. (United States)" }, +{ "station": "72797", "rs_types": ["24"], "lat": 47.94, "lon": -124.55, "alt": 59, "station_name": "Quillayute, Quillayute State Airport (United States)" }, +{ "station": "74004", "rs_types": ["41"], "lat": 32.50, "lon": -114.00, "alt": 231, "station_name": "Yuma Proving Grounds Az. (United States)" }, +{ "station": "74389", "rs_types": ["52"], "lat": 43.90, "lon": -70.25, "alt": 125, "station_name": "Gray, Me. (United States)" }, +{ "station": "74455", "rs_types": ["11"], "lat": 41.62, "lon": -90.58, "alt": 229, "station_name": "Davenport, Ia. (United States)" }, +{ "station": "74560", "rs_types": ["82"], "lat": 40.15, "lon": -89.33, "alt": 178, "station_name": "Lincoln, Il. (United States)" }, +{ "station": "74626", "rs_types": ["41"], "lat": 33.45, "lon": -111.95, "alt": 386, "station_name": "Wfo Phoenix, Az. (United States)" }, +{ "station": "74646", "rs_types": ["23"], "lat": 34.52, "lon": -96.98, "alt": 320, "station_name": "Sulphur Municipal (United States)" }, +{ "station": "74794", "rs_types": [" "], "lat": 28.47, "lon": -80.57, "alt": 3, "station_name": "Cocoa Beach, Cape Canaveral Air Force Station Skid Strip (United States)" }, +{ "station": "76225", "rs_types": ["41", "35"], "lat": 28.63, "lon": -106.08, "alt": 1435, "station_name": "University De Chihuahua, Chihuahua, Chih. (Mexico)" }, +{ "station": "76256", "rs_types": ["41", "35"], "lat": 27.92, "lon": -110.90, "alt": 11, "station_name": "Empalme, Son. (Mexico)" }, +{ "station": "76394", "rs_types": ["41"], "lat": 25.87, "lon": -100.23, "alt": 448, "station_name": "Aerop. Internacional Monterrey, N. L. (Mexico)" }, +{ "station": "76405", "rs_types": ["41"], "lat": 24.27, "lon": -110.42, "alt": 18, "station_name": "La Paz, B. C. S. (Mexico)" }, +{ "station": "76458", "rs_types": ["41", "35"], "lat": 23.20, "lon": -106.42, "alt": 4, "station_name": "Colonia Juan Carras-Co Mazatlan, Sin. (Mexico)" }, +{ "station": "76595", "rs_types": ["41"], "lat": 21.03, "lon": -87.42, "alt": 10, "station_name": "Cancun (Mexico)" }, +{ "station": "76612", "rs_types": ["41", "35"], "lat": 20.67, "lon": -103.38, "alt": 1551, "station_name": "Guadalajara, Jal. (Mexico)" }, +{ "station": "76644", "rs_types": ["41"], "lat": 20.93, "lon": -89.65, "alt": 10, "station_name": "Merida / lic Manuel Crecencio (Mexico)" }, +{ "station": "76654", "rs_types": ["41"], "lat": 19.05, "lon": -104.33, "alt": 3, "station_name": "Manzanillo, Col. (Mexico)" }, +{ "station": "76679", "rs_types": ["41", "35"], "lat": 19.43, "lon": -99.08, "alt": 2234, "station_name": "Aerop. Internacionalmexico, D. F. (Mexico)" }, +{ "station": "76692", "rs_types": ["41", "35"], "lat": 19.15, "lon": -96.12, "alt": 13, "station_name": "Hacienda Ylang Ylangveracruz, Ver. (Mexico)" }, +{ "station": "76743", "rs_types": ["41"], "lat": 17.98, "lon": -92.92, "alt": 10, "station_name": "Villahermosa, Tab. (Mexico)" }, +{ "station": "76805", "rs_types": ["41", "35"], "lat": 16.83, "lon": -99.93, "alt": 13, "station_name": "Acapulco, Gro. (Mexico)" }, +{ "station": "76903", "rs_types": ["41"], "lat": 14.92, "lon": -92.27, "alt": 118, "station_name": "Tapachula, Chis (Mexico)" }, +{ "station": "78016", "rs_types": ["41"], "lat": 32.37, "lon": -64.68, "alt": 6, "station_name": "Bermuda (Bermuda)" }, +{ "station": "78384", "rs_types": ["17"], "lat": 19.28, "lon": -81.35, "alt": 3, "station_name": "Owen Roberts Airportgrand Cayman (Cayman Islands)" }, +{ "station": "78397", "rs_types": ["17"], "lat": 17.93, "lon": -76.78, "alt": 3, "station_name": "Kingston / Norman Manley (Jamaica)" }, +{ "station": "78486", "rs_types": ["17"], "lat": 18.43, "lon": -69.88, "alt": 14, "station_name": "Santo Domingo (Dominican Republic)" }, +{ "station": "78526", "rs_types": ["52"], "lat": 18.43, "lon": -66.00, "alt": 3, "station_name": "San Juan / Int., Puerto Rico (Puerto Rico)" }, +{ "station": "78762", "rs_types": ["41"], "lat": 10.00, "lon": -84.22, "alt": 920, "station_name": "Juan Santamaria (Costa Rica)" }, +{ "station": "78807", "rs_types": ["41"], "lat": 8.97, "lon": -79.57, "alt": 7, "station_name": "Corozal Oeste (Panama)" }, +{ "station": "78866", "rs_types": ["17"], "lat": 18.05, "lon": -63.12, "alt": 4, "station_name": "Juliana Airport, Saint Maarten (Netherlands Antilles)" }, +{ "station": "78897", "rs_types": ["77"], "lat": 16.27, "lon": -61.52, "alt": 11, "station_name": "Le Raizet, Guadeloupe (Guadeloupe)" }, +{ "station": "78954", "rs_types": ["17"], "lat": 13.07, "lon": -59.48, "alt": 50, "station_name": "Grantley Adams (Barbados)" }, +{ "station": "78970", "rs_types": ["17"], "lat": 10.62, "lon": -61.35, "alt": 12, "station_name": "Piarco International Airport, Trinidad (Trinidad and Tobago)" }, +{ "station": "78988", "rs_types": ["17"], "lat": 12.20, "lon": -68.97, "alt": 9, "station_name": "Hato Airport, Curacao (Netherlands Antilles)" }, +{ "station": "80001", "rs_types": ["17"], "lat": 12.58, "lon": -81.72, "alt": 1, "station_name": "San Andres Isla / Sesquicentenario (Colombia)" }, +{ "station": "80222", "rs_types": ["41"], "lat": 4.72, "lon": -74.15, "alt": 2547, "station_name": "Bogota / Eldorado (Colombia)" }, +{ "station": "81405", "rs_types": ["77"], "lat": 4.83, "lon": -52.37, "alt": 105, "station_name": "Cayenne / Rochambeau (French Guiana)" }, +{ "station": "82022", "rs_types": ["23"], "lat": 2.83, "lon": -60.70, "alt": 84, "station_name": "Boa Vista, Boa Vista Intl (Brazil)" }, +{ "station": "82026", "rs_types": ["23"], "lat": 2.48, "lon": -55.98, "alt": 325, "station_name": "Tirios (Brazil)" }, +{ "station": "82099", "rs_types": ["23"], "lat": 0.05, "lon": -51.07, "alt": 16, "station_name": "Macapa-Aeroporto (Brazil)" }, +{ "station": "82107", "rs_types": ["23"], "lat": -0.12, "lon": -66.97, "alt": 79, "station_name": "Sao Gabriel Da Cachoeira (Aero) (Brazil)" }, +{ "station": "82193", "rs_types": ["23"], "lat": -1.38, "lon": -48.48, "alt": 16, "station_name": "Belem Aeroporto (Brazil)" }, +{ "station": "82244", "rs_types": ["23"], "lat": -2.43, "lon": -54.72, "alt": 72, "station_name": "Santarem-Aeroporto (Brazil)" }, +{ "station": "82332", "rs_types": ["23"], "lat": -3.15, "lon": -59.98, "alt": 84, "station_name": "Manaus Aeroporto (Brazil)" }, +{ "station": "82400", "rs_types": ["24", "42"], "lat": -3.85, "lon": -32.42, "alt": 56, "station_name": "Fernando De Noronha (Brazil)" }, +{ "station": "82411", "rs_types": ["23"], "lat": -3.67, "lon": -69.67, "alt": 85, "station_name": "Tabatinga (Brazil)" }, +{ "station": "82532", "rs_types": ["23"], "lat": -5.82, "lon": -61.28, "alt": 53, "station_name": "Manicore (Aero) (Brazil)" }, +{ "station": "82599", "rs_types": ["23"], "lat": -5.92, "lon": -35.25, "alt": 52, "station_name": "Natal Aeroporto (Brazil)" }, +{ "station": "82705", "rs_types": ["23"], "lat": -7.58, "lon": -72.77, "alt": 199, "station_name": "Cruzeiro Do Sul (Aero) (Brazil)" }, +{ "station": "82824", "rs_types": ["23"], "lat": -8.77, "lon": -63.92, "alt": 102, "station_name": "Porto Velho Aeroporto (Brazil)" }, +{ "station": "82917", "rs_types": ["23"], "lat": -10.00, "lon": -67.80, "alt": 142, "station_name": "Rio Branco (Brazil)" }, +{ "station": "82965", "rs_types": ["23"], "lat": -9.87, "lon": -56.10, "alt": 288, "station_name": "Alta Floresta Aeroporto (Brazil)" }, +{ "station": "83208", "rs_types": ["23"], "lat": -12.70, "lon": -60.10, "alt": 612, "station_name": "Vilhena Aeroporto (Brazil)" }, +{ "station": "83362", "rs_types": ["23"], "lat": -15.65, "lon": -56.10, "alt": 187, "station_name": "Cuiaba Aeroporto (Brazil)" }, +{ "station": "83378", "rs_types": ["23"], "lat": -15.87, "lon": -47.93, "alt": 1061, "station_name": "Brasilia Aeroporto (Brazil)" }, +{ "station": "83525", "rs_types": ["23"], "lat": -18.88, "lon": -48.22, "alt": 943, "station_name": "Uberlandia (Aero) (Brazil)" }, +{ "station": "83554", "rs_types": ["23"], "lat": -19.00, "lon": -57.67, "alt": 141, "station_name": "Corumba Aeroporto (Brazil)" }, +{ "station": "83566", "rs_types": ["23"], "lat": -19.62, "lon": -43.57, "alt": 827, "station_name": "Confis Intnl Airport (Brazil)" }, +{ "station": "83612", "rs_types": ["23"], "lat": -20.47, "lon": -54.67, "alt": 567, "station_name": "Campo Grande Aeroporto (Brazil)" }, +{ "station": "83649", "rs_types": ["23"], "lat": -20.27, "lon": -40.28, "alt": 4, "station_name": "Vitoria Aeroporto (Brazil)" }, +{ "station": "83746", "rs_types": ["23"], "lat": -22.82, "lon": -43.25, "alt": 6, "station_name": "Galeao (Brazil)" }, +{ "station": "83768", "rs_types": ["23"], "lat": -23.33, "lon": -51.13, "alt": 569, "station_name": "Londrina Aeroporto (Brazil)" }, +{ "station": "83779", "rs_types": ["14", "23"], "lat": -23.52, "lon": -46.63, "alt": 722, "station_name": "Marte Civ / Mil (Brazil)" }, +{ "station": "83827", "rs_types": ["23"], "lat": -25.52, "lon": -54.58, "alt": 180, "station_name": "Foz Do Iguacu Aeroporto (Brazil)" }, +{ "station": "83840", "rs_types": ["23"], "lat": -25.52, "lon": -49.17, "alt": 908, "station_name": "Curitiba Aeroporto (Brazil)" }, +{ "station": "83899", "rs_types": ["23"], "lat": -27.67, "lon": -48.55, "alt": 5, "station_name": "Florianopolis Aeroporto (Brazil)" }, +{ "station": "83928", "rs_types": ["24"], "lat": -29.78, "lon": -57.03, "alt": 74, "station_name": "Uruguaiana Aeroporto (Brazil)" }, +{ "station": "83937", "rs_types": ["23"], "lat": -29.72, "lon": -53.70, "alt": 85, "station_name": "Santa Maria Aero-Porto (Brazil)" }, +{ "station": "83971", "rs_types": ["23"], "lat": -30.00, "lon": -51.18, "alt": 3, "station_name": "Porto Alegre Aero-Porto (Brazil)" }, +{ "station": "85442", "rs_types": ["41"], "lat": -23.43, "lon": -70.43, "alt": 135, "station_name": "Antofagasta (Chile)" }, +{ "station": "85469", "rs_types": ["41"], "lat": -27.15, "lon": -109.42, "alt": 51, "station_name": "Isla De Pascua (Chile)" }, +{ "station": "85577", "rs_types": ["41"], "lat": -33.43, "lon": -70.68, "alt": 520, "station_name": "Santiago Q. Normal (Chile)" }, +{ "station": "85586", "rs_types": ["41"], "lat": -33.65, "lon": -71.62, "alt": 75, "station_name": "Santo Domingo (Chile)" }, +{ "station": "85799", "rs_types": ["41"], "lat": -41.42, "lon": -73.08, "alt": 85, "station_name": "Puerto Montt (Chile)" }, +{ "station": "85934", "rs_types": ["41"], "lat": -53.00, "lon": -70.85, "alt": 37, "station_name": "Punta Arenas (Chile)" }, +{ "station": "87155", "rs_types": ["41"], "lat": -27.45, "lon": -59.05, "alt": 53, "station_name": "Resistencia Aero. (Argentina)" }, +{ "station": "87576", "rs_types": ["41"], "lat": -34.82, "lon": -58.53, "alt": 20, "station_name": "Ezeiza Aerodrome (Argentina)" }, +{ "station": "87860", "rs_types": ["41"], "lat": -45.78, "lon": -67.50, "alt": 58, "station_name": "Comodoro Rivadavia Aerodrome (Argentina)" }, +{ "station": "88889", "rs_types": ["41"], "lat": -51.82, "lon": -58.45, "alt": 74, "station_name": "Mount Pleasant Airport (Falkland Islands)" }, +{ "station": "89009", "rs_types": ["23"], "lat": -90.00, "lon": 0.00, "alt": 2830, "station_name": "South Pole (Antarctica)" }, +{ "station": "89062", "rs_types": ["41"], "lat": -67.57, "lon": -68.13, "alt": 16, "station_name": "Rothera Point (Antarctica)" }, +{ "station": "89532", "rs_types": ["22"], "lat": -69.00, "lon": 39.58, "alt": 18, "station_name": "Syowa (Antarctica)" }, +{ "station": "89564", "rs_types": ["41"], "lat": -67.60, "lon": 62.87, "alt": 10, "station_name": "Mawson (Antarctica)" }, +{ "station": "89571", "rs_types": ["41", "23"], "lat": -68.58, "lon": 77.97, "alt": 18, "station_name": "Davis (Antarctica)" }, +{ "station": "89611", "rs_types": ["41"], "lat": -66.28, "lon": 110.52, "alt": 40, "station_name": "Casey (Antarctica)" }, +{ "station": "89625", "rs_types": ["23"], "lat": -75.10, "lon": 123.40, "alt": 3232, "station_name": "Concordia (Antarctica)" }, +{ "station": "89642", "rs_types": ["77"], "lat": -66.67, "lon": 140.02, "alt": 41, "station_name": "Dumont D'Urville (Antarctica)" }, +{ "station": "91165", "rs_types": ["11", "24"], "lat": 21.98, "lon": -159.34, "alt": 45, "station_name": "Lihue, Lihue Airport (United States)" }, +{ "station": "91212", "rs_types": ["52"], "lat": 13.48, "lon": 144.80, "alt": 91, "station_name": "Agana, Guam International Airport (United States)" }, +{ "station": "91285", "rs_types": ["24"], "lat": 19.72, "lon": -155.06, "alt": 11, "station_name": "Hilo, Hilo International Airport (United States)" }, +{ "station": "91334", "rs_types": ["82"], "lat": 7.47, "lon": 151.85, "alt": 3, "station_name": "Weno Island, Chuuk Int. Airp. (Micronesia, Federated States of)" }, +{ "station": "91348", "rs_types": ["82"], "lat": 6.98, "lon": 158.20, "alt": 2, "station_name": "Pohnpei Island, Pohnpei Int. Airp. (Micronesia, Federated States of)" }, +{ "station": "91366", "rs_types": ["07", "90"], "lat": 8.72, "lon": 167.73, "alt": 3, "station_name": "Kwajalein, Bucholz AAF, Kwajalein KMR ATOL Airport (Marshall Islands)" }, +{ "station": "91376", "rs_types": ["82"], "lat": 7.07, "lon": 171.28, "alt": 2, "station_name": "Majuro Atoll, Marshall Islands Int. Airp. (Marshall Islands)" }, +{ "station": "91408", "rs_types": ["11"], "lat": 7.37, "lon": 134.53, "alt": 54, "station_name": "Babelthuap Island, Babelthuap/Koror Airport (Palau)" }, +{ "station": "91413", "rs_types": ["82"], "lat": 9.48, "lon": 138.08, "alt": 28, "station_name": "Yap Island, Yap Int. Airp. (Micronesia, Federated States of)" }, +{ "station": "91592", "rs_types": ["77"], "lat": -22.27, "lon": 166.45, "alt": 69, "station_name": "Noumea Nlle-Caledonie (New Caledonia)" }, +{ "station": "91610", "rs_types": ["41"], "lat": 1.35, "lon": 172.92, "alt": 2, "station_name": "Tarawa (Kiribati)" }, +{ "station": "91643", "rs_types": ["41"], "lat": -8.52, "lon": 179.22, "alt": 1, "station_name": "Funafuti (Tuvalu)" }, +{ "station": "91680", "rs_types": ["41"], "lat": -17.75, "lon": 177.45, "alt": 13, "station_name": "Nandi (Fiji)" }, +{ "station": "91765", "rs_types": ["82"], "lat": -14.33, "lon": -170.72, "alt": 3, "station_name": "Pago Pago / Int. Airport (United States Minor Outlying Islands)" }, +{ "station": "91925", "rs_types": ["77"], "lat": -9.80, "lon": -139.03, "alt": 51, "station_name": "Atuona (French Polynesia)" }, +{ "station": "91938", "rs_types": ["77"], "lat": -17.55, "lon": -149.62, "alt": 2, "station_name": "Tahiti-Faaa (French Polynesia)" }, +{ "station": "91948", "rs_types": ["77"], "lat": -23.13, "lon": -134.97, "alt": 91, "station_name": "Rikitea (French Polynesia)" }, +{ "station": "91958", "rs_types": ["77"], "lat": -27.62, "lon": -144.33, "alt": 1, "station_name": "Rapa (French Polynesia)" }, +{ "station": "93112", "rs_types": ["41"], "lat": -36.78, "lon": 174.63, "alt": 30, "station_name": "Whenuapai (New Zealand)" }, +{ "station": "93417", "rs_types": ["41"], "lat": -40.90, "lon": 174.98, "alt": 7, "station_name": "Paraparaumu Aerodrome (New Zealand)" }, +{ "station": "93844", "rs_types": ["41"], "lat": -46.70, "lon": 168.55, "alt": 4, "station_name": "Invercargill Aerodrome (New Zealand)" }, +{ "station": "94120", "rs_types": ["41"], "lat": -12.40, "lon": 130.87, "alt": 31, "station_name": "Darwin Airport (Australia)" }, +{ "station": "94150", "rs_types": ["41"], "lat": -12.27, "lon": 136.82, "alt": 52, "station_name": "Gove Airport (Australia)" }, +{ "station": "94170", "rs_types": ["42"], "lat": -12.68, "lon": 141.92, "alt": 20, "station_name": "Weipa Amo (Australia)" }, +{ "station": "94203", "rs_types": ["41"], "lat": -17.95, "lon": 122.22, "alt": 17, "station_name": "Broome Airport (Australia)" }, +{ "station": "94299", "rs_types": ["41"], "lat": -16.30, "lon": 149.98, "alt": 6, "station_name": "Willis Island (Australia)" }, +{ "station": "94302", "rs_types": ["42"], "lat": -22.23, "lon": 114.08, "alt": 5, "station_name": "Learmonth Airport (Australia)" }, +{ "station": "94312", "rs_types": ["42"], "lat": -20.10, "lon": 119.57, "alt": 9, "station_name": "Port Hedland Pardoo (Australia)" }, +{ "station": "94326", "rs_types": ["41"], "lat": -23.80, "lon": 133.88, "alt": 545, "station_name": "Alice Springs Aerodrome (Australia)" }, +{ "station": "94332", "rs_types": ["42"], "lat": -20.67, "lon": 139.47, "alt": 342, "station_name": "Mount Isa Amo (Australia)" }, +{ "station": "94374", "rs_types": ["41"], "lat": -23.38, "lon": 150.47, "alt": 10, "station_name": "Rockhampton Airport (Australia)" }, +{ "station": "94403", "rs_types": ["42"], "lat": -28.78, "lon": 114.70, "alt": 37, "station_name": "Geraldton Airport (Australia)" }, +{ "station": "94430", "rs_types": ["42"], "lat": -26.60, "lon": 118.53, "alt": 522, "station_name": "Meekatharra Airport (Australia)" }, +{ "station": "94461", "rs_types": ["41"], "lat": -25.03, "lon": 128.28, "alt": 580, "station_name": "Giles (Australia)" }, +{ "station": "94510", "rs_types": ["42"], "lat": -26.40, "lon": 146.27, "alt": 306, "station_name": "Charleville Airport (Australia)" }, +{ "station": "94578", "rs_types": ["41"], "lat": -27.38, "lon": 153.10, "alt": 4, "station_name": "Brisbane Airport M. O (Australia)" }, +{ "station": "94610", "rs_types": ["41"], "lat": -31.93, "lon": 115.95, "alt": 20, "station_name": "Belmont Perth Airport (Australia)" }, +{ "station": "94637", "rs_types": ["42"], "lat": -30.78, "lon": 121.45, "alt": 367, "station_name": "Kalgoorlie Boulder Amo (Australia)" }, +{ "station": "94638", "rs_types": ["42"], "lat": -33.82, "lon": 121.88, "alt": 25, "station_name": "Esperance (Australia)" }, +{ "station": "94653", "rs_types": ["42"], "lat": -32.12, "lon": 133.70, "alt": 23, "station_name": "Ceduna Airport (Australia)" }, +{ "station": "94659", "rs_types": ["42"], "lat": -31.13, "lon": 136.82, "alt": 165, "station_name": "Woomera Aerodrome (Australia)" }, +{ "station": "94672", "rs_types": ["41"], "lat": -34.93, "lon": 138.52, "alt": 6, "station_name": "Adelaide Airport (Australia)" }, +{ "station": "94711", "rs_types": ["42"], "lat": -31.48, "lon": 145.82, "alt": 264, "station_name": "Cobar (Australia)" }, +{ "station": "94750", "rs_types": ["41"], "lat": -34.95, "lon": 150.53, "alt": 122, "station_name": "Nowra Ran Air Station (Australia)" }, +{ "station": "94767", "rs_types": ["41"], "lat": -33.95, "lon": 151.18, "alt": 6, "station_name": "Sydney Airport (Australia)" }, +{ "station": "94776", "rs_types": ["42"], "lat": -32.78, "lon": 151.82, "alt": 9, "station_name": "Williamtown Aerodrome (Australia)" }, +{ "station": "94802", "rs_types": ["42"], "lat": -34.93, "lon": 117.80, "alt": 71, "station_name": "Albany Airport (Australia)" }, +{ "station": "94821", "rs_types": ["42"], "lat": -37.73, "lon": 140.78, "alt": 65, "station_name": "Mount Gambier Aerodrome (Australia)" }, +{ "station": "94866", "rs_types": ["41"], "lat": -37.67, "lon": 144.83, "alt": 132, "station_name": "Melbourne Airport (Australia)" }, +{ "station": "94910", "rs_types": ["42"], "lat": -35.15, "lon": 147.45, "alt": 221, "station_name": "Wagga Airport (Australia)" }, +{ "station": "94975", "rs_types": ["41"], "lat": -42.83, "lon": 147.48, "alt": 4, "station_name": "Hobart Airport (Australia)" }, +{ "station": "94995", "rs_types": ["42"], "lat": -31.53, "lon": 159.07, "alt": 5, "station_name": "Lord Howe Island (Australia)" }, +{ "station": "94996", "rs_types": ["41"], "lat": -29.03, "lon": 167.93, "alt": 113, "station_name": "Norfolk Island Airport (Australia)" }, +{ "station": "94998", "rs_types": ["41", "23"], "lat": -54.48, "lon": 158.95, "alt": 6, "station_name": "Macquarie Island (Australia)" }, +{ "station": "95527", "rs_types": ["42"], "lat": -29.48, "lon": 149.83, "alt": 214, "station_name": "Moree Mo (Australia)" }, +{ "station": "96413", "rs_types": ["54"], "lat": 1.48, "lon": 110.33, "alt": 27, "station_name": "Kuching (Malaysia)" }, +{ "station": "96441", "rs_types": ["54"], "lat": 3.20, "lon": 113.03, "alt": 2, "station_name": "Bintulu (Malaysia)" }, +{ "station": "96471", "rs_types": ["54"], "lat": 5.93, "lon": 116.05, "alt": 3, "station_name": "Kota Kinabalu (Malaysia)" }, +{ "station": "96481", "rs_types": ["54"], "lat": 4.27, "lon": 117.88, "alt": 18, "station_name": "Tawau (Malaysia)" }, +{ "station": "96581", "rs_types": ["35"], "lat": -0.15, "lon": 109.40, "alt": 3, "station_name": "Pontianak / Supadio (Indonesia)" }, +{ "station": "96749", "rs_types": ["35"], "lat": -6.12, "lon": 106.65, "alt": 8, "station_name": "Jakarta / Soekarno-Hatta (Indonesia)" }, +{ "station": "96996", "rs_types": ["42"], "lat": -12.18, "lon": 96.82, "alt": 3, "station_name": "Cocos Island Airport (Christmas Island)" }, +{ "station": "97014", "rs_types": ["35"], "lat": 1.53, "lon": 124.92, "alt": 80, "station_name": "Menado / Dr. Sam Ratulangi (Indonesia)" }, +{ "station": "97072", "rs_types": ["35"], "lat": -0.68, "lon": 119.73, "alt": 6, "station_name": "Palu / Mutiara (Indonesia)" }, +{ "station": "97180", "rs_types": ["35"], "lat": -5.07, "lon": 119.55, "alt": 14, "station_name": "Ujung Pandang / Hasanuddin (Indonesia)" }, +{ "station": "97372", "rs_types": ["35"], "lat": -10.17, "lon": 123.67, "alt": 108, "station_name": "Kupang / El Tari (Indonesia)" }, +{ "station": "97502", "rs_types": ["77"], "lat": -0.93, "lon": 131.12, "alt": 3, "station_name": "Sorong / Jefman (Indonesia)" }, +{ "station": "97560", "rs_types": ["35"], "lat": -1.18, "lon": 136.12, "alt": 11, "station_name": "Biak / Mokmer (Indonesia)" }, +{ "station": "97724", "rs_types": ["35"], "lat": -3.70, "lon": 128.08, "alt": 12, "station_name": "Ambon / Pattimura (Indonesia)" }, +{ "station": "97900", "rs_types": ["35"], "lat": -7.98, "lon": 131.30, "alt": 24, "station_name": "Saumlaki (Indonesia)" }, +{ "station": "97980", "rs_types": ["35"], "lat": -8.47, "lon": 140.38, "alt": 3, "station_name": "Merauke / Mopah (Indonesia)" }, +{ "station": "98223", "rs_types": ["11", "18"], "lat": 18.18, "lon": 120.53, "alt": 4, "station_name": "Laoag (Philippines)" }, +{ "station": "98233", "rs_types": ["11"], "lat": 17.62, "lon": 121.73, "alt": 61, "station_name": "Tuguegarao (Philippines)" }, +{ "station": "98328", "rs_types": ["11"], "lat": 16.42, "lon": 120.60, "alt": 1500, "station_name": "Baguio (Philippines)" }, +{ "station": "98433", "rs_types": ["11"], "lat": 14.50, "lon": 121.35, "alt": 614, "station_name": "Tanay (Philippines)" }, +{ "station": "98444", "rs_types": ["41"], "lat": 13.13, "lon": 123.73, "alt": 16, "station_name": "Legaspi (Philippines)" }, +{ "station": "98558", "rs_types": ["11"], "lat": 11.03, "lon": 126.73, "alt": 56, "station_name": "Guiuan (Philippines)" }, +{ "station": "98618", "rs_types": ["11"], "lat": 9.75, "lon": 118.73, "alt": 14, "station_name": "Puerto Princesa (Philippines)" }, +{ "station": "98646", "rs_types": ["41"], "lat": 10.30, "lon": 123.97, "alt": 23, "station_name": "Mactan (Philippines)" }, +{ "station": "98753", "rs_types": ["11"], "lat": 7.12, "lon": 125.65, "alt": 17, "station_name": "Davao Airport (Philippines)" } +] diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..acb0ecb --- /dev/null +++ b/manifest.json @@ -0,0 +1,63 @@ +{ + "name": "SondeHub Tracker", + "short_name": "SondeHub", + "description": "A webapp for tracking radiosondes.", + "theme_color": "#00a3d3", + "background_color": "#00a3d3", + "display": "standalone", + "categories": ["utilities"], + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "/img/icons/maskable_icon_x192.png", + "type": "image/png", + "sizes": "192x192", + "purpose": "maskable" + }, + { + "src": "/img/icons/maskable_icon_x512.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "maskable" + }, + { + "src": "/img/icons/icon_x192.png", + "type": "image/png", + "sizes": "192x192", + "purpose": "any" + }, + { + "src": "/img/icons/icon_x512.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "any" + } + ], + "shortcuts": [ + { + "name": "Nyan Mode", + "short_name": "Nyan", + "description": "Start the tracker with Nyan Cat mode enabled", + "url": "/#!nyan=1", + "icons": [{ "src": "/img/icons/nyan_icon_x192.png", "sizes": "192x192" }] + } + ], + "screenshots": [ + { + "src": "/img/screenshots/screenshot1.png", + "type": "image/png", + "sizes": "1242x2208" + }, + { + "src": "/img/screenshots/screenshot2.png", + "type": "image/png", + "sizes": "1242x2208" + }, + { + "src": "/img/screenshots/screenshot3.png", + "type": "image/png", + "sizes": "1242x2208" + } + ] +} diff --git a/resources/mobiletracker-screencap.png b/resources/mobiletracker-screencap.png index 09467bf..71237bc 100644 Binary files a/resources/mobiletracker-screencap.png and b/resources/mobiletracker-screencap.png differ diff --git a/resources/nyan_icon.psd b/resources/nyan_icon.psd new file mode 100644 index 0000000..8d6845e Binary files /dev/null and b/resources/nyan_icon.psd differ diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 0000000..c00d635 --- /dev/null +++ b/service-worker.js @@ -0,0 +1,73 @@ +self.addEventListener('install', function(event) { + event.waitUntil( + caches.open(cacheName).then(function(cache) { + return cache.addAll( + [ + '/css/mobile.css', + '/css/leaflet.css', + '/css/leaflet.fullscreen.css', + '/js/leaflet.js', + '/js/Leaflet.fullscreen.min.js', + '/js/L.Terminator.js', + '/js/mobile.js', + '/js/rbush.js', + '/js/leaflet.canvas-markers.js', + '/js/pwa.js', + '/js/init_plot.js', + '/img/markers/antenna-green.png', + '/img/markers/balloon-blue.png', + '/img/markers/balloon-cyan.png', + '/img/markers/balloon-green.png', + '/img/markers/balloon-orange.png', + '/img/markers/balloon-purple.png', + '/img/markers/balloon-red.png', + '/img/markers/balloon-yellow.png', + '/img/markers/car-blue.png', + '/img/markers/car-green.png', + '/img/markers/car-red.png', + '/img/markers/car-yellow.png', + '/img/markers/parachute-yellow.png', + '/img/markers/parachute-blue.png', + '/img/markers/parachute-cyan.png', + '/img/markers/parachute-green.png', + '/img/markers/parachute-orange.png', + '/img/markers/parachute-purple.png', + '/img/markers/parachute-red.png', + '/img/markers/payload-blue.png', + '/img/markers/payload-cyan.png', + '/img/markers/payload-green.png', + '/img/markers/payload-not-recovered.png', + '/img/markers/payload-orange.png', + '/img/markers/payload-purple.png', + '/img/markers/payload-recovered.png', + '/img/markers/payload-red.png', + '/img/markers/payload-yellow.png', + '/img/markers/target-blue.png', + '/img/markers/target-cyan.png', + '/img/markers/target-green.png', + '/img/markers/target-orange.png', + '/img/markers/target-purple.png', + '/img/markers/target-red.png', + '/img/markers/target-yellow.png', + '/img/markers/shadow.png', + '/img/markers/balloon-pop.png', + '/img/hab-spinner.gif', + '/img/marker-you.gif', + '/img/sondehub_logo.gif', + '/favicon.ico', + '/font/HabitatFont.woff', + '/font/Roboto-regular.woff', + '/index.html' + ] + ); + }) + ); +}); + +self.addEventListener('fetch', function (event) { + event.respondWith( + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }), + ); +}); \ No newline at end of file