├── favicon.ico ├── img ├── logo.png ├── blank.png ├── fluidicon.png ├── openhand.cur ├── closedhand.cur ├── hab-spinner.gif ├── marker-you.png ├── markers │ ├── iss.png │ ├── nyan.gif │ ├── shadow.png │ ├── car-blue.png │ ├── car-green.png │ ├── car-red.png │ ├── hab_nyan.gif │ ├── nyan-afro.gif │ ├── nyan-coin.gif │ ├── nyan-cool.gif │ ├── nyan-mon.gif │ ├── antenna-red.png │ ├── balloon-iss.png │ ├── balloon-pop.png │ ├── balloon-red.png │ ├── balloon-rob.png │ ├── balloon-rpi.png │ ├── car-yellow.png │ ├── nyan-mummy.gif │ ├── nyan-pirate.gif │ ├── payload-red.png │ ├── payload-rpi.png │ ├── target-blue.png │ ├── target-cyan.png │ ├── target-red.png │ ├── antenna-green.png │ ├── antenna-grey.png │ ├── balloon-blue.png │ ├── balloon-buzz.png │ ├── balloon-cyan.png │ ├── balloon-green.png │ ├── balloon-orange.png │ ├── balloon-purple.png │ ├── balloon-thereg.png │ ├── balloon-xmark.png │ ├── balloon-yellow.png │ ├── nyan-gameboy.gif │ ├── nyan-pumpkin.gif │ ├── nyan-tothemax.gif │ ├── parachute-blue.png │ ├── parachute-cyan.png │ ├── parachute-red.png │ ├── parachute-rpi.png │ ├── payload-blue.png │ ├── payload-cyan.png │ ├── payload-green.png │ ├── payload-orange.png │ ├── payload-purple.png │ ├── payload-yellow.png │ ├── target-green.png │ ├── target-orange.png │ ├── target-purple.png │ ├── target-yellow.png │ ├── balloon-adafruit.png │ ├── parachute-green.png │ ├── parachute-orange.png │ ├── parachute-purple.png │ ├── parachute-yellow.png │ ├── balloon-invisible.png │ └── balloon-shockpink.png └── apple-touch-icon.png ├── resources ├── car.psd ├── logo.psd ├── antenna.psd ├── balloon.psd ├── fluid-icon.psd ├── parachute.psd ├── concept-app-tablet.png ├── concept-app-portrait.png └── mobiletracker-screencap.png ├── tools ├── pngout.exe └── yuicompressor-2.4.8.jar ├── font ├── HabitatFont.eot ├── HabitatFont.ttf ├── HabitatFont.woff └── Roboto-regular.woff ├── .gitignore ├── .htaccess ├── track.kml ├── opensearchspec.xml ├── LICENSE ├── glyphs ├── icon-clock_simple.svg ├── icon-compass_simple.svg ├── icon-code.svg ├── icon-clock.svg ├── icon-weather.svg └── icon-compass.svg ├── cache.manifest-dev ├── css ├── habitat-font.css ├── layout.css ├── base.css ├── skeleton.css └── main.css ├── README.md ├── js ├── chasecar.lib.js ├── nite-overlay.js ├── plot_config.js ├── gmaps_extentions.js └── app.js ├── embed-preview.html └── index.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/blank.png -------------------------------------------------------------------------------- /img/fluidicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/fluidicon.png -------------------------------------------------------------------------------- /img/openhand.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/openhand.cur -------------------------------------------------------------------------------- /resources/car.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/car.psd -------------------------------------------------------------------------------- /tools/pngout.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/tools/pngout.exe -------------------------------------------------------------------------------- /font/HabitatFont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/font/HabitatFont.eot -------------------------------------------------------------------------------- /font/HabitatFont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/font/HabitatFont.ttf -------------------------------------------------------------------------------- /img/closedhand.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/closedhand.cur -------------------------------------------------------------------------------- /img/hab-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/hab-spinner.gif -------------------------------------------------------------------------------- /img/marker-you.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/marker-you.png -------------------------------------------------------------------------------- /img/markers/iss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/iss.png -------------------------------------------------------------------------------- /img/markers/nyan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan.gif -------------------------------------------------------------------------------- /resources/logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/logo.psd -------------------------------------------------------------------------------- /font/HabitatFont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/font/HabitatFont.woff -------------------------------------------------------------------------------- /img/markers/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/shadow.png -------------------------------------------------------------------------------- /resources/antenna.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/antenna.psd -------------------------------------------------------------------------------- /resources/balloon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/balloon.psd -------------------------------------------------------------------------------- /font/Roboto-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/font/Roboto-regular.woff -------------------------------------------------------------------------------- /img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/apple-touch-icon.png -------------------------------------------------------------------------------- /img/markers/car-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/car-blue.png -------------------------------------------------------------------------------- /img/markers/car-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/car-green.png -------------------------------------------------------------------------------- /img/markers/car-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/car-red.png -------------------------------------------------------------------------------- /img/markers/hab_nyan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/hab_nyan.gif -------------------------------------------------------------------------------- /img/markers/nyan-afro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-afro.gif -------------------------------------------------------------------------------- /img/markers/nyan-coin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-coin.gif -------------------------------------------------------------------------------- /img/markers/nyan-cool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-cool.gif -------------------------------------------------------------------------------- /img/markers/nyan-mon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-mon.gif -------------------------------------------------------------------------------- /resources/fluid-icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/fluid-icon.psd -------------------------------------------------------------------------------- /resources/parachute.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/parachute.psd -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.log 4 | js/mobile.js 5 | js/init_plot.js 6 | css/mobile.css 7 | cache.manifest 8 | tiles/ 9 | -------------------------------------------------------------------------------- /img/markers/antenna-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/antenna-red.png -------------------------------------------------------------------------------- /img/markers/balloon-iss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-iss.png -------------------------------------------------------------------------------- /img/markers/balloon-pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-pop.png -------------------------------------------------------------------------------- /img/markers/balloon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-red.png -------------------------------------------------------------------------------- /img/markers/balloon-rob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-rob.png -------------------------------------------------------------------------------- /img/markers/balloon-rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-rpi.png -------------------------------------------------------------------------------- /img/markers/car-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/car-yellow.png -------------------------------------------------------------------------------- /img/markers/nyan-mummy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-mummy.gif -------------------------------------------------------------------------------- /img/markers/nyan-pirate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-pirate.gif -------------------------------------------------------------------------------- /img/markers/payload-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-red.png -------------------------------------------------------------------------------- /img/markers/payload-rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-rpi.png -------------------------------------------------------------------------------- /img/markers/target-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-blue.png -------------------------------------------------------------------------------- /img/markers/target-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-cyan.png -------------------------------------------------------------------------------- /img/markers/target-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-red.png -------------------------------------------------------------------------------- /img/markers/antenna-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/antenna-green.png -------------------------------------------------------------------------------- /img/markers/antenna-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/antenna-grey.png -------------------------------------------------------------------------------- /img/markers/balloon-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-blue.png -------------------------------------------------------------------------------- /img/markers/balloon-buzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-buzz.png -------------------------------------------------------------------------------- /img/markers/balloon-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-cyan.png -------------------------------------------------------------------------------- /img/markers/balloon-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-green.png -------------------------------------------------------------------------------- /img/markers/balloon-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-orange.png -------------------------------------------------------------------------------- /img/markers/balloon-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-purple.png -------------------------------------------------------------------------------- /img/markers/balloon-thereg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-thereg.png -------------------------------------------------------------------------------- /img/markers/balloon-xmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-xmark.png -------------------------------------------------------------------------------- /img/markers/balloon-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-yellow.png -------------------------------------------------------------------------------- /img/markers/nyan-gameboy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-gameboy.gif -------------------------------------------------------------------------------- /img/markers/nyan-pumpkin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-pumpkin.gif -------------------------------------------------------------------------------- /img/markers/nyan-tothemax.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/nyan-tothemax.gif -------------------------------------------------------------------------------- /img/markers/parachute-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-blue.png -------------------------------------------------------------------------------- /img/markers/parachute-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-cyan.png -------------------------------------------------------------------------------- /img/markers/parachute-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-red.png -------------------------------------------------------------------------------- /img/markers/parachute-rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-rpi.png -------------------------------------------------------------------------------- /img/markers/payload-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-blue.png -------------------------------------------------------------------------------- /img/markers/payload-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-cyan.png -------------------------------------------------------------------------------- /img/markers/payload-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-green.png -------------------------------------------------------------------------------- /img/markers/payload-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-orange.png -------------------------------------------------------------------------------- /img/markers/payload-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-purple.png -------------------------------------------------------------------------------- /img/markers/payload-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/payload-yellow.png -------------------------------------------------------------------------------- /img/markers/target-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-green.png -------------------------------------------------------------------------------- /img/markers/target-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-orange.png -------------------------------------------------------------------------------- /img/markers/target-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-purple.png -------------------------------------------------------------------------------- /img/markers/target-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/target-yellow.png -------------------------------------------------------------------------------- /tools/yuicompressor-2.4.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/tools/yuicompressor-2.4.8.jar -------------------------------------------------------------------------------- /img/markers/balloon-adafruit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-adafruit.png -------------------------------------------------------------------------------- /img/markers/parachute-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-green.png -------------------------------------------------------------------------------- /img/markers/parachute-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-orange.png -------------------------------------------------------------------------------- /img/markers/parachute-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-purple.png -------------------------------------------------------------------------------- /img/markers/parachute-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/parachute-yellow.png -------------------------------------------------------------------------------- /resources/concept-app-tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/concept-app-tablet.png -------------------------------------------------------------------------------- /img/markers/balloon-invisible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-invisible.png -------------------------------------------------------------------------------- /img/markers/balloon-shockpink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/img/markers/balloon-shockpink.png -------------------------------------------------------------------------------- /resources/concept-app-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/concept-app-portrait.png -------------------------------------------------------------------------------- /resources/mobiletracker-screencap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossengeorgiev/habitat-mobile-tracker/HEAD/resources/mobiletracker-screencap.png -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | SetOutputFilter DEFLATE 3 | 4 | 5 | AddType text/cache-manifest .manifest 6 | AddType text/cache-manifest .appcache 7 | 8 | AddType application/x-font-woff .woff 9 | AddType application/x-font-ttf .ttf 10 | AddType application/vnd.ms-fontobject .eot 11 | AddType image/svg+xml .svg 12 | -------------------------------------------------------------------------------- /track.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | habhub tracker (GE) 5 | Live tracking of high altitude balloons via Google Earth 6 | 7 | http://spacenear.us/tracker/datanew.php?format=kml&mode=2days&vehicles=!RS_*; 8 | onInterval 9 | 20 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /opensearchspec.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Habhub Tracker 4 | Habhub Tracker - Search 5 | UTF-8 6 | hello@rgp.io 7 | https://tracker.habhub.org/favicon.ico 8 | Rossen Georgiev 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 Rossen Georgiev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /glyphs/icon-clock_simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Clock Icon 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | image/svg+xml 16 | 17 | 18 | 19 | Clock Icon 20 | 21 | 22 | 23 | 2014-06-11 24 | 25 | 26 | 27 | 28 | 29 | Rossen Georgiev 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Rossen Georgiev 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /glyphs/icon-compass_simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clock Compass 4 | 5 | 6 | 7 | image/svg+xml 8 | 9 | Clock Compass 10 | 11 | 2014-07-09 12 | 13 | 14 | Rossen Georgiev 15 | 16 | 17 | 18 | 19 | Rossen Georgiev 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cache.manifest-dev: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # version {VERSION} 3 | 4 | # gogole maps files 5 | http://maps.google.com/maps/api/js?v=3.22&sensor=false&libraries=map,common,controls,util,marker,onion,kml,ga,infowindow,stats,poly,overlay,weather,weather_impl,geometry&language=en_us&key=AIzaSyCOqkcNey4CCyG4X0X5qxHAhCgD8g5DwXg 6 | http://fonts.googleapis.com/css?family=Roboto:300,400,500,700 7 | http://maps.gstatic.com/mapfiles/undo_poly.png 8 | http://maps.gstatic.com/mapfiles/mv/imgs8.png 9 | http://maps.gstatic.com/mapfiles/transparent.png 10 | http://maps.gstatic.com/mapfiles/api-3/images/mapcnt3.png 11 | http://maps.gstatic.com/mapfiles/api-3/images/google_white2_hdpi.png 12 | http://maps.gstatic.com/mapfiles/api-3/images/google_white2.png 13 | http://maps.gstatic.com/mapfiles/openhand_8_8.cur 14 | 15 | # app files 16 | img/closedhand.cur 17 | img/openhand.cur 18 | img/logo.png 19 | img/blank.png 20 | img/marker-you.png 21 | img/apple-touch-icon.png 22 | img/markers/hab_nyan.gif 23 | img/markers/nyan.gif 24 | img/markers/antenna-green.png 25 | img/markers/balloon-red.png 26 | img/markers/balloon-blue.png 27 | img/markers/balloon-green.png 28 | img/markers/balloon-purple.png 29 | img/markers/balloon-cyan.png 30 | img/markers/balloon-orange.png 31 | img/markers/balloon-yellow.png 32 | img/markers/balloon-rpi.png 33 | img/markers/car-blue.png 34 | img/markers/car-green.png 35 | img/markers/car-red.png 36 | img/markers/car-yellow.png 37 | img/markers/parachute-blue.png 38 | img/markers/parachute-green.png 39 | img/markers/parachute-red.png 40 | img/markers/parachute-yellow.png 41 | img/markers/parachute-cyan.png 42 | img/markers/parachute-orange.png 43 | img/markers/parachute-purple.png 44 | img/markers/parachute-rpi.png 45 | img/markers/payload-blue.png 46 | img/markers/payload-cyan.png 47 | img/markers/payload-green.png 48 | img/markers/payload-orange.png 49 | img/markers/payload-purple.png 50 | img/markers/payload-red.png 51 | img/markers/payload-yellow.png 52 | img/markers/payload-rpi.png 53 | img/markers/shadow.png 54 | img/markers/target-blue.png 55 | img/markers/target-cyan.png 56 | img/markers/target-green.png 57 | img/markers/target-orange.png 58 | img/markers/target-purple.png 59 | img/markers/target-red.png 60 | img/markers/target-yellow.png 61 | img/hab-spinner.gif 62 | css/mobile.css 63 | js/mobile.js 64 | js/init_plot.js 65 | font/HabitatFont.eot 66 | font/HabitatFont.svg 67 | font/HabitatFont.ttf 68 | font/HabitatFont.woff 69 | font/Roboto-regular.woff 70 | 71 | NETWORK: 72 | * 73 | 74 | FALLBACK: 75 | / index.html 76 | -------------------------------------------------------------------------------- /css/habitat-font.css: -------------------------------------------------------------------------------- 1 | /* Habitat Font 2 | * icons specifically created for habitat websites and apps 3 | * 4 | * Inspired from Font Awesome @ https://github.com/FortAwesome 5 | * 6 | * Author: Rossen Georgiev @ http://rossengeorgiev.github.com/ 7 | */ 8 | 9 | @font-face { 10 | font-family: 'HabitatFont'; 11 | src: url('../font/HabitatFont.eot'); 12 | src: url('../font/HabitatFont.eot?#iefix') format('embedded-opentype'), 13 | url('../font/HabitatFont.woff') format('woff'), 14 | url('../font/HabitatFont.ttf') format('truetype'), 15 | url('../font/HabitatFont.svg#habitatfontregular') format('svg'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | [class^="icon-"], 21 | [class*=" icon-"] { 22 | font-family: HabitatFont; 23 | font-weight: normal; 24 | font-style: normal; 25 | text-decoration: inherit; 26 | display: inline; 27 | width: auto; 28 | height: auto; 29 | line-height: normal; 30 | vertical-align: baseline; 31 | background-image: none !important; 32 | background-position: 0% 0%; 33 | background-repeat: repeat; 34 | } 35 | [class^="icon-"]:before, 36 | [class*=" icon-"]:before { 37 | text-decoration: inherit; 38 | display: inline-block; 39 | speak: none; 40 | } 41 | .icon-spin { 42 | display: inline-block; 43 | -moz-animation: spin 2s infinite linear; 44 | -o-animation: spin 2s infinite linear; 45 | -webkit-animation: spin 2s infinite linear; 46 | animation: spin 2s infinite linear; 47 | } 48 | @-moz-keyframes spin { 49 | 0% { -moz-transform: rotate(0deg); } 50 | 100% { -moz-transform: rotate(359deg); } 51 | } 52 | @-webkit-keyframes spin { 53 | 0% { -webkit-transform: rotate(0deg); } 54 | 100% { -webkit-transform: rotate(359deg); } 55 | } 56 | @-o-keyframes spin { 57 | 0% { -o-transform: rotate(0deg); } 58 | 100% { -o-transform: rotate(359deg); } 59 | } 60 | @-ms-keyframes spin { 61 | 0% { -ms-transform: rotate(0deg); } 62 | 100% { -ms-transform: rotate(359deg); } 63 | } 64 | @keyframes spin { 65 | 0% { transform: rotate(0deg); } 66 | 100% { transform: rotate(359deg); } 67 | } 68 | 69 | .icon-habhub:before { content: "\f000"; } 70 | .icon-compass:before { content: "\f001"; } 71 | .icon-locate-me:before { content: "\f002"; } 72 | .icon-car:before { content: "\f003"; } 73 | .icon-question:before { content: "\f004"; } 74 | .icon-location:before { content: "\f005"; } 75 | .icon-target:before { content: "\f006"; } 76 | .icon-earth:before { content: "\f007"; } 77 | .icon-daylight:before { content: "\f008"; } 78 | .icon-settings:before { content: "\f010"; } 79 | 80 | -------------------------------------------------------------------------------- /glyphs/icon-code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | Code Icon 21 | 23 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | Code Icon 52 | 54 | 2014-06-06 55 | 56 | 57 | Rossen Georgiev 58 | 59 | 60 | 61 | 62 | Rossen Georgiev 63 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Habhub tracker 2 | 3 | ![mobile tracker screenshot](resources/mobiletracker-screencap.png "mobile tracker screenshot") 4 | 5 | A webapp for tracking high altitude balloons. Works an desktop and mobile devices. 6 | The habhub tracker is a continuation of [spacenear.us/tracker](http://spacenear.us/tracker). 7 | 8 | Checkout the [Live version](http://habhub.org/mt/) 9 | 10 | ## Features 11 | 12 | * HAB tracking with [Habitat](http://habitat.habhub.org/) ([@github/ukhas/habitat](https://github.com/ukhas/habitat)) 13 | * Telemetry graph for each balloon 14 | * Chase Car functionality 15 | * Near realtime weather overlays 16 | * [Daylight cycle overlay](https://github.com/rossengeorgiev/nite-overlay), for long flights 17 | * Available to embed on any website 18 | * Map tracker with Google Maps API 3 19 | * Run the app natively on `iOS` or with Chrome's 'add to screen' on `Android` 20 | 21 | ### Geo position 22 | 23 | Available only on mobile devices. 24 | 25 | The app will ask for permission to use your location. 26 | This is required for some of the features. It is **important** to note that 27 | your location will not be made available or send to anyone. Unless, you enable 28 | the `chase car mode`, which will periodically upload it to habitat. _The app 29 | will always start with `chase car mode` disabled._ 30 | 31 | ### Offline storage 32 | 33 | The app will ask to use offline storage. You will need to accept, in order to 34 | use the offline capabilities. The app will cache all files making it available 35 | even when there is no network coverage. Latest position data will also be stored 36 | and used when you start up with no network. When you get back online, the app 37 | will fetch the latest position data. 38 | 39 | ## Browser requirements 40 | 41 | Any modern browser should be able to run the app. Including the latest version of IE. 42 | This is also true for mobile, some build-in browsers may not work. 43 | 44 | ## Contribute 45 | 46 | Don't hesitate to report any issues, or suggest improvements. Just visit the [issues page](https://github.com/rossengeorgiev/habitat-mobile-tracker/issues). 47 | Pull requests are welcome. 48 | 49 | 50 | ## Installation 51 | 52 | Requirements: __Java__ and (Linux or Cygwin environment) 53 | 54 | $ git clone git://github.com/rossengeorgiev/habitat-mobile-tracker.git 55 | $ ./build.sh 56 | 57 | For __applicationCache__ to work your server needs to send the correct MIME type. 58 | `.htaccess` for Apache is included. Consult it if you are using different server software. 59 | 60 | ## Original design 61 | 62 | Author: Daniel Saul [@danielsaul](https://github.com/danielsaul) 63 | 64 | [See concept for phone portrait mode](https://github.com/rossengeorgiev/habitat-mobile-tracker/blob/master/resources/concept-app-portrait.png) 65 | [See concept for tablets](https://github.com/rossengeorgiev/habitat-mobile-tracker/blob/master/resources/concept-app-tablet.png) 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /js/chasecar.lib.js: -------------------------------------------------------------------------------- 1 | /* Habitat ChaseCar lib 2 | * Uploads geolocation for chase cars to habitat 3 | * 4 | * Author: Rossen Gerogiev 5 | * Requires: jQuery 6 | */ 7 | 8 | ChaseCar = { 9 | db_uri: "https://tracker.habhub.org/", // db address 10 | uuidsRequested: false, // used in .request() to track whenever it has requested uuids 11 | _uuids: [], // array with uuids 12 | ucount: 20, // number of uuids in the _uuids array (determines how many uuids are request at a time) 13 | uused: 0, // how many have been used for requests 14 | queue: [] // request queue, incase we dont have uuids 15 | }; 16 | ChaseCar.uused = ChaseCar.ucount; // we start without any uuids 17 | 18 | // get some uuids for us to use 19 | ChaseCar.getUUIDS = function(callback) { 20 | $.getJSON(ChaseCar.db_uri + "_uuids?count=" + ChaseCar.ucount, function (data) { 21 | ChaseCar._uuids = data.uuids; // load the new uuids 22 | ChaseCar.uused = 0; // reset counter 23 | if(callback) callback(); 24 | }); 25 | }; 26 | // handles request and uuid management 27 | // @doc JSONobject 28 | ChaseCar.request = function(doc) { 29 | if(doc) { ChaseCar.queue.push(doc); } 30 | 31 | var i = ChaseCar.queue.length; 32 | while(i--) { 33 | if(ChaseCar.ucount == ChaseCar.uused && !ChaseCar.uuidsRequested) { 34 | ChaseCar.uuidsRequested = true; // blocks further uuids request until the current one completes 35 | ChaseCar.getUUIDS(function() { 36 | ChaseCar.uuidsRequested = false; 37 | ChaseCar.request(); 38 | }); 39 | return; 40 | } else { 41 | ChaseCar.uused++; 42 | // get one uuid and one doc from the queue and push to habitat 43 | var uuid = ChaseCar._uuids.shift(); 44 | doc = ChaseCar.queue.shift(); 45 | 46 | // update doc with uuids and time of upload 47 | doc._id = uuid; 48 | doc.time_uploaded = (new Date()).toISOString(); 49 | 50 | // push the doc to habitat 51 | $.ajax({ 52 | type: "POST", 53 | url: ChaseCar.db_uri + "habitat/", 54 | contentType: "application/json; charset=utf-8", 55 | dataType: "json", 56 | data: JSON.stringify(doc), 57 | }); 58 | } 59 | } 60 | }; 61 | // run once at start, 62 | // @callsign string 63 | ChaseCar.putListenerInfo = function(callsign) { 64 | if(!callsign) return; 65 | 66 | ChaseCar.request({ 67 | 'type': "listener_information", 68 | 'time_created': (new Date()).toISOString(), 69 | 'data': { 'callsign': callsign } 70 | }); 71 | }; 72 | // run every time the location has changed 73 | // @callsign string 74 | // @position object (geolocation position object) 75 | ChaseCar.updatePosition = function(callsign, position) { 76 | if(!position || !position.coords) return; 77 | 78 | ChaseCar.request({ 79 | 'type': "listener_telemetry", 80 | 'time_created': (new Date()).toISOString(), 81 | 'data': { 82 | 'callsign': callsign, 83 | 'chase': true, 84 | 'latitude': position.coords.latitude, 85 | 'longitude': position.coords.longitude, 86 | 'altitude': ((!!position.coords.altitude) ? position.coords.altitude : 0), 87 | 'speed': ((!!position.coords.speed) ? position.coords.speed : 0), 88 | 'client': { 89 | 'name': 'Habitat Mobile Tracker', 90 | 'version': '{VER}', 91 | 'agent': navigator.userAgent 92 | } 93 | } 94 | }); 95 | }; 96 | -------------------------------------------------------------------------------- /css/layout.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.2 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 6/20/2012 8 | 9 | * Edited by Daniel Saul 10 | * For use in the Habitat Webpage Template 11 | 12 | */ 13 | 14 | /* Table of Content 15 | ================================================== 16 | #Site Styles 17 | #Page Styles 18 | #Media Queries 19 | #Font-Face */ 20 | 21 | /* #Site Styles 22 | ================================================== */ 23 | 24 | /* Header */ 25 | header{ 26 | width: 100%; 27 | min-height: 75px; 28 | 29 | text-align: left; 30 | line-height: 75px; 31 | color: #ffffff; 32 | font-size: 14px; 33 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); 34 | 35 | background: #00a3d3; 36 | border-bottom: 1px solid #009bc9; 37 | 38 | } 39 | 40 | header h1{ 41 | height: 75px; 42 | float: left; 43 | } 44 | 45 | /* Grey Area */ 46 | #grey-section{ 47 | width: 100%; 48 | margin-bottom: 50px; 49 | padding: 15px 0 15px 0; 50 | 51 | color: #666666; 52 | 53 | background: #fcfcfc; 54 | border-top: 1px solid #ffffff; 55 | border-bottom: 1px solid #eeeeee; 56 | } 57 | 58 | #grey-section h3{ 59 | color: #00a3d3; 60 | } 61 | 62 | #grey-section .badge{ 63 | margin-top: -30px; 64 | margin-bottom: -100%; 65 | float: right; 66 | visibility: hidden; 67 | } 68 | 69 | /* Forms */ 70 | .form.row label{ 71 | padding-top: 3px; 72 | font-weight: normal; 73 | } 74 | 75 | .form.row{ 76 | margin-bottom: 44px; 77 | } 78 | 79 | .form.row input, .form.row select { 80 | margin-bottom: 0 !important; 81 | } 82 | 83 | .form.row select { 84 | /* base.css makes this different to input for some reason. */ 85 | padding: 5px 4px; 86 | width: 220px; 87 | } 88 | 89 | .validated input{ 90 | float: left; 91 | } 92 | 93 | .validated img{ 94 | padding: 3px 0 0 10px; 95 | float: left; 96 | } 97 | 98 | .form.row .input_extra{ 99 | background: #f8f8f8; 100 | color: #999999; 101 | padding: 5px; 102 | -moz-border-radius: 4px; 103 | -webkit-border-radius: 4px; 104 | border-radius: 4px; 105 | position: relative; 106 | min-height: 22px; 107 | width: auto; 108 | } 109 | 110 | @media only screen and (min-width: 768px){ 111 | .input_extra:before { 112 | content: "\0020"; 113 | width: 0; 114 | height: 0; 115 | border-top: 15px solid transparent; 116 | border-bottom: 15px solid transparent; 117 | border-right: 15px solid #f8f8f8; 118 | position: absolute; 119 | left: -14px; 120 | top: 1px; 121 | display: inline; 122 | } 123 | .form.row input.long { 124 | width: 380px; 125 | } 126 | } 127 | 128 | .long_protection { 129 | white-space: nowrap; 130 | overflow: hidden; 131 | display: block; 132 | } 133 | 134 | /* #Page Styles 135 | ================================================== */ 136 | 137 | /* #Media Queries 138 | ================================================== */ 139 | 140 | /* Smaller than standard 960 (devices and browsers) */ 141 | @media only screen and (max-width: 959px) {} 142 | 143 | /* Tablet Portrait size to standard 960 (devices and browsers) */ 144 | @media only screen and (min-width: 768px) and (max-width: 959px) {} 145 | 146 | /* All Mobile Sizes (devices and browser) */ 147 | @media only screen and (max-width: 767px) {} 148 | 149 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ 150 | @media only screen and (min-width: 480px) and (max-width: 767px) {} 151 | 152 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ 153 | @media only screen and (max-width: 479px) {} 154 | 155 | 156 | /* #Font-Face 157 | ================================================== */ 158 | /* This is the proper syntax for an @font-face file 159 | Just create a "fonts" folder at the root, 160 | copy your FontName into code below and remove 161 | comment brackets */ 162 | 163 | /* @font-face { 164 | font-family: 'FontName'; 165 | src: url('../fonts/FontName.eot'); 166 | src: url('../fonts/FontName.eot?iefix') format('eot'), 167 | url('../fonts/FontName.woff') format('woff'), 168 | url('../fonts/FontName.ttf') format('truetype'), 169 | url('../fonts/FontName.svg#webfontZam02nTh') format('svg'); 170 | font-weight: normal; 171 | font-style: normal; } 172 | */ 173 | -------------------------------------------------------------------------------- /glyphs/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | Clock Icon 20 | 22 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | Clock Icon 49 | 51 | 2014-06-11 52 | 53 | 54 | Rossen Georgiev 55 | 56 | 57 | 58 | 59 | Rossen Georgiev 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 73 | 78 | 88 | 97 | 106 | 115 | 124 | 133 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /glyphs/icon-weather.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | Weather Icon 21 | 23 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | Weather Icon 52 | 54 | 2013 55 | 56 | 57 | KickstandApps (kickstandapps.com). 58 | 59 | 60 | 61 | 62 | KickstandApps (kickstandapps.com). 63 | 64 | 65 | https://github.com/kickstandapps/WeatherIcons 66 | 67 | 68 | 69 | 74 | 75 | -------------------------------------------------------------------------------- /js/nite-overlay.js: -------------------------------------------------------------------------------- 1 | /* Nite v1.7 2 | * A tiny library to create a night overlay over the map 3 | * Author: Rossen Georgiev @ https://github.com/rossengeorgiev 4 | * Requires: GMaps API 3 5 | */ 6 | 7 | 8 | var nite = { 9 | map: null, 10 | date: null, 11 | sun_position: null, 12 | earth_radius_meters: 6371008, 13 | marker_twilight_civil: null, 14 | marker_twilight_nautical: null, 15 | marker_twilight_astronomical: null, 16 | marker_night: null, 17 | 18 | init: function(map) { 19 | if(typeof google === 'undefined' 20 | || typeof google.maps === 'undefined') throw "Nite Overlay: no google.maps detected"; 21 | 22 | this.map = map; 23 | this.sun_position = this.calculatePositionOfSun(); 24 | 25 | this.marker_twilight_civil = new google.maps.Circle({ 26 | map: this.map, 27 | center: this.getShadowPosition(), 28 | radius: this.getShadowRadiusFromAngle(0.566666), 29 | fillColor: "#000", 30 | fillOpacity: 0.1, 31 | strokeOpacity: 0, 32 | clickable: false, 33 | editable: false 34 | }); 35 | this.marker_twilight_nautical = new google.maps.Circle({ 36 | map: this.map, 37 | center: this.getShadowPosition(), 38 | radius: this.getShadowRadiusFromAngle(6), 39 | fillColor: "#000", 40 | fillOpacity: 0.1, 41 | strokeOpacity: 0, 42 | clickable: false, 43 | editable: false 44 | }); 45 | this.marker_twilight_astronomical = new google.maps.Circle({ 46 | map: this.map, 47 | center: this.getShadowPosition(), 48 | radius: this.getShadowRadiusFromAngle(12), 49 | fillColor: "#000", 50 | fillOpacity: 0.1, 51 | strokeOpacity: 0, 52 | clickable: false, 53 | editable: false 54 | }); 55 | this.marker_night = new google.maps.Circle({ 56 | map: this.map, 57 | center: this.getShadowPosition(), 58 | radius: this.getShadowRadiusFromAngle(18), 59 | fillColor: "#000", 60 | fillOpacity: 0.1, 61 | strokeOpacity: 0, 62 | clickable: false, 63 | editable: false 64 | }); 65 | }, 66 | getShadowRadiusFromAngle: function(angle) { 67 | var shadow_radius = this.earth_radius_meters * Math.PI * 0.5; 68 | var twilight_dist = ((this.earth_radius_meters * 2 * Math.PI) / 360) * angle; 69 | return shadow_radius - twilight_dist; 70 | }, 71 | getSunPosition: function() { 72 | return this.sun_position; 73 | }, 74 | getShadowPosition: function() { 75 | return (this.sun_position) ? new google.maps.LatLng(-this.sun_position.lat(), this.sun_position.lng() + 180) : null; 76 | }, 77 | refresh: function() { 78 | if(!this.isVisible()) return; 79 | this.sun_position = this.calculatePositionOfSun(this.date); 80 | var shadow_position = this.getShadowPosition(); 81 | this.marker_twilight_civil.setCenter(shadow_position); 82 | this.marker_twilight_nautical.setCenter(shadow_position); 83 | this.marker_twilight_astronomical.setCenter(shadow_position); 84 | this.marker_night.setCenter(shadow_position); 85 | }, 86 | jday: function(date) { 87 | return (date.getTime() / 86400000.0) + 2440587.5; 88 | }, 89 | calculatePositionOfSun: function(date) { 90 | date = (date instanceof Date) ? date : new Date(); 91 | 92 | var rad = 0.017453292519943295; 93 | 94 | // based on NOAA solar calculations 95 | var ms_past_midnight = ((date.getUTCHours() * 60 + date.getUTCMinutes()) * 60 + date.getUTCSeconds()) * 1000 + date.getUTCMilliseconds(); 96 | var jc = (this.jday(date) - 2451545)/36525; 97 | var mean_long_sun = (280.46646+jc*(36000.76983+jc*0.0003032)) % 360; 98 | var mean_anom_sun = 357.52911+jc*(35999.05029-0.0001537*jc); 99 | var sun_eq = Math.sin(rad*mean_anom_sun)*(1.914602-jc*(0.004817+0.000014*jc))+Math.sin(rad*2*mean_anom_sun)*(0.019993-0.000101*jc)+Math.sin(rad*3*mean_anom_sun)*0.000289; 100 | var sun_true_long = mean_long_sun + sun_eq; 101 | var sun_app_long = sun_true_long - 0.00569 - 0.00478*Math.sin(rad*125.04-1934.136*jc); 102 | var mean_obliq_ecliptic = 23+(26+((21.448-jc*(46.815+jc*(0.00059-jc*0.001813))))/60)/60; 103 | var obliq_corr = mean_obliq_ecliptic + 0.00256*Math.cos(rad*125.04-1934.136*jc); 104 | 105 | var lat = Math.asin(Math.sin(rad*obliq_corr)*Math.sin(rad*sun_app_long)) / rad; 106 | 107 | var eccent = 0.016708634-jc*(0.000042037+0.0000001267*jc); 108 | var y = Math.tan(rad*(obliq_corr/2))*Math.tan(rad*(obliq_corr/2)); 109 | var rq_of_time = 4*((y*Math.sin(2*rad*mean_long_sun)-2*eccent*Math.sin(rad*mean_anom_sun)+4*eccent*y*Math.sin(rad*mean_anom_sun)*Math.cos(2*rad*mean_long_sun)-0.5*y*y*Math.sin(4*rad*mean_long_sun)-1.25*eccent*eccent*Math.sin(2*rad*mean_anom_sun))/rad); 110 | var true_solar_time_in_deg = ((ms_past_midnight+rq_of_time*60000) % 86400000) / 240000; 111 | 112 | var lng = -((true_solar_time_in_deg < 0) ? true_solar_time_in_deg + 180 : true_solar_time_in_deg - 180); 113 | 114 | return new google.maps.LatLng(lat, lng); 115 | }, 116 | setDate: function(date) { 117 | this.date = date; 118 | this.refresh(); 119 | }, 120 | setMap: function(map) { 121 | this.map = map; 122 | this.marker_twilight_civil.setMap(this.map); 123 | this.marker_twilight_nautical.setMap(this.map); 124 | this.marker_twilight_astronomical.setMap(this.map); 125 | this.marker_night.setMap(this.map); 126 | }, 127 | show: function() { 128 | this.marker_twilight_civil.setVisible(true); 129 | this.marker_twilight_nautical.setVisible(true); 130 | this.marker_twilight_astronomical.setVisible(true); 131 | this.marker_night.setVisible(true); 132 | this.refresh(); 133 | }, 134 | hide: function() { 135 | this.marker_twilight_civil.setVisible(false); 136 | this.marker_twilight_nautical.setVisible(false); 137 | this.marker_twilight_astronomical.setVisible(false); 138 | this.marker_night.setVisible(false); 139 | }, 140 | isVisible: function() { 141 | return this.marker_night.getVisible(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /js/plot_config.js: -------------------------------------------------------------------------------- 1 | // init plot 2 | plot = $.plot(plot_holder, {}, plot_options); 3 | var updateLegendTimeout = null; 4 | var polyMarker = null; 5 | 6 | // updates legend with extrapolated values under the mouse position 7 | function updateLegend(pos) { 8 | var legend = $(plot_holder + " .legendLabel"); 9 | $(plot_holder + " .legend table").css({'background-color':"rgba(255,255,255,0.9)","pointer-events":"none"}); 10 | legend.each(function() { 11 | $(this).css({'padding-left':'3px'}); 12 | }); 13 | 14 | 15 | var i, j, ij, pij, dataset = plot.getData(); 16 | var outside = false; 17 | //var axes = plot.getAxes(); 18 | 19 | if(dataset.length === 0) return; 20 | 21 | // this loop find the value for each series 22 | // and updates the legend 23 | // 24 | // here we don't snap to existing data point 25 | for (i = 0; i < dataset.length; ++i) { 26 | var series = dataset[i]; 27 | var y; 28 | y = null; 29 | 30 | // Find the nearest points, x-wise 31 | 32 | if(series.data.length < 2 || 33 | (i===1 && series.data[0][0] > pos.x)) { 34 | y = null; 35 | } 36 | else if (i !== 1 && pos.x > series.data[series.data.length-1][0]) { 37 | outside = true; 38 | } 39 | else { 40 | for (j = 0; j < series.data.length; ++j) { 41 | if (series.data[j][0] > pos.x) { 42 | break; 43 | } 44 | } 45 | 46 | if(i === 0) ij = j; 47 | if(i === 1) { 48 | pij = (j >= series.data.length) ? j-1 : j; 49 | } 50 | 51 | if(series.noInterpolate === true) { y = series.data[((j===0)?j:j-1)][1]; } 52 | else { 53 | var p1 = (j===0) ? null : series.data[j-1]; 54 | p2 = series.data[j]; 55 | 56 | if (p1 === null) { 57 | y = p2[1]; 58 | } else if (p2 === null || p2 === undefined) { 59 | y = p1[1]; 60 | } else { 61 | y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]); 62 | } 63 | 64 | y = ((p1 && p1[1] === null) || (p2 && p2[1] === null)) ? null : y.toFixed(2); 65 | } 66 | } 67 | legend.eq(i).text(series.label.replace(/=.*/, "= " + y)); 68 | } 69 | 70 | if(!polyMarker) { 71 | polyMarker = new google.maps.Marker({ 72 | clickable: true, 73 | flat: true, 74 | map: map, 75 | visible: true, 76 | icon: null 77 | }); 78 | google.maps.event.addListener(polyMarker, 'click', function() { mapInfoBox_handle_path({latLng: this.getPosition()}); }); 79 | } 80 | 81 | // this loop finds an existing data point, so we can get coordinates 82 | // if the crosshair happens to be over null area, we snap to the previous data point 83 | // 84 | // to snap accurate to the corresponding LatLng, we need to count the number of null data points 85 | // then we remove them form the count and we get the index we need for the positions array 86 | if(follow_vehicle !== null && vehicles[follow_vehicle].positions.length) { 87 | // adjust index for null data points 88 | var null_count = 0; 89 | 90 | if(outside && pij !== undefined) { 91 | polyMarker.setPosition(vehicles[follow_vehicle].prediction_polyline.getPath().getArray()[pij]); 92 | } 93 | else { 94 | var data_ref = vehicles[follow_vehicle].graph_data[0]; 95 | 96 | if(ij > data_ref.data.length / 2) { 97 | for(i = data_ref.data.length - 1; i > ij; i--) null_count += (data_ref.data[i][1] === null) ? 1 : 0; 98 | null_count = data_ref.nulls - null_count * 2; 99 | } else { 100 | for(i = 0; i < ij; i++) null_count += (data_ref.data[i][1] === null) ? 1 : 0; 101 | null_count *= 2; 102 | } 103 | 104 | // update position 105 | ij -= null_count + ((null_count===0||null_count===data_ref.nulls) ? 0 : 1); 106 | if(ij < 0) ij = 0; 107 | 108 | polyMarker.setPosition(vehicles[follow_vehicle].positions[ij]); 109 | } 110 | 111 | // adjust nite overlay 112 | var date = new Date(pos.x1); 113 | 114 | nite.setDate(date); 115 | nite.refresh(); 116 | // set timebox 117 | $('#timebox').removeClass('present').addClass('past'); 118 | updateTimebox(date); 119 | } 120 | } 121 | 122 | var plot_crosshair_locked = false; 123 | 124 | $(plot_holder).bind("click", function (event) { 125 | if(plot_crosshair_locked) { 126 | plot_crosshair_locked = false; 127 | } else if(event.ctrlKey) { 128 | plot_crosshair_locked = true; 129 | } 130 | }); 131 | // update legend values on mouse hover 132 | $(plot_holder).bind("plothover", function (event, pos, item) { 133 | if(plot_crosshair_locked) return; 134 | 135 | if (!updateLegendTimeout) { 136 | plot.lockCrosshair(); 137 | plot.setCrosshair(pos); 138 | updateLegend(pos); 139 | updateLegendTimeout = setTimeout(function() { updateLegendTimeout = null; }, 40); 140 | } 141 | }); 142 | 143 | // double click on the plot clears selection 144 | $(plot_holder).bind("dblclick", function () { 145 | if(!follow_vehicle) return; 146 | 147 | if(plot_options.xaxis) { 148 | if(plot_options.xaxis.superzoom == 2) { 149 | delete plot_options.xaxis; 150 | } 151 | else { 152 | if(plot_options.xaxis.superzoom == 1) { 153 | if(!confirm("You are about to zoom out to the entire graph. It may hang your browser. Do you wish to continue?")) return; 154 | } 155 | plot_options.xaxis = {}; 156 | } 157 | } 158 | 159 | updateGraph(follow_vehicle, false); 160 | }); 161 | 162 | // limit range after selection 163 | $(plot_holder).bind("plotselected", function (event, ranges) { 164 | if(typeof ranges.xaxis == 'undefined') return; 165 | 166 | if(plot_options.xaxis && plot_options.xaxis.superzoom) plot_options.xaxis.superzoom = 2; 167 | 168 | $.extend(true, plot_options, { 169 | xaxis: { 170 | min: ranges.xaxis.from, 171 | max: ranges.xaxis.to 172 | } 173 | }); 174 | 175 | updateGraph(follow_vehicle, false); 176 | }); 177 | -------------------------------------------------------------------------------- /glyphs/icon-compass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | Clock Compass 20 | 22 | 43 | 47 | 51 | 55 | 59 | 62 | 63 | 65 | 66 | 68 | image/svg+xml 69 | 71 | Clock Compass 72 | 74 | 2014-07-09 75 | 76 | 77 | Rossen Georgiev 78 | 79 | 80 | 81 | 82 | Rossen Georgiev 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 96 | 101 | 117 | 133 | N 145 | W 157 | E 169 | S 181 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /embed-preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Preview of embedded habhub tracker 4 | 5 | 6 | 62 | 63 | 211 | 212 | 213 |
214 |

Embed habhub tracker on your page

215 |

1. Options

216 |
217 | 218 | 219 |
220 | 221 | 222 |
223 | 224 | 225 |
226 | 227 | 228 |
229 |

2. Style

230 |
231 | 232 | 233 | 234 | 235 |
236 | 237 | 238 | 239 | 240 |
241 |

3. HTML code

242 | 243 | 244 |

4. Live preview

245 |
246 | 247 |
248 |
249 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /js/gmaps_extentions.js: -------------------------------------------------------------------------------- 1 | 2 | // custom label function 3 | 4 | google.maps.Label = function(opt_options) { 5 | // init default values 6 | this.set('visible', true); 7 | this.set('opacity', 1); 8 | this.set('clickable', false); 9 | this.set('strokeColor', "#00F"); 10 | this.set('text', ""); 11 | this.set('textOnly', false); // true only text, false text within a box 12 | 13 | this.setValues(opt_options); 14 | 15 | var span = this.span_ = document.createElement('span'); 16 | span.style.cssText = 'position: relative; left: -50%;' + 17 | 'white-space: nowrap; color: #000;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;'; 18 | 19 | span.style.cssText += !this.get('textOnly') ? 20 | 'border: 1px solid '+this.get('strokeColor')+'; border-radius: 5px; ' + 21 | 'top:-12px;font-size:9px;padding: 2px; background-color: white' 22 | : 23 | '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;' 24 | ; 25 | 26 | var div = this.div_ = document.createElement('div'); 27 | div.appendChild(span); 28 | div.style.cssText = 'position: absolute; display: none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;'; 29 | }; 30 | 31 | google.maps.Label.prototype = new google.maps.OverlayView(); 32 | 33 | 34 | // Implement onAdd 35 | google.maps.Label.prototype.onAdd = function() { 36 | var pane = this.getPanes().overlayImage; 37 | pane.appendChild(this.div_); 38 | 39 | // redraw if any option is changed 40 | var ctx = this; 41 | var callback = function() { ctx.draw(); }; 42 | this.listeners_ = [ 43 | google.maps.event.addListener(this, 'opacity_changed', callback), 44 | google.maps.event.addListener(this, 'position_changed', callback), 45 | google.maps.event.addListener(this, 'visible_changed', callback), 46 | google.maps.event.addListener(this, 'clickable_changed', callback), 47 | google.maps.event.addListener(this, 'text_changed', callback), 48 | google.maps.event.addListener(this, 'zindex_changed', callback), 49 | google.maps.event.addDomListener(this.div_, 'click', function() { 50 | if (ctx.get('clickable')) { 51 | google.maps.event.trigger(ctx, 'click'); 52 | } 53 | }) 54 | ]; 55 | }; 56 | 57 | 58 | // Implement onRemove 59 | google.maps.Label.prototype.onRemove = function() { 60 | this.div_.parentNode.removeChild(this.div_); 61 | 62 | // remove all listeners 63 | for (var i = 0, j = this.listeners_.length; i < j; i++) { 64 | google.maps.event.removeListener(this.listeners_[i]); 65 | } 66 | }; 67 | 68 | 69 | // Implement draw 70 | google.maps.Label.prototype.draw = function() { 71 | var projection = this.getProjection(); 72 | var position = projection.fromLatLngToDivPixel(this.get('position')); 73 | 74 | var div = this.div_; 75 | if(position !== null) { 76 | div.style.left = position.x + 'px'; 77 | div.style.top = position.y + 'px'; 78 | } 79 | 80 | div.style.display = this.get('visible') && this.get('opacity') >= 0.6 ? 'block' : 'none'; 81 | this.span_.style.cursor = this.get('clickable') ? 'pointer' : ''; 82 | div.style.zIndex = this.get('zIndex'); 83 | this.span_.innerHTML = this.get('text').toString(); 84 | }; 85 | 86 | 87 | // custom dropdown menu control 88 | 89 | google.maps.DropDownControl = function(options) { 90 | var ctx = this; 91 | this.options = options; 92 | 93 | // generate the controls 94 | this.div_ = document.createElement('div'); 95 | this.div_.className = "gmnoprint"; 96 | this.div_.draggable = false; 97 | 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"; 98 | 99 | this.div_head = document.createElement('div'); 100 | 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;"; 101 | this.div_head.title = options.title; 102 | 103 | google.maps.event.addDomListener(this.div_head, 'mouseover', function(){ 104 | ctx.div_head.style.backgroundColor = "rgb(235,235,235)"; 105 | }); 106 | google.maps.event.addDomListener(this.div_head, 'mouseout', function(){ 107 | ctx.div_head.style.backgroundColor = "rgb(255,255,255)"; 108 | }); 109 | 110 | this.header = document.createElement('span'); 111 | this.header.innerHTML = (options.headerPrefix || "") + options.list[options.listDefault || 0]; 112 | var arrow = document.createElement('img'); 113 | arrow.src = "//maps.gstatic.com/mapfiles/arrow-down.png"; 114 | 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;"; 115 | 116 | this.div_head.appendChild(this.header); 117 | this.div_head.appendChild(arrow); 118 | this.div_.appendChild(this.div_head); 119 | 120 | // generate list of dropdown entries 121 | this.div_list = document.createElement('div'); 122 | 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;"; 123 | 124 | var div_list = this.div_list; 125 | 126 | options.list.forEach(function(name) { 127 | var row = document.createElement('div'); 128 | 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);"; 129 | row.innerHTML = name; 130 | 131 | google.maps.event.addDomListener(row, 'click', function(){ 132 | if(ctx.options.callback(row.innerHTML)) { 133 | ctx.header.innerHTML = (ctx.options.headerPrefix || "") + row.innerHTML; 134 | row.style.fontWeight = "800"; 135 | row.style.color = "rgb(0,0,0)"; 136 | } 137 | }); 138 | google.maps.event.addDomListener(row, 'mouseover', function(){ 139 | if(ctx.header.innerHTML == (ctx.options.headerPrefix || "") + row.innerHTML) { 140 | row.style.fontWeight = "800"; 141 | row.style.color = "rgb(0,0,0)"; 142 | } 143 | row.style.backgroundColor = "rgb(235,235,235)"; 144 | }); 145 | google.maps.event.addDomListener(row, 'mouseout', function(){ 146 | row.style.fontWeight = "500"; 147 | row.style.color = "rgb(86,86,86)"; 148 | row.style.backgroundColor = "rgb(255,255,255)"; 149 | }); 150 | 151 | div_list.appendChild(row); 152 | }); 153 | 154 | this.div_.appendChild(this.div_list); 155 | 156 | // add control 157 | options.map.controls[options.position].push(this.div_); 158 | 159 | // event for expanding 160 | 161 | google.maps.event.addDomListener(this.div_head, 'click', function(){ 162 | clearTimeout(ctx.hideTimeout); 163 | div_list.style.display = (div_list.style.display != 'none') ? 'none' : 'block'; 164 | }); 165 | google.maps.event.addDomListener(this.div_, 'mouseout', function(){ 166 | ctx.hideTimeout = setTimeout(function() { div_list.style.display = 'none'; }, 1000); 167 | }); 168 | google.maps.event.addDomListener(this.div_, 'mouseover', function(){ 169 | clearTimeout(ctx.hideTimeout); 170 | }); 171 | }; 172 | 173 | google.maps.DropDownControl.prototype.setVisible = function(isVisible) { 174 | isVisible = !!isVisible; 175 | this.div_.style.display = (isVisible) ? 'block' : 'none'; 176 | }; 177 | 178 | google.maps.DropDownControl.prototype.select = function(text) { 179 | this.header.innerHTML = (this.options.headerPrefix || "") + text; 180 | }; 181 | 182 | // simple status control 183 | 184 | google.maps.StatusTextControl = function(options) { 185 | this.options = options || { 186 | text: "", 187 | map: null, 188 | position: 0, 189 | fontSize: "10px", 190 | }; 191 | 192 | this.div_ = document.createElement('div'); 193 | this.div_.style.cssText = "display: none"; 194 | this.div_.innerHTML = "
" + 195 | "
" + 196 | "
"; 197 | 198 | var div = document.createElement('div'); 199 | div.style.cssText = 'position: relative; padding-right: 6px; padding-left: 6px;' + 200 | ' font-family: Roboto, Arial, sans-serif; color: rgb(68, 68, 68);' + 201 | ' white-space: nowrap; direction: ltr; text-align: right;' + 202 | ' font-size: ' + this.options.fontSize; 203 | 204 | this.span_ = document.createElement('span'); 205 | div.appendChild(this.span_); 206 | this.div_.appendChild(div); 207 | 208 | // update text 209 | this.setText(this.options.text); 210 | 211 | // add control 212 | if(this.options.map) 213 | this.options.map.controls[options.position].push(this.div_); 214 | }; 215 | 216 | google.maps.StatusTextControl.prototype.setText = function(text) { 217 | this.options.text = text; 218 | this.span_.innerHTML = text; 219 | this.div_.style.display = (text === "") ? "none" : "block"; 220 | }; 221 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.2 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 6/20/2012 8 | 9 | * Edited by Daniel Saul 10 | * For use in the Habitat Webpage Template 11 | 12 | */ 13 | 14 | 15 | 16 | /* Table of Content 17 | ================================================== 18 | #Reset & Basics 19 | #Basic Styles 20 | #Site Styles 21 | #Typography 22 | #Links 23 | #Lists 24 | #Images 25 | #Buttons 26 | #Forms 27 | #Misc */ 28 | 29 | 30 | /* #Reset & Basics (Inspired by E. Meyers) 31 | ================================================== */ 32 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 33 | margin: 0; 34 | padding: 0; 35 | border: 0; 36 | font-size: 100%; 37 | font: inherit; 38 | vertical-align: baseline; } 39 | sup { 40 | font-size: smaller; 41 | vertical-align: +0.4em; } 42 | sub { 43 | font-size: smaller; 44 | vertical-align: -0.25em; } 45 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 46 | display: block; } 47 | body { 48 | line-height: 1; } 49 | ol, ul { 50 | list-style: none; } 51 | blockquote, q { 52 | quotes: none; } 53 | blockquote:before, blockquote:after, 54 | q:before, q:after { 55 | content: ''; 56 | content: none; } 57 | table { 58 | border-collapse: collapse; 59 | border-spacing: 0; } 60 | 61 | 62 | /* #Basic Styles 63 | ================================================== */ 64 | body { 65 | background: #fff; 66 | font: 14px/21px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 67 | color: #666; 68 | -webkit-font-smoothing: antialiased; /* Fix for webkit rendering */ 69 | -webkit-text-size-adjust: 100%; 70 | } 71 | 72 | 73 | /* #Typography 74 | ================================================== */ 75 | h1, h2, h3, h4, h5, h6 { 76 | font-weight: normal; } 77 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } 78 | h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;} 79 | h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; } 80 | h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; } 81 | h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; } 82 | h5 { font-size: 17px; line-height: 24px; } 83 | h6 { font-size: 14px; line-height: 21px; } 84 | .subheader { color: #777; } 85 | 86 | p { margin: 0 0 20px 0; } 87 | p img { margin: 0; } 88 | p.lead { font-size: 21px; line-height: 27px; color: #777; } 89 | 90 | em { font-style: italic; } 91 | strong { font-weight: bold; color: #333; } 92 | b { font-weight: bold; } 93 | small { font-size: 80%; } 94 | 95 | /* Blockquotes */ 96 | blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; } 97 | blockquote { margin: 0 0 20px; padding: 9px 20px 0 19px; border-left: 1px solid #ddd; } 98 | blockquote cite { display: block; font-size: 12px; color: #555; } 99 | blockquote cite:before { content: "\2014 \0020"; } 100 | blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: #555; } 101 | 102 | hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; } 103 | 104 | 105 | /* #Links 106 | ================================================== */ 107 | a, a:visited { color: #333; text-decoration: underline; outline: 0; } 108 | a:hover, a:focus { color: #000; } 109 | p a, p a:visited { line-height: inherit; } 110 | 111 | 112 | /* #Lists 113 | ================================================== */ 114 | ul, ol { margin-bottom: 20px; } 115 | ul { list-style: none outside; } 116 | ol { list-style: decimal; } 117 | ol, ul.square, ul.circle, ul.disc { margin-left: 30px; } 118 | ul.square { list-style: square outside; } 119 | ul.circle { list-style: circle outside; } 120 | ul.disc { list-style: disc outside; } 121 | ul ul, ul ol, 122 | ol ol, ol ul { margin: 4px 0 5px 30px; font-size: 90%; } 123 | ul ul li, ul ol li, 124 | ol ol li, ol ul li { margin-bottom: 6px; } 125 | li { line-height: 18px; margin-bottom: 12px; } 126 | ul.large li { line-height: 21px; } 127 | li p { line-height: 21px; } 128 | 129 | /* #Images 130 | ================================================== */ 131 | 132 | img.scale-with-grid { 133 | max-width: 100%; 134 | height: auto; } 135 | 136 | 137 | /* #Buttons 138 | ================================================== */ 139 | 140 | .button, 141 | button, 142 | input[type="submit"], 143 | input[type="reset"], 144 | input[type="button"] { 145 | background: #eee; /* Old browsers */ 146 | background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */ 147 | background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */ 148 | background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */ 149 | background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */ 150 | background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */ 151 | background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */ 152 | border: 1px solid #aaa; 153 | border-top: 1px solid #ccc; 154 | border-left: 1px solid #ccc; 155 | -moz-border-radius: 3px; 156 | -webkit-border-radius: 3px; 157 | border-radius: 3px; 158 | color: #444; 159 | display: inline-block; 160 | font-size: 11px; 161 | font-weight: bold; 162 | text-decoration: none; 163 | text-shadow: 0 1px rgba(255, 255, 255, .75); 164 | cursor: pointer; 165 | margin-bottom: 20px; 166 | line-height: normal; 167 | padding: 8px 10px; 168 | font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; } 169 | 170 | .button:hover, 171 | button:hover, 172 | input[type="submit"]:hover, 173 | input[type="reset"]:hover, 174 | input[type="button"]:hover { 175 | color: #222; 176 | background: #ddd; /* Old browsers */ 177 | background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */ 178 | background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */ 179 | background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */ 180 | background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */ 181 | background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */ 182 | background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */ 183 | border: 1px solid #888; 184 | border-top: 1px solid #aaa; 185 | border-left: 1px solid #aaa; } 186 | 187 | .button:active, 188 | button:active, 189 | input[type="submit"]:active, 190 | input[type="reset"]:active, 191 | input[type="button"]:active { 192 | border: 1px solid #666; 193 | background: #ccc; /* Old browsers */ 194 | background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */ 195 | background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */ 196 | background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */ 197 | background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */ 198 | background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */ 199 | background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ } 200 | 201 | .button.full-width, 202 | button.full-width, 203 | input[type="submit"].full-width, 204 | input[type="reset"].full-width, 205 | input[type="button"].full-width { 206 | width: 100%; 207 | padding-left: 0 !important; 208 | padding-right: 0 !important; 209 | text-align: center; } 210 | 211 | /* Fix for odd Mozilla border & padding issues */ 212 | button::-moz-focus-inner, 213 | input::-moz-focus-inner { 214 | border: 0; 215 | padding: 0; 216 | } 217 | 218 | .button.disabled, 219 | button.disabled, 220 | input[type="submit"].disabled, 221 | input[type="reset"].disabled, 222 | input[type="button"].disabled { 223 | border: 1px solid #aaa; 224 | color: #aaa; 225 | background: #fff; 226 | } 227 | 228 | 229 | /* #Forms 230 | ================================================== */ 231 | 232 | form { 233 | margin-bottom: 20px; } 234 | fieldset { 235 | margin-bottom: 20px; } 236 | input[type="text"], 237 | input[type="password"], 238 | input[type="email"], 239 | textarea, 240 | select { 241 | border: 1px solid #ccc; 242 | padding: 6px 4px; 243 | outline: none; 244 | -moz-border-radius: 4px; 245 | -webkit-border-radius: 4px; 246 | border-radius: 4px; 247 | font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 248 | color: #888; 249 | margin: 0; 250 | width: 210px; 251 | max-width: 100%; 252 | display: block; 253 | margin-bottom: 20px; 254 | background: #fff; } 255 | select { 256 | padding: 0; } 257 | input[type="text"]:focus, 258 | input[type="password"]:focus, 259 | input[type="email"]:focus, 260 | textarea:focus { 261 | border: 1px solid #aaa; 262 | color: #444; 263 | -moz-box-shadow: 0 0 3px rgba(0,0,0,.2); 264 | -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2); 265 | box-shadow: 0 0 3px rgba(0,0,0,.2); } 266 | textarea { 267 | min-height: 60px; } 268 | label, 269 | legend { 270 | display: block; 271 | font-weight: bold; 272 | font-size: 13px; } 273 | select { 274 | width: 220px; } 275 | input[type="checkbox"] { 276 | display: inline; } 277 | label span, 278 | legend span { 279 | font-weight: normal; 280 | font-size: 13px; 281 | color: #444; } 282 | 283 | /* #Misc 284 | ================================================== */ 285 | .remove-bottom { margin-bottom: 0 !important; } 286 | .half-bottom { margin-bottom: 10px !important; } 287 | .add-bottom { margin-bottom: 20px !important; } 288 | .no-margin { margin: 0 !important; padding: 0; } 289 | 290 | 291 | -------------------------------------------------------------------------------- /css/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.2 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 6/20/2012 8 | */ 9 | 10 | 11 | /* Table of Contents 12 | ================================================== 13 | #Base 960 Grid 14 | #Tablet (Portrait) 15 | #Mobile (Portrait) 16 | #Mobile (Landscape) 17 | #Clearing */ 18 | 19 | 20 | 21 | /* #Base 960 Grid 22 | ================================================== */ 23 | 24 | .container { position: relative; width: 960px; margin: 0 auto; padding: 0; } 25 | .container .column, 26 | .container .columns { float: left; display: inline; margin-left: 10px; margin-right: 10px; } 27 | .row { margin-bottom: 20px; } 28 | 29 | /* Nested Column Classes */ 30 | .column.alpha, .columns.alpha { margin-left: 0; } 31 | .column.omega, .columns.omega { margin-right: 0; } 32 | 33 | /* Base Grid */ 34 | .container .one.column, 35 | .container .one.columns { width: 40px; } 36 | .container .two.columns { width: 100px; } 37 | .container .three.columns { width: 160px; } 38 | .container .four.columns { width: 220px; } 39 | .container .five.columns { width: 280px; } 40 | .container .six.columns { width: 340px; } 41 | .container .seven.columns { width: 400px; } 42 | .container .eight.columns { width: 460px; } 43 | .container .nine.columns { width: 520px; } 44 | .container .ten.columns { width: 580px; } 45 | .container .eleven.columns { width: 640px; } 46 | .container .twelve.columns { width: 700px; } 47 | .container .thirteen.columns { width: 760px; } 48 | .container .fourteen.columns { width: 820px; } 49 | .container .fifteen.columns { width: 880px; } 50 | .container .sixteen.columns { width: 940px; } 51 | 52 | .container .one-third.column { width: 300px; } 53 | .container .two-thirds.column { width: 620px; } 54 | 55 | /* Offsets */ 56 | .container .offset-by-one { padding-left: 60px; } 57 | .container .offset-by-two { padding-left: 120px; } 58 | .container .offset-by-three { padding-left: 180px; } 59 | .container .offset-by-four { padding-left: 240px; } 60 | .container .offset-by-five { padding-left: 300px; } 61 | .container .offset-by-six { padding-left: 360px; } 62 | .container .offset-by-seven { padding-left: 420px; } 63 | .container .offset-by-eight { padding-left: 480px; } 64 | .container .offset-by-nine { padding-left: 540px; } 65 | .container .offset-by-ten { padding-left: 600px; } 66 | .container .offset-by-eleven { padding-left: 660px; } 67 | .container .offset-by-twelve { padding-left: 720px; } 68 | .container .offset-by-thirteen { padding-left: 780px; } 69 | .container .offset-by-fourteen { padding-left: 840px; } 70 | .container .offset-by-fifteen { padding-left: 900px; } 71 | 72 | 73 | 74 | /* #Tablet (Portrait) 75 | ================================================== */ 76 | 77 | /* Note: Design for a width of 768px */ 78 | 79 | @media only screen and (min-width: 768px) and (max-width: 959px) { 80 | .container { width: 768px; } 81 | .container .column, 82 | .container .columns { margin-left: 10px; margin-right: 10px; } 83 | .column.alpha, .columns.alpha { margin-left: 0; margin-right: 10px; } 84 | .column.omega, .columns.omega { margin-right: 0; margin-left: 10px; } 85 | .alpha.omega { margin-left: 0; margin-right: 0; } 86 | 87 | .container .one.column, 88 | .container .one.columns { width: 28px; } 89 | .container .two.columns { width: 76px; } 90 | .container .three.columns { width: 124px; } 91 | .container .four.columns { width: 172px; } 92 | .container .five.columns { width: 220px; } 93 | .container .six.columns { width: 268px; } 94 | .container .seven.columns { width: 316px; } 95 | .container .eight.columns { width: 364px; } 96 | .container .nine.columns { width: 412px; } 97 | .container .ten.columns { width: 460px; } 98 | .container .eleven.columns { width: 508px; } 99 | .container .twelve.columns { width: 556px; } 100 | .container .thirteen.columns { width: 604px; } 101 | .container .fourteen.columns { width: 652px; } 102 | .container .fifteen.columns { width: 700px; } 103 | .container .sixteen.columns { width: 748px; } 104 | 105 | .container .one-third.column { width: 236px; } 106 | .container .two-thirds.column { width: 492px; } 107 | 108 | /* Offsets */ 109 | .container .offset-by-one { padding-left: 48px; } 110 | .container .offset-by-two { padding-left: 96px; } 111 | .container .offset-by-three { padding-left: 144px; } 112 | .container .offset-by-four { padding-left: 192px; } 113 | .container .offset-by-five { padding-left: 240px; } 114 | .container .offset-by-six { padding-left: 288px; } 115 | .container .offset-by-seven { padding-left: 336px; } 116 | .container .offset-by-eight { padding-left: 384px; } 117 | .container .offset-by-nine { padding-left: 432px; } 118 | .container .offset-by-ten { padding-left: 480px; } 119 | .container .offset-by-eleven { padding-left: 528px; } 120 | .container .offset-by-twelve { padding-left: 576px; } 121 | .container .offset-by-thirteen { padding-left: 624px; } 122 | .container .offset-by-fourteen { padding-left: 672px; } 123 | .container .offset-by-fifteen { padding-left: 720px; } 124 | } 125 | 126 | 127 | /* #Mobile (Portrait) 128 | ================================================== */ 129 | 130 | /* Note: Design for a width of 320px */ 131 | 132 | @media only screen and (max-width: 767px) { 133 | .container { width: 300px; } 134 | .container .columns, 135 | .container .column { margin: 0; } 136 | 137 | .container .one.column, 138 | .container .one.columns, 139 | .container .two.columns, 140 | .container .three.columns, 141 | .container .four.columns, 142 | .container .five.columns, 143 | .container .six.columns, 144 | .container .seven.columns, 145 | .container .eight.columns, 146 | .container .nine.columns, 147 | .container .ten.columns, 148 | .container .eleven.columns, 149 | .container .twelve.columns, 150 | .container .thirteen.columns, 151 | .container .fourteen.columns, 152 | .container .fifteen.columns, 153 | .container .sixteen.columns, 154 | .container .one-third.column, 155 | .container .two-thirds.column { width: 300px; } 156 | 157 | /* Offsets */ 158 | .container .offset-by-one, 159 | .container .offset-by-two, 160 | .container .offset-by-three, 161 | .container .offset-by-four, 162 | .container .offset-by-five, 163 | .container .offset-by-six, 164 | .container .offset-by-seven, 165 | .container .offset-by-eight, 166 | .container .offset-by-nine, 167 | .container .offset-by-ten, 168 | .container .offset-by-eleven, 169 | .container .offset-by-twelve, 170 | .container .offset-by-thirteen, 171 | .container .offset-by-fourteen, 172 | .container .offset-by-fifteen { padding-left: 0; } 173 | 174 | } 175 | 176 | 177 | /* #Mobile (Landscape) 178 | ================================================== */ 179 | 180 | /* Note: Design for a width of 480px */ 181 | 182 | @media only screen and (min-width: 480px) and (max-width: 767px) { 183 | .container { width: 420px; } 184 | .container .columns, 185 | .container .column { margin: 0; } 186 | 187 | .container .one.column, 188 | .container .one.columns, 189 | .container .two.columns, 190 | .container .three.columns, 191 | .container .four.columns, 192 | .container .five.columns, 193 | .container .six.columns, 194 | .container .seven.columns, 195 | .container .eight.columns, 196 | .container .nine.columns, 197 | .container .ten.columns, 198 | .container .eleven.columns, 199 | .container .twelve.columns, 200 | .container .thirteen.columns, 201 | .container .fourteen.columns, 202 | .container .fifteen.columns, 203 | .container .sixteen.columns, 204 | .container .one-third.column, 205 | .container .two-thirds.column { width: 420px; } 206 | } 207 | 208 | 209 | /* #Clearing 210 | ================================================== */ 211 | 212 | /* Self Clearing Goodness */ 213 | .container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; } 214 | 215 | /* Use clearfix class on parent to clear nested columns, 216 | or wrap each row of columns in a
*/ 217 | .clearfix:before, 218 | .clearfix:after, 219 | .row:before, 220 | .row:after { 221 | content: '\0020'; 222 | display: block; 223 | overflow: hidden; 224 | visibility: hidden; 225 | width: 0; 226 | height: 0; } 227 | .row:after, 228 | .clearfix:after { 229 | clear: both; } 230 | .row, 231 | .clearfix { 232 | zoom: 1; } 233 | 234 | /* You can also use a
to clear columns */ 235 | .clear { 236 | clear: both; 237 | display: block; 238 | overflow: hidden; 239 | visibility: hidden; 240 | width: 0; 241 | height: 0; 242 | } 243 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /* Habitat Mobile Tracker 2 | * Main style sheet 3 | * 4 | */ 5 | @font-face { 6 | font-family: 'Roboto'; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: local('Roboto Regular'), local('Roboto-Regular'), url(../font/Roboto-regular.woff) format('woff'); 10 | } 11 | 12 | html, body { 13 | margin: 0; 14 | padding: 0; 15 | width: 100%; 16 | height: 100%; 17 | overflow: hidden; 18 | -webkit-touch-action: none; 19 | -khtml-touch-action: none; 20 | -moz-touch-action: none; 21 | -ms-touch-action: none; 22 | touch-action: none; 23 | } 24 | body { 25 | font-family: "Roboto", Ariel, sans-serif; 26 | } 27 | 28 | .noselect { 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | .rfloat { 38 | float: right; 39 | } 40 | .lfloat { 41 | float: left; 42 | } 43 | 44 | .iScrollVerticalScrollbar { 45 | z-index: 49; 46 | width: 5px; 47 | bottom: 6px; 48 | top: 6px; 49 | left: 1px; 50 | position: absolute; 51 | } 52 | 53 | .iScrollVerticalScrollbar > div { 54 | position: absolute; 55 | background: none repeat scroll 0 0 padding-box rgba(0, 0, 0, 0.5); 56 | border: 1px solid rgba(255, 255, 255, 0.9); 57 | border-radius: 3px 3px 3px 3px; 58 | position: absolute; 59 | width: 100%; 60 | z-index: 49; 61 | } 62 | 63 | .slickbox { 64 | z-index: 49; 65 | background-color: #fff; 66 | position: absolute; 67 | height: 28px; 68 | width: 195px; 69 | border-radius: 20px; 70 | box-shadow: 0 0 5px #888; 71 | font-size: 11px; 72 | } 73 | 74 | .slickbox svg { 75 | width: 28px; 76 | height: 28px; 77 | } 78 | 79 | .slickbox span { 80 | line-height: 14px; 81 | } 82 | 83 | .slickbox div { 84 | font-size: 14px; 85 | margin-top: 3px; 86 | width: 150px; 87 | text-align: center; 88 | } 89 | 90 | .slickbox svg path { 91 | -webkit-transition: all 0.4s ease-in-out; 92 | -moz-transition: all 0.4s ease-in-out; 93 | -ms-transition: all 0.4s ease-in-out; 94 | -o-transition: all 0.4s ease-in-out; 95 | transition: all 0.4s ease-in-out; 96 | fill: #00a3d3; 97 | } 98 | 99 | #timebox { 100 | top: 7px; 101 | right: 5px; 102 | } 103 | 104 | #lookanglesbox { 105 | top: 40px; 106 | right: 5px; 107 | } 108 | 109 | #timebox.past svg path { 110 | fill: #c00; 111 | } 112 | #timebox .current { 113 | margin-left: 11px; 114 | } 115 | #timebox .local { 116 | margin-left: 5px; 117 | } 118 | 119 | .slickbox .azimuth { 120 | margin-left: 9px; 121 | width: 91px; 122 | } 123 | .slickbox .bearing { 124 | margin-right: 5px; 125 | width: 60px; 126 | } 127 | .slickbox .elevation { 128 | margin-left: 5px; 129 | width: 95px; 130 | } 131 | .slickbox .range { 132 | margin-right: 5px; 133 | width: 60px; 134 | } 135 | 136 | #mapscreen { 137 | float: right; 138 | position: relative; 139 | } 140 | 141 | #map img { 142 | max-width: none; 143 | } 144 | 145 | #loading { 146 | position: absolute; 147 | z-index: 99; 148 | width: 100%; 149 | height: 100%; 150 | background: #00A3D3; 151 | } 152 | #loading img { 153 | width: 198px; 154 | height: 178px; 155 | } 156 | #loading > div { 157 | position: absolute; 158 | top: 50%; 159 | left: 50%; 160 | width: 198px; 161 | height: 200px; 162 | margin-top: -100px; 163 | margin-left: -99px; 164 | } 165 | #loading .bar { 166 | position: relative; 167 | width: 200px; 168 | height: 5px; 169 | border-radius: 3px; 170 | background: #005C76; 171 | } 172 | #loading .complete { 173 | width: 0px; 174 | height: 5px; 175 | border-radius: 3px; 176 | background: #fff; 177 | } 178 | 179 | header { 180 | position: fixed; 181 | top: 0; 182 | left; 0; 183 | padding: 0; 184 | margin: 0; 185 | height: 50px; 186 | min-height: 50px; 187 | max-height: 50px; 188 | line-height: normal; 189 | border-bottom: 5px solid #33b5e5; 190 | box-shadow: 0px 1px 3px #555; 191 | z-index: 5; 192 | } 193 | #app_name { 194 | line-height: normal; 195 | margin-top: 6px; 196 | position: absolute; 197 | left: 225px; 198 | height: 40px; 199 | text-align: left; 200 | cursor: pointer; 201 | } 202 | header > div { 203 | position: relative; 204 | height: 50px; 205 | } 206 | 207 | #mapscreen { 208 | margin-top: 55px; 209 | } 210 | 211 | #map, 212 | #main { 213 | position: relative; 214 | z-index: 1; 215 | } 216 | #map { 217 | margin: 0; 218 | padding: 0; 219 | height: 100%; 220 | width: 100%; 221 | } 222 | #main { 223 | float: left; 224 | overflow: hidden; 225 | -webkit-touch-callout: none; 226 | -webkit-user-select: none; 227 | -khtml-user-select: none; 228 | -moz-user-select: none; 229 | -ms-user-select: none; 230 | user-select: none; 231 | } 232 | 233 | header .search { 234 | float: left; 235 | width: 190px; 236 | margin-top: 10px; 237 | } 238 | 239 | header .search form { 240 | position: relative; 241 | } 242 | header .search form input { 243 | position: absolute; 244 | border-radius: 100px; 245 | } 246 | header .search form input[type='text'] { 247 | left: 0; 248 | padding-left: 10px; 249 | padding-right: 35px; 250 | width: 143px; 251 | } 252 | header .search form input[type='submit'] { 253 | right: 0; 254 | } 255 | 256 | .nav { 257 | list-style: none outside none; 258 | margin: 0; 259 | padding: 0; 260 | height: 40px; 261 | display: block; 262 | min-width: 40px; 263 | width: auto; 264 | float: right; 265 | margin: 5px 0px; 266 | } 267 | 268 | #locate-me { 269 | position: absolute; 270 | left: 195px; 271 | top: 12px; 272 | font-size: 25px; 273 | height: 25px; 274 | width: 25px; 275 | line-height: 25px; 276 | cursor: pointer; 277 | } 278 | 279 | .nav > li { 280 | margin: 0; 281 | padding: 0 5px;; 282 | float: left; 283 | height: 40px; 284 | width: 35px; 285 | border-right: 1px solid #33b5e5; 286 | cursor: pointer; 287 | color: #fff; 288 | line-height: 45px; 289 | font-size: 35px; 290 | } 291 | .nav > li.last { border: 0; } 292 | .nav > li:active { background-color: #33b5e5; } 293 | .nav > li:hover { border-bottom: 5px solid #fff; } 294 | 295 | 296 | #main .data { 297 | cursor: url('../img/openhand.cur'), row-resize; 298 | } 299 | 300 | #main.drag, 301 | #main.drag .data, 302 | #main.drag .header { 303 | cursor: url('../img/closedhand.cur'), row-resize; 304 | } 305 | 306 | #main .header { 307 | height: 20px; 308 | padding: 10px; 309 | padding-right: 3px; 310 | padding-left: 5px; 311 | border-bottom: 1px solid #33b5e5; 312 | position: relative; 313 | z-index: 51; 314 | cursor: pointer; 315 | background-color: #fff; 316 | border-left: 5px solid #fff; 317 | } 318 | #main .row.selected { 319 | border-left: 5px solid #00A3D3; 320 | } 321 | #main .row:hover .header { 322 | border-left: 5px solid #00A3D3; 323 | } 324 | 325 | #main .row:hover .data { 326 | border-left: 5px solid #ccc; 327 | } 328 | 329 | #main .vehicle0 .header { 330 | border-top: 1px solid #33b5e5; 331 | } 332 | #main .header.empty { 333 | text-align: center; 334 | width: 100%; 335 | height: 120px; 336 | line-height: 100px; 337 | border: 0; 338 | } 339 | #main .header.empty:hover { 340 | border:0; 341 | } 342 | #main .header span { 343 | overflow: hidden; 344 | display: block; 345 | width: 90%; 346 | float: left; 347 | white-space: nowrap; 348 | } 349 | 350 | .header .arrow { 351 | font-weight: normal; 352 | float: right; 353 | color: #aaa; 354 | display: block; 355 | height: 14px; 356 | width: 16px; 357 | line-height: 11px; 358 | font-size: 16px; 359 | margin-top: 4px; 360 | -webkit-transition: 0.2s linear; 361 | -moz-transition: 0.2s linear; 362 | -ms-transition: 0.2s linear; 363 | -o-transition: 0.2s linear; 364 | transition: 0.2s linear; 365 | -webkit-transform-origin: center; 366 | -moz-transform-origin: center; 367 | -ms-transform-origin: center; 368 | -o-transform-origin: center; 369 | transform-origin: center; 370 | } 371 | 372 | 373 | .row .header .arrow:after { 374 | content: "▲"; 375 | } 376 | .row:hover .arrow { 377 | -webkit-transform: rotate(-90deg); 378 | -moz-transform: rotate(-90deg); 379 | -ms-transform: rotate(-90deg); 380 | -o-transform: rotate(-90deg); 381 | transform: rotate(-90deg); 382 | color: #00a3d3; 383 | } 384 | .row.active .header .arrow { 385 | -webkit-transform: rotate(-180deg); 386 | -moz-transform: rotate(-180deg); 387 | -ms-transform: rotate(-180deg); 388 | -o-transform: rotate(-180deg); 389 | transform: rotate(-180deg); 390 | } 391 | 392 | #main .row { 393 | background-color: #f4f4f4; 394 | margin: 0; 395 | padding: 0; 396 | position: relative; 397 | } 398 | #main .row .header { 399 | } 400 | #main .row .data { 401 | display: none; 402 | width: 100%; 403 | border-left: 5px solid #F4F4F4; 404 | } 405 | 406 | #main .row .icon-target:before { 407 | display: none; 408 | } 409 | #main .row.follow .icon-target:before { 410 | display: inline-block; 411 | } 412 | #main .row.active .data { display: inline-block; } 413 | #main .row .data .left, 414 | #main .row .data .right { 415 | position: relative; 416 | z-index: 4; 417 | } 418 | #main .row .data .vbutton { 419 | position: absolute; 420 | background-color: #fff; 421 | right: 5px; 422 | top: 150px; 423 | padding-left: 3px; 424 | padding-right: 3px; 425 | font-size: 10px; 426 | border-radius: 5px; 427 | border: 1px solid #ccc; 428 | cursor: pointer; 429 | z-index: 5; 430 | } 431 | 432 | #main .row .data .vbutton.active { 433 | background-color: #33b5e5; 434 | border: 1px solid #33b5e5; 435 | color: #fff; 436 | } 437 | 438 | #main .row .data .vbutton:hover { 439 | border: 1px solid #5E5E5E; 440 | } 441 | 442 | #main .portrait .row .data .vbutton { 443 | display: none; 444 | } 445 | 446 | #main .row .data img { 447 | position: absolute; 448 | z-index: 2; 449 | bottom: 40%; 450 | right: 30%; 451 | opacity: 0.6; 452 | width: 46px; 453 | height: 84px; 454 | -webkit-transition: 0.2s ease; 455 | -moz-transition: 0.2s ease; 456 | -ms-transition: 0.2s ease; 457 | -o-transition: 0.2s ease; 458 | transition: 0.2s ease; 459 | } 460 | #main .row .data img.car { 461 | width: 55px; 462 | height: 25px; 463 | } 464 | #main .row:hover .data img { 465 | opacity: 0.8; 466 | } 467 | #main .row.follow .data img { 468 | opacity: 1.0; 469 | } 470 | #main .row .header .graph { 471 | position: absolute; 472 | bottom: -1px; 473 | right: 22px; 474 | width: 60px; 475 | height: 40px; 476 | z-index: 1; 477 | } 478 | #main .row .data dt > i { 479 | font-size: 12px; 480 | } 481 | #main .row .data a { 482 | text-decoration: none; 483 | color: #00A3D3; 484 | } 485 | #main .data dl > dt.receivers { 486 | font-size: 12px; 487 | font-weight: normal; 488 | } 489 | 490 | .flatpage { 491 | margin-top: 55px; 492 | overflow: auto; 493 | position: absolute; 494 | width: 100%; 495 | z-index: 100; 496 | background: #fff; 497 | } 498 | 499 | .topanel { 500 | float: right; 501 | position: relative; 502 | width: auto; 503 | padding: 0; 504 | padding-left: 10px; 505 | padding-right: 20px; 506 | box-shadow: 2px 0px 8px 0px #555; 507 | overflow-x: hidden; 508 | z-index: 3; 509 | } 510 | 511 | .flatpage p { 512 | display: block; 513 | text-align: justify; 514 | margin-bottom: 15px; 515 | } 516 | #cc_callsign { 517 | text-align: right; 518 | padding: 4px 10px; 519 | margin: 0; 520 | } 521 | .slimContainer { 522 | position: relative; 523 | margin: 20px auto; 524 | width: 290px; 525 | } 526 | .slimContainer hr { 527 | margin-bottom: 10px; 528 | } 529 | .slimContainer .row { 530 | width: 280px; 531 | display: block; 532 | margin: 5px; 533 | vertical-align: middle; 534 | position: relative; 535 | } 536 | .slimContainer .row.info { 537 | margin-top: 10px; 538 | } 539 | .slimContainer .row > span { 540 | float: left; 541 | } 542 | .slimContainer .row.option > span { 543 | width: 200px; 544 | } 545 | .slimContainer .row.option > span { 546 | line-height: 30px; 547 | } 548 | .slimContainer .row > span.r { 549 | float: right; 550 | } 551 | 552 | /* iOS styled switch buttons 553 | */ 554 | .switch { 555 | position: absolute; 556 | right: 0px; 557 | height: 28px; 558 | width: 77px; 559 | border: 1px solid #979797; 560 | border-radius: 20px; 561 | box-shadow: inset 0 1px 3px #BABABA, inset 0 12px 3px 2px rgba(232, 232, 232, 0.5); 562 | cursor: pointer; 563 | overflow: hidden; 564 | } 565 | .switch input[type=checkbox] { 566 | display: none; 567 | } 568 | .switch:before { 569 | content: ""; 570 | display: block; 571 | height: 28px; 572 | width: 0px; 573 | position: absolute; 574 | border-radius: 20px; 575 | -webkit-box-shadow: inset 0 1px 2px #33B5E5, inset 0 12px 3px 2px #00A3D3; 576 | box-shadow: inset 0 1px 2px #33B5E5, inset 0 12px 3px 2px #00A3D3; 577 | background-color: #33B5E5; 578 | } 579 | .switch.on:before { 580 | width: 77px; 581 | } 582 | .switch > .thumb { 583 | display: block; 584 | width: 26px; 585 | height: 26px; 586 | position: relative; 587 | top: 0; 588 | z-index: 51; 589 | border: solid 1px #919191; 590 | border-radius: 28px; 591 | box-shadow: inset 0 2px 1px white, inset 0 -2px 1px white; 592 | background-color: #CECECE; 593 | background-image: -webkit-linear-gradient(#CECECE, #FBFBFB); 594 | background-image: -moz-linear-gradient(#CECECE, #FBFBFB); 595 | background-image: -o-linear-gradient(#CECECE, #FBFBFB); 596 | -webkit-transition: all 0.125s ease-in-out; 597 | -moz-transition: all 0.125s ease-in-out; 598 | -ms-transition: all 0.125s ease-in-out; 599 | -o-transition: all 0.125s ease-in-out; 600 | transition: all 0.125s ease-in-out; 601 | -webkit-transform: translate3d(0,0,0); 602 | -moz-transform: translateX(0px); 603 | -ms-transform: translateX(0px); 604 | -o-transform: translateX(0px); 605 | transform: translateX(0px); 606 | } 607 | .switch.on > .thumb { 608 | -webkit-transform: translate3d(49px,0,0); 609 | -moz-transform: translateX(49px); 610 | -ms-transform: translateX(49px); 611 | -o-transform: translateX(49px); 612 | transform: translateX(49px); 613 | } 614 | .switch:hover > .thumb { 615 | box-shadow: inset 0 2px 1px #fff, inset 0 -2px 1px #fff; 616 | background-image: none; 617 | } 618 | .switch > .thumb:before { 619 | font-weight: bold; 620 | font-size: 14px; 621 | color: #fff; 622 | content: "On"; 623 | display: block; 624 | height: 14px; 625 | width: 14px; 626 | border: none; 627 | position: absolute; 628 | top: 3px; 629 | left: -30px; 630 | } 631 | .switch > .thumb:after { 632 | font-weight: bold; 633 | font-size: 14px; 634 | content: "Off"; 635 | display: block; 636 | height: 14px; 637 | width: 14px; 638 | position: absolute; 639 | right: -28px; 640 | top: 3px; 641 | } 642 | 643 | #telemetry_graph { 644 | display: none; 645 | } 646 | 647 | .nav .home { 648 | display: none; 649 | } 650 | 651 | @media only screen and (min-width: 900px) { 652 | } 653 | 654 | @media only screen and (max-width: 600px) { 655 | #app_name { 656 | left: 150px; 657 | } 658 | #locate-me { 659 | left: 120px; 660 | } 661 | header .search { 662 | width: 110px; 663 | } 664 | header .search form input[type='text'] { 665 | width: 63px; 666 | } 667 | } 668 | 669 | @media only screen and (max-width: 500px) { 670 | #app_name { 671 | left: 30px; 672 | } 673 | #locate-me { 674 | left: 0px; 675 | } 676 | header .search { 677 | display: none; 678 | } 679 | } 680 | 681 | @media only screen and (min-width: 481px) { 682 | .portrait { display: none; } 683 | .landscape { display: block; } 684 | #telemetry_graph { 685 | display: block; 686 | float: right; 687 | height: 200px; 688 | width: 280px; 689 | background: #fff; 690 | position: relative; 691 | z-index: 2; 692 | } 693 | #telemetry_graph .holder { 694 | border-left: 1px solid #ddd; 695 | } 696 | #telemetry_graph .graph_label { 697 | position: absolute; 698 | top: -26px; 699 | left: 0px; 700 | height: 20px; 701 | padding: 3px 5px; 702 | background: #00a3d3; 703 | z-index: 20; 704 | font-weight: bold; 705 | font-size: 11px; 706 | color: #fff; 707 | border-radius: 0px 5px 0 0; 708 | box-shadow: 1px -1px 5px rgba(0, 0, 0, 0.2); 709 | cursor: pointer; 710 | } 711 | #map { 712 | height: 245px; 713 | width: 280px; 714 | } 715 | #main { 716 | float: left; 717 | height: 245px; 718 | width: 199px; 719 | margin-top: 55px; 720 | box-shadow: -2px 0px 6px 0px #555; 721 | } 722 | #main .data { 723 | height: 100%; 724 | padding-bottom: 5px; 725 | } 726 | #main .data .left { 727 | float: left; 728 | width: 160px; 729 | padding-left: 5px; 730 | } 731 | #main .data dl > dt { 732 | color: #000; 733 | line-height: 11px; 734 | margin-top: 5px; 735 | font-weight: bold; 736 | font-size: 14px; 737 | } 738 | #main .data dl > dd { 739 | padding: 0; 740 | margin: 0; 741 | text-transform:uppercase; 742 | line-height: 11px; 743 | font-size: 11px; 744 | } 745 | #main .row .data img { 746 | right: 5%; 747 | top: 50px; 748 | } 749 | } 750 | @media only screen and (max-width: 480px) { 751 | .portrait { display: block; } 752 | .landscape { display: none; } 753 | #map{ 754 | height: 225px; 755 | } 756 | #main { 757 | height: 150px; 758 | } 759 | #main .data { 760 | min-height: 108px; 761 | } 762 | #main .data .left { 763 | float: left; 764 | width: 65%; 765 | padding-left: 5px; 766 | } 767 | #main .data .right { 768 | float: right; 769 | padding-right: 10px; 770 | width: 25%; 771 | } 772 | #main .data dl > dt { 773 | color: #000; 774 | line-height: 11px; 775 | margin-top: 7px; 776 | font-weight: bold; 777 | font-size: 14px; 778 | } 779 | #main .data dl > dd { 780 | padding: 0; 781 | margin: 0; 782 | text-transform:uppercase; 783 | line-height: 11px; 784 | font-size: 11px; 785 | } 786 | #main .row .header .graph { 787 | width: 180px; 788 | height: 40px; 789 | } 790 | #locate-me { 791 | display: none; 792 | } 793 | #app_name { 794 | left: 0px; 795 | } 796 | } 797 | 798 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | habhub tracker (high altitude balloons) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 | 45 | 61 | 62 | no
location
63 |
64 |
65 | 66 | 84 | 104 | 210 | 221 | 266 |
267 |
268 | 275 | 276 | 287 | 288 |
289 | 290 | 294 |
295 | 305 | 306 | 307 | 316 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | // detect if mobile 2 | var is_mobile = false; 3 | 4 | (function(a,b){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) is_mobile = true;})(navigator.userAgent||navigator.vendor||window.opera); 5 | 6 | // hash url 7 | var history_supported = (typeof history !== 'undefined'); 8 | 9 | function lhash_update(history_step) { 10 | history_step = !!history_step; 11 | 12 | var url = document.location.href.split("#",1)[0]; 13 | var hash = ""; 14 | 15 | // generate hash 16 | hash += "mt=" + map.getMapTypeId(); 17 | hash += "&mz=" + map.getZoom(); 18 | 19 | if(!/^[a-z0-9]{32}$/ig.exec(wvar.query)) { 20 | hash += "&qm=" + wvar.mode.replace(/ /g, '_'); 21 | } 22 | 23 | if(follow_vehicle === null || manual_pan) { 24 | var latlng = map.getCenter(); 25 | hash += "&mc=" + roundNumber(latlng.lat(), 5) + 26 | "," + roundNumber(latlng.lng(), 5); 27 | } 28 | 29 | if(follow_vehicle !== null) { 30 | hash += "&f=" + follow_vehicle; 31 | } 32 | 33 | if(wvar.query !== "") { 34 | hash += "&q=" + wvar.query; 35 | $("header .search input[type='text']").val(wvar.query); 36 | } 37 | 38 | // other vars 39 | if(wvar.nyan) { 40 | hash += "&nyan=1"; 41 | } 42 | 43 | hash = encodeURI(hash); 44 | // set state 45 | if(history_supported) { 46 | if(!history_step) { 47 | history.replaceState(null, null, url + "#!" + hash); 48 | } else { 49 | history.pushState(null, null, url + "#!" + hash); 50 | } 51 | } else { 52 | document.location.hash = "!" + hash; 53 | } 54 | } 55 | 56 | // wvar detection 57 | var wvar = { 58 | enabled: false, 59 | vlist: true, 60 | graph: true, 61 | graph_exapnded: false, 62 | focus: "", 63 | mode: (is_mobile) ? modeDefaultMobile : modeDefault, 64 | zoom: true, 65 | query: "!RS_*;", 66 | nyan: false, 67 | }; 68 | 69 | 70 | function load_hash(no_refresh) { 71 | no_refresh = (no_refresh === null); 72 | var hash = window.location.hash.slice(2); 73 | 74 | if(hash === "") return; 75 | 76 | var parms = hash.split('&'); 77 | var refresh = false; 78 | var refocus = false; 79 | 80 | // defaults 81 | manual_pan = false; 82 | 83 | var def = { 84 | mode: wvar.mode, 85 | zoom: true, 86 | focus: "", 87 | query: "", 88 | nyan: false, 89 | }; 90 | 91 | parms.forEach(function(v) { 92 | v = v.split('='); 93 | k = v[0]; 94 | v = decodeURIComponent(v[1]); 95 | 96 | switch(k) { 97 | case "mt": 98 | map.setMapTypeId(v); 99 | break; 100 | case "mz": 101 | map.setZoom(parseInt(v)); 102 | break; 103 | case "mc": 104 | def.zoom = false; 105 | manual_pan = true; 106 | v = v.split(','); 107 | var latlng = new google.maps.LatLng(v[0], v[1]); 108 | map.setCenter(latlng); 109 | break; 110 | case "f": 111 | refocus = (follow_vehicle != v); 112 | follow_vehicle = v; 113 | def.focus = v; 114 | break; 115 | case "qm": 116 | def.mode = v.replace(/_/g, ' '); 117 | if(modeList.indexOf(def.mode) == -1) def.mode = (is_mobile) ? modeDefaultMobile : modeDefault; 118 | break; 119 | case "q": 120 | def.query = v; 121 | $("header .search input[type='text']").val(v); 122 | break; 123 | case "nyan": 124 | def[k] = !!parseInt(v); 125 | break; 126 | } 127 | }); 128 | 129 | // check if we should force refresh 130 | ['mode','query','nyan'].forEach(function(k) { 131 | if(wvar[k] != def[k]) refresh = true; 132 | }); 133 | 134 | $.extend(true, wvar, def); 135 | 136 | // force refresh 137 | if(!no_refresh) { 138 | if(refresh) { 139 | zoomed_in = false; 140 | clean_refresh(wvar.mode, true); 141 | } 142 | else if(refocus) { 143 | $(".row.active").removeClass('active'); 144 | $(".vehicle"+vehicles[wvar.focus].uuid).addClass('active'); 145 | followVehicle(wvar.focus, manual_pan, true); 146 | } 147 | } 148 | 149 | lhash_update(); 150 | } 151 | window.onhashchange = load_hash; 152 | 153 | var params = window.location.search.substring(1).split('&'); 154 | 155 | for(var idx in params) { 156 | var line = params[idx].split('='); 157 | if(line.length < 2) continue; 158 | 159 | switch(line[0]) { 160 | case "embed": 161 | if(line[1] == "1") { 162 | wvar.enabled = true; 163 | if(!is_mobile) wvar.mode = 'All'; 164 | } 165 | break; 166 | case "hidelist": if(line[1] == "1") wvar.vlist = false; break; 167 | case "hidegraph": if(line[1] == "1") wvar.graph = false; break; 168 | case "expandgraph": if(line[1] == "1") wvar.graph_expanded = true; break; 169 | case "filter": 170 | wvar.query = decodeURIComponent(line[1]); 171 | $("header .search input[type='text']").val(wvar.query); 172 | break; 173 | case "nyan": wvar.nyan = true; break; 174 | case "focus": wvar.focus = decodeURIComponent(line[1]); break; 175 | case "docid": wvar.docid = line[1]; break; 176 | case "mode": wvar.mode = decodeURIComponent(line[1]); break; 177 | } 178 | } 179 | 180 | if(wvar.enabled) { 181 | //analytics 182 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Embed Opts', window.location.search]); 183 | } 184 | 185 | $.ajaxSetup({ cache: true }); 186 | 187 | var force_check_cache = false; 188 | 189 | // handle cachin events and display a loading bar 190 | var loadComplete = function(e) { 191 | clearTimeout(initTimer); 192 | 193 | if(e.type == 'updateready') { 194 | // swapCache may throw exception if the isn't a previous cache 195 | try { 196 | window.applicationCache.swapCache(); 197 | } catch(v) {} 198 | 199 | window.location.reload(); 200 | return; 201 | } 202 | 203 | $('#loading .complete').stop(true,true).animate({width: 200}, {complete: trackerInit }); 204 | }; 205 | 206 | var hysplit = {}; 207 | var hysplit_data = {}; 208 | var refresh_hysplit = function() { 209 | $.getJSON("//spacenear.us/tracker/datanew.php?type=hysplit&format=json", function(data) { 210 | var refresh = false; 211 | 212 | for(var k in data) { 213 | if(k in hysplit_data) { 214 | // if the jobid is the same, skip to next one 215 | if(hysplit_data[k].jobid == data[k].jobid) continue; 216 | 217 | // otherwise update the url 218 | hysplit_data[k] = data[k]; 219 | hysplit[k].setUrl(hysplit_data[k].url_kmz); 220 | } else { 221 | hysplit_data[k] = data[k]; 222 | hysplit[k] = new google.maps.KmlLayer({url: hysplit_data[k].url_kmz, preserveViewport:true }); 223 | refresh = true; 224 | } 225 | } 226 | 227 | if(refresh) refreshUI(); 228 | }); 229 | }; 230 | 231 | // loads the tracker interface 232 | function trackerInit() { 233 | $('#loading,#settingsbox,#aboutbox,#chasebox').hide(); // welcome screen 234 | $('header,#main').show(); // interface elements 235 | checkSize(); 236 | 237 | if(map) return; 238 | 239 | if(is_mobile || wvar.enabled) $(".nav .wvar").hide(); 240 | 241 | if(!is_mobile) { 242 | $.getScript("js/init_plot.js", function() { checkSize(); if(!map) load(); }); 243 | if(wvar.graph) $('#telemetry_graph').attr('style',''); 244 | 245 | // fetch hysplit jobs 246 | setInterval(refresh_hysplit, 60 * 1000); 247 | refresh_hysplit(); 248 | 249 | return; 250 | } 251 | if(!map) load(); 252 | } 253 | 254 | // if for some reason, applicationCache is not working, load the app after a 3s timeout 255 | var initTimer = setTimeout(trackerInit, 3000); 256 | 257 | var cache = window.applicationCache; 258 | cache.addEventListener('noupdate', loadComplete, false); 259 | cache.addEventListener('updateready', loadComplete, false); 260 | cache.addEventListener('cached', loadComplete, false); 261 | cache.addEventListener('error', loadComplete, false); 262 | 263 | // if the browser supports progress events, display a loading bar 264 | cache.addEventListener('checking', function() { if(map && !force_check_cache) return; force_check_cache = false; clearTimeout(initTimer); $('#loading .bar,#loading').show(); $('#loading .complete').css({width: 0}); }, false); 265 | cache.addEventListener('progress', function(e) { $('#loading .complete').stop(true,true).animate({width: (200/e.total)*e.loaded}); }, false); 266 | 267 | var listScroll; 268 | var GPS_ts = null; 269 | var GPS_lat = null; 270 | var GPS_lon = null; 271 | var GPS_alt = null; 272 | var GPS_speed = null; 273 | var CHASE_enabled = null; 274 | var CHASE_listenerSent = false; 275 | var CHASE_timer = 0; 276 | var callsign = ""; 277 | 278 | function checkSize() { 279 | // we are in landscape mode 280 | w = window.innerWidth; 281 | 282 | // this is hacky fix for off by 1px that makes the vechicle list disappear 283 | wrect = document.body.getBoundingClientRect(); 284 | // chrome seems to calculate the body bounding box differently from every other browser 285 | if (!!window.chrome) { 286 | w_fix = (w >= wrect.width) ? 1 : 0; 287 | } else { 288 | w_fix = (w === Math.floor(wrect.width)) ? 0 : 1; 289 | } 290 | 291 | w = (w < 320) ? 320 : w; // absolute minimum 320px 292 | h = window.innerHeight; 293 | //h = (h < 300) ? 300 : h; // absolute minimum 320px minus 20px for the iphone bar 294 | hh = $('header').height(); 295 | 296 | ph = 0; 297 | 298 | if(w > 900 && $('.flatpage:visible').length) { 299 | $('.flatpage').addClass('topanel'); 300 | ph = $('.flatpage:visible').width()+30; 301 | } else { 302 | $('.flatpage.topanel').removeClass('topanel'); 303 | } 304 | 305 | $("#mapscreen,.flatpage").height(h-hh-5); 306 | 307 | sw = (wvar.vlist) ? 200 : 0; 308 | 309 | $('.container').width(w-20); 310 | 311 | if($('.landscape:visible').length) { 312 | $('#main').height(h-hh-5); 313 | if($('#telemetry_graph .graph_label').hasClass('active')) { 314 | $('#map').height(h-hh-5-200); 315 | } else { 316 | $('#map').height(h-hh-5); 317 | } 318 | $('body,#loading').height(h); 319 | $('#mapscreen,#map,#telemetry_graph,#telemetry_graph .holder').width(w-sw-ph-w_fix); 320 | $('#main').width(sw); 321 | } else { // portrait mode 322 | //if(h < 420) h = 420; 323 | var mh = (wvar.vlist) ? 150 : 0; 324 | 325 | $('body,#loading').height(h); 326 | $('#map,#mapscreen').height(h-hh-5-mh); 327 | $('#map,#mapscreen').width(w); 328 | $('#main').height(mh); // 180px is just enough to hold one expanded vehicle 329 | $('#main').width(w); 330 | } 331 | 332 | // this should hide the address bar on mobile phones, when possible 333 | window.scrollTo(0,1); 334 | 335 | if(map) google.maps.event.trigger(map, 'resize'); 336 | } 337 | 338 | window.onresize = checkSize; 339 | window.onchangeorientation = checkSize; 340 | 341 | 342 | // functions 343 | 344 | function positionUpdateError(error) { 345 | switch(error.code) 346 | { 347 | case error.PERMISSION_DENIED: 348 | alert("no permission to use your location"); 349 | $('#sw_chasecar').click(); // turn off chase car 350 | break; 351 | default: 352 | break; 353 | } 354 | } 355 | 356 | var positionUpdateHandle = function(position) { 357 | if(CHASE_enabled && !CHASE_listenerSent) { 358 | if(offline.get('opt_station')) { 359 | ChaseCar.putListenerInfo(callsign); 360 | CHASE_listenerSent = true; 361 | } 362 | } 363 | 364 | //navigator.geolocation.getCurrentPosition(function(position) { 365 | var lat = position.coords.latitude; 366 | var lon = position.coords.longitude; 367 | var alt = (position.coords.altitude) ? position.coords.altitude : 0; 368 | var accuracy = (position.coords.accuracy) ? position.coords.accuracy : 0; 369 | var speed = (position.coords.speed) ? position.coords.speed : 0; 370 | 371 | // constantly update 'last updated' field, and display friendly time since last update 372 | if(!GPS_ts) { 373 | GPS_ts = parseInt(position.timestamp/1000); 374 | 375 | setInterval(function() { 376 | var delta_ts = parseInt(Date.now()/1000) - GPS_ts; 377 | 378 | // generate friendly timestamp 379 | var hours = Math.floor(delta_ts / 3600); 380 | var minutes = Math.floor(delta_ts / 60) % 60; 381 | var ts_str = (delta_ts >= 60) ? 382 | ((hours)?hours+'h ':'') + 383 | ((minutes)?minutes+'m':'') + 384 | ' ago' 385 | : 'just now'; 386 | $('#cc_timestamp').text(ts_str); 387 | }, 30000); 388 | 389 | $('#cc_timestamp').text('just now'); 390 | } 391 | 392 | // save position and update only if different is available 393 | if(CHASE_timer < (new Date()).getTime() && 394 | ( 395 | GPS_lat != lat || 396 | GPS_lon != lon || 397 | GPS_alt != alt || 398 | GPS_speed != speed 399 | ) 400 | ) 401 | { 402 | GPS_lat = lat; 403 | GPS_lon = lon; 404 | GPS_alt = alt; 405 | GPS_speed = speed; 406 | GPS_ts = parseInt(position.timestamp/1000); 407 | $('#cc_timestamp').text('just now'); 408 | 409 | // update look angles once we get position 410 | if(follow_vehicle !== null && vehicles[follow_vehicle] !== undefined) { 411 | update_lookangles(follow_vehicle); 412 | } 413 | 414 | if(CHASE_enabled) { 415 | ChaseCar.updatePosition(callsign, position); 416 | CHASE_timer = (new Date()).getTime() + 15000; 417 | 418 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']); 419 | } 420 | } 421 | else { return; } 422 | 423 | // add/update marker on the map (tracker.js) 424 | updateCurrentPosition(lat, lon); 425 | 426 | // round the coordinates 427 | lat = parseInt(lat * 10000)/10000; // 4 decimal places (11m accuracy at equator) 428 | lon = parseInt(lon * 10000)/10000; // 4 decimal places 429 | speed = parseInt(speed * 10)/10; // 1 decimal place 430 | accuracy = parseInt(accuracy); 431 | alt = parseInt(alt); 432 | 433 | // dispaly them in the top right corner 434 | $('#app_name b').html(lat + '
' + lon); 435 | 436 | // update chase car interface 437 | $('#cc_lat').text(lat); 438 | $('#cc_lon').text(lon); 439 | $('#cc_alt').text(alt + " m"); 440 | $('#cc_accuracy').text(accuracy + " m"); 441 | $('#cc_speed').text(speed + " m/s"); 442 | /* 443 | }, 444 | function() { 445 | // when there is no location 446 | $('#app_name b').html('mobile
tracker'); 447 | }); 448 | */ 449 | }; 450 | 451 | var twoZeroPad = function(n) { 452 | n = String(n); 453 | return (n.length<2) ? '0'+n : n; 454 | }; 455 | 456 | // updates timebox 457 | var updateTimebox = function(date) { 458 | var elm = $("#timebox"); 459 | var a,b,c,d,e,f,g,z; 460 | 461 | a = date.getUTCFullYear(); 462 | b = twoZeroPad(date.getUTCMonth()+1); // months 0-11 463 | c = twoZeroPad(date.getUTCDate()); 464 | e = twoZeroPad(date.getUTCHours()); 465 | f = twoZeroPad(date.getUTCMinutes()); 466 | g = twoZeroPad(date.getUTCSeconds()); 467 | 468 | elm.find(".current").text("UTC: "+a+'-'+b+'-'+c+' '+e+':'+f+':'+g); 469 | 470 | a = date.getFullYear(); 471 | b = twoZeroPad(date.getMonth()+1); // months 0-11 472 | c = twoZeroPad(date.getDate()); 473 | e = twoZeroPad(date.getHours()); 474 | f = twoZeroPad(date.getMinutes()); 475 | g = twoZeroPad(date.getSeconds()); 476 | z = date.getTimezoneOffset() / -60; 477 | 478 | elm.find(".local").text("Local: "+a+'-'+b+'-'+c+' '+e+':'+f+':'+g+" "+((z<0)?"-":"+")+z); 479 | }; 480 | 481 | var format_time_friendly = function(start, end) { 482 | var dt = Math.floor((end - start) / 1000); 483 | if(dt < 0) return null; 484 | 485 | if(dt < 60) return dt + 's'; 486 | else if(dt < 3600) return Math.floor(dt/60)+'m'; 487 | else if(dt < 86400) { 488 | dt = Math.floor(dt/60); 489 | return Math.floor(dt/60)+'h '+(dt % 60)+'m'; 490 | } else { 491 | dt = Math.floor(dt/3600); 492 | return Math.floor(dt/24)+'d '+(dt % 24)+'h'; 493 | } 494 | }; 495 | 496 | // runs every second 497 | var updateTime = function(date) { 498 | // update timebox 499 | var elm = $("#timebox.present"); 500 | if(elm.length > 0) updateTimebox(date); 501 | 502 | // update friendly delta time fields 503 | elm = $(".friendly-dtime"); 504 | if(elm.length > 0) { 505 | var now = new Date().getTime(); 506 | 507 | elm.each(function(k,v) { 508 | var e = $(v); 509 | if(e.attr('data-timestamp') === undefined) return; 510 | var ts = e.attr('data-timestamp'); 511 | var str = format_time_friendly(ts, now); 512 | if(str) e.text(str + ' ago'); 513 | }); 514 | } 515 | }; 516 | 517 | 518 | $(window).ready(function() { 519 | // refresh timebox 520 | setInterval(function() { 521 | updateTime(new Date()); 522 | }, 1000); 523 | 524 | // resize elements if needed 525 | checkSize(); 526 | 527 | // add inline scroll to vehicle list 528 | listScroll = new IScroll('#main', { 529 | hScrollbar: false, 530 | hScroll: false, 531 | snap: false, 532 | mouseWheel: true, 533 | scrollbars: true, 534 | scrollbarClass: 'scrollStyle', 535 | shrinkScrollbars: 'scale', 536 | tap: true, 537 | click: true, 538 | disableMouse: false, 539 | disableTouch: false, 540 | disablePointer: false, 541 | }); 542 | 543 | $('#telemetry_graph').on('click', '.graph_label', function() { 544 | var e = $(this), h; 545 | if(e.hasClass('active')) { 546 | e.removeClass('active'); 547 | h = $('#map').height() + $('#telemetry_graph').height(); 548 | 549 | plot_open = false; 550 | 551 | //analytics 552 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Collapse', 'Telemetry Graph']); 553 | } else { 554 | e.addClass('active'); 555 | h = $('#map').height() - $('#telemetry_graph').height(); 556 | 557 | plot_open = true; 558 | 559 | //analytics 560 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Expand', 'Telemetry Graph']); 561 | } 562 | $('#map').stop(null,null).animate({'height': h}, function() { 563 | if(map) google.maps.event.trigger(map, 'resize'); 564 | 565 | if(plot_open && 566 | follow_vehicle !== null && 567 | (follow_vehicle != graph_vehicle || vehicles[follow_vehicle].graph_data_updated)) updateGraph(follow_vehicle, true); 568 | }); 569 | }); 570 | 571 | // expand graph on startup, if nessary 572 | if(wvar.graph_expanded) $('#telemetry_graph .graph_label').click(); 573 | 574 | // hysplit button 575 | $("#main").on('click','.row .data .vbutton.hysplit', function(event) { 576 | event.stopPropagation(); 577 | 578 | var elm = $(this); 579 | var name = elm.attr('data-vcallsign'); 580 | 581 | if(elm.hasClass("active")) { 582 | elm.removeClass('active'); 583 | hysplit[name].setMap(null); 584 | } 585 | else { 586 | elm.addClass('active'); 587 | hysplit[name].setMap(map); 588 | } 589 | }); 590 | 591 | $("#main").on('click','.row .data .vbutton.path', function(event) { 592 | event.stopPropagation(); 593 | 594 | var elm = $(this); 595 | var name = elm.attr('data-vcallsign'); 596 | 597 | if(elm.hasClass("active")) { 598 | elm.removeClass('active'); 599 | set_polyline_visibility(name, false); 600 | } 601 | else { 602 | elm.addClass('active'); 603 | set_polyline_visibility(name, true); 604 | } 605 | }); 606 | 607 | // reset nite-overlay and timebox when mouse goes out of the graph box 608 | $("#telemetry_graph").on('mouseout','.holder', function() { 609 | if(plot_crosshair_locked) return; 610 | 611 | updateGraph(null, true); 612 | }); 613 | 614 | // hand cursor for dragging the vehicle list 615 | $("#main").on("mousedown", ".row", function () { 616 | $("#main").addClass("drag"); 617 | }); 618 | $("body").on("mouseup", function () { 619 | $("#main").removeClass("drag"); 620 | }); 621 | 622 | // confirm dialog when launchnig a native map app with coordinates 623 | //$('#main').on('click', '#launch_mapapp', function() { 624 | // var answer = confirm("Launch your maps app?"); 625 | 626 | // //analytics 627 | // if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', ((answer)?"Yes":"No"), 'Coord Click']); 628 | 629 | // return answer; 630 | //}); 631 | 632 | // follow vehicle by clicking on data 633 | $('#main').on('click', '.row .data', function() { 634 | var e = $(this).parent(); 635 | 636 | followVehicle(e.attr('data-vcallsign')); 637 | }); 638 | 639 | // expand/collapse data when header is clicked 640 | $('#main').on('click', '.row .header', function() { 641 | var e = $(this).parent(); 642 | if(e.hasClass('active')) { 643 | // collapse data for selected vehicle 644 | e.removeClass('active'); 645 | e.find('.data').hide(); 646 | 647 | listScroll.refresh(); 648 | 649 | // disable following only we are collapsing the followed vehicle 650 | if(follow_vehicle !== null && follow_vehicle == e.attr('data-vcallsign')) { 651 | stopFollow(); 652 | } 653 | } else { 654 | // expand data for selected vehicle 655 | e.addClass('active'); 656 | e.find('.data').show(); 657 | 658 | listScroll.refresh(); 659 | 660 | // auto scroll when expanding an item 661 | if($('.portrait:visible').length) { 662 | var eName = "." + e.parent().attr('class') + " ." + e.attr('class').match(/vehicle\d+/)[0]; 663 | listScroll.scrollToElement(eName); 664 | } 665 | 666 | // pan to selected vehicle 667 | followVehicle(e.attr('data-vcallsign')); 668 | } 669 | }); 670 | 671 | // menu interface options 672 | $('.nav') 673 | .on('click', 'li', function() { 674 | var e = $(this); 675 | var name = e.attr('class').replace(" last",""); 676 | 677 | // makes the menu buttons act like a switch 678 | if($("#"+name+"box").is(':visible')) name = 'home'; 679 | 680 | var box = $("#"+name+"box"); 681 | 682 | if(box.is(':hidden')) { 683 | $('.flatpage, #homebox').hide(); 684 | box.show().scrollTop(0); 685 | 686 | if(name == 'about' && !$('#motd').hasClass('inited')) { 687 | $('#motd').addClass('inited'); 688 | 689 | $.getJSON("//spacenear.us/tracker/datanew.php?type=info", function(data) { 690 | if('html' in data) $('#motd').html(data.html.replace(/\\/g,'')); 691 | }); 692 | 693 | var iframe = box.find('iframe'); 694 | var src = iframe.attr('data-src'); 695 | iframe.attr('src', src); 696 | } 697 | 698 | // analytics 699 | var pretty_name; 700 | switch(name) { 701 | case "home": pretty_name = "Map"; break; 702 | case "chasecar": pretty_name = "Chase Car"; break; 703 | default: pretty_name = name[0].toUpperCase() + name.slice(1); 704 | } 705 | 706 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI Menubar', 'Open Page', pretty_name]); 707 | } 708 | checkSize(); 709 | }); 710 | 711 | // toggle functionality for switch button 712 | $("#sw_chasecar").click(function() { 713 | var e = $(this); 714 | var field = $('#cc_callsign'); 715 | 716 | // turning the switch off 717 | if(e.hasClass('on')) { 718 | field.removeAttr('disabled'); 719 | e.removeClass('on').addClass('off'); 720 | 721 | if(navigator.geolocation) navigator.geolocation.clearWatch(CHASE_enabled); 722 | CHASE_enabled = null; 723 | //CHASE_enabled = false; 724 | 725 | // blue man reappers :) 726 | if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(true); 727 | 728 | // analytics 729 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', 'Chase Car']); 730 | // turning the switch on 731 | } else { 732 | if(callsign.length < 5) { alert('Please enter a valid callsign, at least 5 characters'); return; } 733 | if(!callsign.match(/^[a-zA-Z0-9\_\-]+$/)) { alert('Invalid characters in callsign (use only a-z,0-9,-,_)'); return; } 734 | 735 | field.attr('disabled','disabled'); 736 | e.removeClass('off').addClass('on'); 737 | 738 | // push listener doc to habitat 739 | // this gets a station on the map, under the car marker 740 | // im still not sure its nessesary 741 | if(!CHASE_listenerSent) { 742 | if(offline.get('opt_station')) { 743 | ChaseCar.putListenerInfo(callsign); 744 | CHASE_listenerSent = true; 745 | } 746 | } 747 | // if already have a position push it to habitat 748 | if(GPS_ts) { 749 | ChaseCar.updatePosition(callsign, { coords: { latitude: GPS_lat, longitude: GPS_lon, altitude: GPS_alt, speed: GPS_speed }}); 750 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']); 751 | } 752 | 753 | if(navigator.geolocation) CHASE_enabled = navigator.geolocation.watchPosition(positionUpdateHandle, positionUpdateError); 754 | //CHASE_enabled = true; 755 | 756 | // hide the blue man 757 | if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(false); 758 | 759 | // analytics 760 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', 'Chase Car']); 761 | } 762 | }); 763 | 764 | // remember callsign as a cookie 765 | $("#cc_callsign").on('change keyup', function() { 766 | callsign = $(this).val().trim(); 767 | offline.set('callsign', callsign); // put in localStorage 768 | CHASE_listenerSent = false; 769 | }); 770 | 771 | // load value from localStorage 772 | callsign = offline.get('callsign'); 773 | $('#cc_callsign').val(callsign); 774 | 775 | // settings page 776 | 777 | // list of all switches 778 | var opts = [ 779 | "#sw_layers_aprs", 780 | "#sw_offline", 781 | "#sw_station", 782 | "#sw_imperial", 783 | "#sw_haxis_hours", 784 | "#sw_daylight", 785 | "#sw_hide_receivers", 786 | "#sw_hide_timebox", 787 | "#sw_hilight_vehicle", 788 | "#sw_nowelcome", 789 | "#sw_interpolate", 790 | ]; 791 | 792 | // applies functionality when switches are toggled 793 | $(opts.join(',')).click(function() { 794 | var e = $(this); 795 | var name = e.attr('id').replace('sw', 'opt'); 796 | var on; 797 | 798 | if(e.hasClass('on')) { 799 | e.removeClass('on').addClass('off'); 800 | on = 0; 801 | 802 | //analytics 803 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', name]); 804 | } else { 805 | e.removeClass('off').addClass('on'); 806 | on = 1; 807 | 808 | //analytics 809 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', name]); 810 | } 811 | 812 | // remember choice 813 | offline.set(name, on); 814 | 815 | // execute functionality 816 | switch(name) { 817 | case "opt_hilight_vehicle": 818 | if(on) focusVehicle(follow_vehicle); 819 | else focusVehicle(null, true); 820 | break; 821 | case "opt_imperial": 822 | case "opt_haxis_hours": 823 | refreshUI(); 824 | break; 825 | case "opt_daylight": 826 | if(on) { nite.show(); } 827 | else { nite.hide(); } 828 | break; 829 | case "opt_hide_receivers": 830 | if(on) { 831 | updateReceivers([]); 832 | clearTimeout(periodical_listeners); 833 | } 834 | else { 835 | refreshReceivers(); 836 | } 837 | break; 838 | case "opt_hide_timebox": 839 | var elm = $("#timebox"); 840 | if(on) { 841 | elm.removeClass('past').removeClass('present').hide(); 842 | $('#lookanglesbox').css({top:'7px'}); 843 | } else { 844 | elm.addClass('present').show(); 845 | $('#lookanglesbox').css({top:'40px'}); 846 | } 847 | break; 848 | case "opt_layers_aprs": 849 | if(on) map.overlayMapTypes.setAt("1", overlayAPRS); 850 | else map.overlayMapTypes.setAt("1", null); 851 | break; 852 | case "opt_interpolate": 853 | if(on) { graph_gap_size = graph_gap_size_max; } 854 | else { graph_gap_size = graph_gap_size_default; } 855 | clean_refresh(wvar.mode, true, false); 856 | break; 857 | } 858 | }); 859 | 860 | // set the switch, based on the remembered choice 861 | for(var k in opts) { 862 | var switch_id = opts[k]; 863 | var opt_name = switch_id.replace("#sw_", "opt_"); 864 | 865 | if(offline.get(opt_name)) $(switch_id).removeClass('off').addClass('on'); 866 | } 867 | 868 | // force re-cache 869 | $('#sw_cache').click(function() { 870 | var e = $(this).removeClass('off').addClass('on'); 871 | if(confirm("The app will automatically reload, if new version is available.")) { 872 | force_check_cache = true; 873 | 874 | try { 875 | applicationCache.update(); 876 | } catch (v) { 877 | force_check_cache = false; 878 | alert("There is no applicationCache available"); 879 | } 880 | } 881 | e.removeClass('on').addClass('off'); 882 | }); 883 | 884 | // We are able to get GPS position on idevices, if the user allows 885 | // The position is displayed in top right corner of the screen 886 | // This should be very handly for in the field tracking 887 | //setTimeout(function() {updateCurrentPosition(50.27533, 3.335166);}, 5000); 888 | if(navigator.geolocation) { 889 | // if we have geolocation services, show the locate me button 890 | // the button pants the map to the user current location 891 | if(is_mobile && !wvar.enabled) $(".chasecar").show(); 892 | $("#locate-me,#app_name").attr('style','').click(function() { 893 | if(map && currentPosition) { 894 | // disable following of vehicles 895 | stopFollow(); 896 | // open map 897 | $('.nav .home').click(); 898 | // pan map to our current location 899 | map.panTo(new google.maps.LatLng(currentPosition.lat, currentPosition.lon)); 900 | 901 | //analytics 902 | if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Locate me']); 903 | } else { 904 | alert("No position available"); 905 | } 906 | }); 907 | 908 | navigator.geolocation.getCurrentPosition(positionUpdateHandle); 909 | // check for location update every 30sec 910 | //setInterval(positionUpdateHandle, 30000); 911 | // immediatelly check for position 912 | //positionUpdateHandle(); 913 | } 914 | 915 | // weather feature 916 | 917 | // list of overlays 918 | var overlayList = [ 919 | ['Global', [ 920 | ['google-radar','Google Earth Radar'], 921 | ['nrl-global-cloudtop','NRL Monterey Cloudtop'], 922 | ['nrl-global-ir','NRL Monterey IR'], 923 | ['nrl-global-vapor','NRL Monterey Vapor'] 924 | ]], 925 | ['Europe/Africa', [ 926 | ['meteosat-Odeg-MPE', 'METEOSAT Precip. Estimate'] 927 | ]], 928 | ['Indian Ocean', [ 929 | ['meteosat-iodc-MPE', 'METEOSAT IODC Precip. Est.'] 930 | ]], 931 | ['North America', [ 932 | ['nexrad-n0q-900913', 'NEXRAD Base Reflectivity'], 933 | ['goes-ir-4km-900913', 'GOES NA Infrared ~4km'], 934 | ['goes-wv-4km-900913', 'GOES NA Water Vapor ~4km'], 935 | ['goes-vis-1km-900913', 'GOES NA Visible ~1km'], 936 | ['goes-east-ir-4km-900913', 'GOES East CONUS Infrared'], 937 | ['goes-east-wv-4km-900913', 'GOES East CONUS Water Vapor'], 938 | ['goes-east-vis-1km-900913', 'GOES East CONUS Visible'], 939 | ['goes-west-ir-4km-900913', 'GOES West CONUS Infrared'], 940 | ['goes-west-wv-4km-900913', 'GOES West CONUS Water Vapor'], 941 | ['goes-west-vis-1km-900913', 'GOES West CONUS Visible'], 942 | ['hawaii-vis-900913', 'GOES West Hawaii Visible'], 943 | ['alaska-vis-900913', 'GOES West Alaska Visible'], 944 | ['alaska-ir-900913', 'GOES West Alaska IR'], 945 | ['alaska-wv-900913', 'GOES West Alaska Water Vapor'], 946 | ['q2-n1p-900913', 'Q2 1 Hour Precipitation'], 947 | ['q2-p24h-900913', 'Q2 24 Hour Precipitation'], 948 | ['q2-p48h-900913', 'Q2 48 Hour Precipitation'], 949 | ['q2-p72h-900913', 'Q2 72 Hour Precipitation'], 950 | ['q2-hsr-900913', 'MRMS Hybrid-Scan Reflectivity Composite.'] 951 | ]] 952 | ]; 953 | 954 | // generate the list of switches for each overlay 955 | var elm = $("#weatherbox .slimContainer"); 956 | var j; 957 | for(j in overlayList) { 958 | var region = overlayList[j][0]; 959 | var switches = overlayList[j][1]; 960 | 961 | elm.append("

"+region+"


"); 962 | 963 | var i; 964 | for(i in switches) { 965 | var id = switches[i][0]; 966 | var name = switches[i][1]; 967 | 968 | var html = '
' + 969 | ''+name+'' + 970 | '
' + 971 | '' + 972 | '' + 973 | '
' + 974 | '
'; 975 | 976 | elm.append(html); 977 | } 978 | } 979 | 980 | // the magic that makes the switches do things 981 | elm.find(".switch").click(function() { 982 | var e = $(this); 983 | var name = e.attr('id').replace('sw', 'opt'); 984 | var id = name.replace("opt_weather_",""); 985 | var on; 986 | 987 | if(e.hasClass('on')) { 988 | e.removeClass('on').addClass('off'); 989 | on = 0; 990 | } else { 991 | // only one overlay at a time 992 | $("#weatherbox .switch").removeClass('on').addClass('off'); 993 | e.removeClass('off').addClass('on'); 994 | on = 1; 995 | } 996 | 997 | weatherImageOverlay.setMap(null); 998 | weatherGoogleRadar.setMap(null); 999 | map.overlayMapTypes.setAt("0", null); 1000 | 1001 | if(on) { 1002 | if(id == "google-radar") { 1003 | weatherGoogleRadar.setMap(map); 1004 | return; 1005 | } else if(id in weatherImageOverlayList) { 1006 | var o = weatherImageOverlayList[id]; 1007 | var sw = new google.maps.LatLng(o[1][0][0], o[1][0][1]); 1008 | var ne = new google.maps.LatLng(o[1][1][0], o[1][1][1]); 1009 | var bounds = new google.maps.LatLngBounds(sw, ne); 1010 | weatherImageOverlay = new google.maps.GroundOverlay(o[0], bounds, {opacity: 0.7}); 1011 | weatherImageOverlay.setMap(map); 1012 | return; 1013 | } 1014 | 1015 | weatherOverlayId = id; 1016 | map.overlayMapTypes.setAt("0", weatherOverlay); 1017 | } 1018 | }); 1019 | 1020 | $("header .search form").on('submit', function(e) { 1021 | e.preventDefault(); 1022 | 1023 | var text = $("header .search input[type='text']").val(); 1024 | 1025 | if(text === wvar.query) return; 1026 | 1027 | // when running an empty search, it's probably best to reset the query mode 1028 | 1029 | wvar.query = text; 1030 | stopFollow(); 1031 | zoomed_in = false; 1032 | wvar.zoom = true; 1033 | 1034 | if(text === "") { wvar.mode = null; } 1035 | clean_refresh(wvar.mode, true, true); 1036 | }); 1037 | }); 1038 | --------------------------------------------------------------------------------