├── EclipseMapping.js ├── README.md ├── ServiceWorker.js ├── TimeZoneMap.js ├── UI.js ├── WebGLEarth ├── astro.js ├── backgroundworker.js ├── eclipse.js ├── images │ ├── bwmap.png │ ├── circle site.png │ ├── map.png │ ├── satellite.png │ └── topo.png ├── overlay.js ├── test.html └── viewer.html ├── alarm.wav ├── astro.js ├── backgroundworker.js ├── eclipse.js ├── eclipsemap.webmanifest ├── images ├── bwmap.png ├── circle site.png ├── icon-32.png ├── icon-512.png ├── map.png ├── satellite.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png └── topo.png ├── overlay.js ├── todo.txt ├── tzmap.png └── viewer.html /EclipseMapping.js: -------------------------------------------------------------------------------- 1 | /* 2 | Greg Miller gmiller@gregmiller.net 2022 3 | https://www.celestialprogramming.com/ 4 | Released as public domain 5 | */ 6 | 7 | import {getElementCoeffs,computeOutlineCurve} from "./eclipse.js"; 8 | import {getPenumbraBeginAndEndInfo,getGreatestEclipse,getRiseSetCurves, computeCentralLatLonForTime, getMaxEclipseAtRiseSetPoints, getExtremePoints, getTotalityLimitsByLongitudeList, getPartialLimitsByLogitudeList} from "./eclipse.js"; 9 | 10 | let map; 11 | let umbraLine=null; 12 | let penumbraLine=null; 13 | 14 | function drawRiseSetCurves(){ 15 | const lists=getRiseSetCurves(); 16 | const setting=createPolyLineFromLatLonList(lists.setting); 17 | const rising=createPolyLineFromLatLonList(lists.rising); 18 | setting.addTo(map); 19 | rising.addTo(map); 20 | return lists; 21 | } 22 | 23 | function computeGreatestEclipse(){ 24 | const ge=getGreatestEclipse(getElementCoeffs()); 25 | let p=new L.LatLng(ge.lat,ge.lon); 26 | let m=L.circleMarker(p,{radius: 5}); 27 | m.addTo(map); 28 | } 29 | 30 | function computePenumbraExtremeInfo(){ 31 | const pi=getPenumbraBeginAndEndInfo(getElementCoeffs()); 32 | let p=new L.LatLng(pi.start.lat,pi.start.lon); 33 | let m=L.circleMarker(p,{radius: 1}); 34 | m.addTo(map); 35 | 36 | p=new L.LatLng(pi.end.lat,pi.end.lon); 37 | m=L.circleMarker(p,{radius: 1}); 38 | m.addTo(map); 39 | return pi; 40 | } 41 | 42 | export function computeEclipseData(map1,workerMessage){ 43 | map=map1; 44 | const e=getElementCoeffs(); 45 | const extremes=getExtremePoints(e); 46 | const begin=e.T0+extremes.begin.t-e.Δt/60/60; 47 | const end=e.T0+extremes.end.t-e.Δt/60/60; 48 | 49 | const centralCurve=computeCentralCurve(e,begin,end); 50 | 51 | const startLon=extremes.begin.λ; 52 | const endLon=extremes.end.λ; 53 | 54 | const limits=drawRiseSetCurves(); 55 | 56 | const north=new Array(); 57 | getTotalityLimitsByLongitudeList(e,1,startLon,endLon)[0].forEach(el => {if(el!=null) north.push(new L.LatLng(el.lat,el.lon))}); 58 | 59 | const south=new Array(); 60 | getTotalityLimitsByLongitudeList(e,-1,startLon,endLon)[0].forEach(el => {if(el!=null) south.push(new L.LatLng(el.lat,el.lon))}); 61 | 62 | const northP=new Array(); 63 | getPartialLimitsByLogitudeList(e,1,limits.nStart,limits.nEnd)[0].forEach(el => {if(el!=null) northP.push(new L.LatLng(el.lat,el.lon))}); 64 | 65 | const southP=new Array(); 66 | getPartialLimitsByLogitudeList(e,-1,limits.sStart,limits.sEnd)[0].forEach(el => {if(el!=null) southP.push(new L.LatLng(el.lat,el.lon))}); 67 | 68 | computeGreatestEclipse(); 69 | const pi=computePenumbraExtremeInfo(); 70 | 71 | 72 | let eclipseData={}; 73 | eclipseData.e=e; 74 | eclipseData.extremes=extremes; 75 | eclipseData.beginTime=pi.start.time; 76 | eclipseData.endTime=pi.end.time; 77 | eclipseData.centralCurve=centralCurve; 78 | eclipseData.northTotalityLimits=north; 79 | eclipseData.southTotalityLimits=south; 80 | eclipseData.northPartialLimits=northP; 81 | eclipseData.southPartialLimits=southP; 82 | //eclipseData.centerPoint=centerPoint; 83 | eclipseData.durationLines=null; 84 | 85 | const backgroundWorker=new Worker("backgroundworker.js",{type: "module"}); 86 | backgroundWorker.addEventListener("message",workerMessage); 87 | backgroundWorker.postMessage(eclipseData); 88 | 89 | return eclipseData; 90 | } 91 | 92 | function createPolyLineFromLatLonList(list){ 93 | const t=new Array(); 94 | for(let i=0;i0) t.push(t[0]); 100 | const line=new L.Polyline(t, {color: 'black', weight: 1}); 101 | return line; 102 | } 103 | 104 | function createPolygonFromLatLonList(list,color){ 105 | const t=new Array(); 106 | for(let i=0;i0) t.push(t[0]); 112 | const line=new L.polygon(t, {color: color, weight: 0}); 113 | return line; 114 | } 115 | 116 | export function displayShadowOutlines(hour){ 117 | if(umbraLine) map.removeLayer(umbraLine); 118 | if(penumbraLine) map.removeLayer(penumbraLine); 119 | const curves=computeOutlineCurve(hour); 120 | umbraLine=createPolygonFromLatLonList(curves.umbra,"#000000ff"); 121 | umbraLine.addTo(map); 122 | penumbraLine=createPolygonFromLatLonList(curves.penumbra,'#000000aa'); 123 | penumbraLine.addTo(map); 124 | } 125 | 126 | function computeCentralCurve(e,begin,end){ 127 | 128 | let a=new Array(); 129 | let i=begin; 130 | while(i<=end+.1){ 131 | const p=computeCentralLatLonForTime(e,i); 132 | if(p!=null && p.lat!=null && p.lon!=null && !isNaN(p.lat) && !isNaN(p.lon)){ 133 | a.push(new L.LatLng(p.lat,p.lon)); 134 | } 135 | i+=.0001; 136 | } 137 | return a; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is under active development, and will have some crap laying around that might not 2 | be used, and will generally be a bit rough around the edges. 3 | 4 | The main file is viewer.html, and on line example is available: https://www.celestialprogramming.com/apps/SolarEclipseViewer/viewer.html 5 | 6 | The code is public domain. 7 | 8 | [Screenshots are slightly old and don't show all features] 9 | Screenshots: 10 | ![screenshot1](https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/main/images/screenshot1.png) 11 | ![screenshot2](https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/main/images/screenshot2.png) 12 | ![screenshot2](https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/main/images/screenshot3.png) 13 | -------------------------------------------------------------------------------- /ServiceWorker.js: -------------------------------------------------------------------------------- 1 | 2 | self.addEventListener('install', () => { 3 | console.log(`installing service worker`); 4 | }) 5 | 6 | self.addEventListener('activate', () => { 7 | console.log(`activating service worker`); 8 | }) 9 | 10 | self.addEventListener('fetch', event => { 11 | //console.log(`fetching... ${event.request.url}`); 12 | }) -------------------------------------------------------------------------------- /TimeZoneMap.js: -------------------------------------------------------------------------------- 1 | /* 2 | gmiller@gregmiller.net 2024 3 | https://www.celestialprogramming.com/ 4 | Released as public domain 5 | 6 | Converts time zone to UTC offset using an image generated from the 7 | shapefiles from https://github.com/evansiroky/timezone-boundary-builder 8 | 9 | Only accurate to about 6 miles at the equator, and offsets are valid 10 | only for April 8 2024. 11 | */ 12 | 13 | const tzoffsetlist={"Africa/Abidjan": 0, "Africa/Accra": 0, "Africa/Addis_Ababa": 10800, "Africa/Algiers": 3600, "Africa/Asmara": 10800, "Africa/Bamako": 0, "Africa/Bangui": 3600, "Africa/Banjul": 0, "Africa/Bissau": 0, "Africa/Blantyre": 7200, "Africa/Brazzaville": 3600, "Africa/Bujumbura": 7200, "Africa/Cairo": 7200, "Africa/Casablanca": 0, "Africa/Ceuta": 7200, "Africa/Conakry": 0, "Africa/Dakar": 0, "Africa/Dar_es_Salaam": 10800, "Africa/Djibouti": 10800, "Africa/Douala": 3600, "Africa/El_Aaiun": 0, "Africa/Freetown": 0, "Africa/Gaborone": 7200, "Africa/Harare": 7200, "Africa/Johannesburg": 7200, "Africa/Juba": 7200, "Africa/Kampala": 10800, "Africa/Khartoum": 7200, "Africa/Kigali": 7200, "Africa/Kinshasa": 3600, "Africa/Lagos": 3600, "Africa/Libreville": 3600, "Africa/Lome": 0, "Africa/Luanda": 3600, "Africa/Lubumbashi": 7200, "Africa/Lusaka": 7200, "Africa/Malabo": 3600, "Africa/Maputo": 7200, "Africa/Maseru": 7200, "Africa/Mbabane": 7200, "Africa/Mogadishu": 10800, "Africa/Monrovia": 0, "Africa/Nairobi": 10800, "Africa/Ndjamena": 3600, "Africa/Niamey": 3600, "Africa/Nouakchott": 0, "Africa/Ouagadougou": 0, "Africa/Porto-Novo": 3600, "Africa/Sao_Tome": 0, "Africa/Tripoli": 7200, "Africa/Tunis": 3600, "Africa/Windhoek": 7200, "America/Adak": -32400, "America/Anchorage": -28800, "America/Anguilla": -14400, "America/Antigua": -14400, "America/Araguaina": -10800, "America/Argentina/Buenos_Aires": -10800, "America/Argentina/Catamarca": -10800, "America/Argentina/Cordoba": -10800, "America/Argentina/Jujuy": -10800, "America/Argentina/La_Rioja": -10800, "America/Argentina/Mendoza": -10800, "America/Argentina/Rio_Gallegos": -10800, "America/Argentina/Salta": -10800, "America/Argentina/San_Juan": -10800, "America/Argentina/San_Luis": -10800, "America/Argentina/Tucuman": -10800, "America/Argentina/Ushuaia": -10800, "America/Aruba": -14400, "America/Asuncion": -14400, "America/Atikokan": -18000, "America/Bahia_Banderas": -21600, "America/Bahia": -10800, "America/Barbados": -14400, "America/Belem": -10800, "America/Belize": -21600, "America/Blanc-Sablon": -14400, "America/Boa_Vista": -14400, "America/Bogota": -18000, "America/Boise": -21600, "America/Cambridge_Bay": -21600, "America/Campo_Grande": -14400, "America/Cancun": -18000, "America/Caracas": -14400, "America/Cayenne": -10800, "America/Cayman": -18000, "America/Chicago": -18000, "America/Chihuahua": -21600, "America/Ciudad_Juarez": -21600, "America/Costa_Rica": -21600, "America/Creston": -25200, "America/Cuiaba": -14400, "America/Curacao": -14400, "America/Danmarkshavn": 0, "America/Dawson": -25200, "America/Dawson_Creek": -25200, "America/Denver": -21600, "America/Detroit": -14400, "America/Dominica": -14400, "America/Edmonton": -21600, "America/Eirunepe": -18000, "America/El_Salvador": -21600, "America/Fortaleza": -10800, "America/Fort_Nelson": -25200, "America/Glace_Bay": -10800, "America/Goose_Bay": -10800, "America/Grand_Turk": -14400, "America/Grenada": -14400, "America/Guadeloupe": -14400, "America/Guatemala": -21600, "America/Guayaquil": -18000, "America/Guyana": -14400, "America/Halifax": -10800, "America/Havana": -14400, "America/Hermosillo": -25200, "America/Indiana/Indianapolis": -14400, "America/Indiana/Knox": -18000, "America/Indiana/Marengo": -14400, "America/Indiana/Petersburg": -14400, "America/Indiana/Tell_City": -18000, "America/Indiana/Vevay": -14400, "America/Indiana/Vincennes": -14400, "America/Indiana/Winamac": -14400, "America/Inuvik": -21600, "America/Iqaluit": -14400, "America/Jamaica": -18000, "America/Juneau": -28800, "America/Kentucky/Louisville": -14400, "America/Kentucky/Monticello": -14400, "America/Kralendijk": -14400, "America/La_Paz": -14400, "America/Lima": -18000, "America/Los_Angeles": -25200, "America/Lower_Princes": -14400, "America/Maceio": -10800, "America/Managua": -21600, "America/Manaus": -14400, "America/Marigot": -14400, "America/Martinique": -14400, "America/Matamoros": -18000, "America/Mazatlan": -25200, "America/Menominee": -18000, "America/Merida": -21600, "America/Metlakatla": -28800, "America/Mexico_City": -21600, "America/Miquelon": -7200, "America/Moncton": -10800, "America/Monterrey": -21600, "America/Montevideo": -10800, "America/Montserrat": -14400, "America/Nassau": -14400, "America/New_York": -14400, "America/Nome": -28800, "America/Noronha": -7200, "America/North_Dakota/Beulah": -18000, "America/North_Dakota/Center": -18000, "America/North_Dakota/New_Salem": -18000, "America/Nuuk": -3600, "America/Ojinaga": -18000, "America/Panama": -18000, "America/Paramaribo": -10800, "America/Phoenix": -25200, "America/Port-au-Prince": -14400, "America/Port_of_Spain": -14400, "America/Porto_Velho": -14400, "America/Puerto_Rico": -14400, "America/Punta_Arenas": -10800, "America/Rankin_Inlet": -18000, "America/Recife": -10800, "America/Regina": -21600, "America/Resolute": -18000, "America/Rio_Branco": -18000, "America/Santarem": -10800, "America/Santiago": -14400, "America/Santo_Domingo": -14400, "America/Sao_Paulo": -10800, "America/Scoresbysund": -3600, "America/Sitka": -28800, "America/St_Barthelemy": -14400, "America/St_Johns": -9000, "America/St_Kitts": -14400, "America/St_Lucia": -14400, "America/St_Thomas": -14400, "America/St_Vincent": -14400, "America/Swift_Current": -21600, "America/Tegucigalpa": -21600, "America/Thule": -10800, "America/Tijuana": -25200, "America/Toronto": -14400, "America/Tortola": -14400, "America/Vancouver": -25200, "America/Whitehorse": -25200, "America/Winnipeg": -18000, "America/Yakutat": -28800, "Antarctica/Casey": 28800, "Antarctica/Davis": 25200, "Antarctica/DumontDUrville": 36000, "Antarctica/Macquarie": 36000, "Antarctica/Mawson": 18000, "Antarctica/McMurdo": 43200, "Antarctica/Palmer": -10800, "Antarctica/Rothera": -10800, "Antarctica/Syowa": 10800, "Antarctica/Troll": 7200, "Antarctica/Vostok": 18000, "Arctic/Longyearbyen": 7200, "Asia/Aden": 10800, "Asia/Almaty": 18000, "Asia/Amman": 10800, "Asia/Anadyr": 43200, "Asia/Aqtau": 18000, "Asia/Aqtobe": 18000, "Asia/Ashgabat": 18000, "Asia/Atyrau": 18000, "Asia/Baghdad": 10800, "Asia/Bahrain": 10800, "Asia/Baku": 14400, "Asia/Bangkok": 25200, "Asia/Barnaul": 25200, "Asia/Beirut": 10800, "Asia/Bishkek": 21600, "Asia/Brunei": 28800, "Asia/Chita": 32400, "Asia/Choibalsan": 28800, "Asia/Colombo": 19800, "Asia/Damascus": 10800, "Asia/Dhaka": 21600, "Asia/Dili": 32400, "Asia/Dubai": 14400, "Asia/Dushanbe": 18000, "Asia/Famagusta": 10800, "Asia/Gaza": 7200, "Asia/Hebron": 7200, "Asia/Ho_Chi_Minh": 25200, "Asia/Hong_Kong": 28800, "Asia/Hovd": 25200, "Asia/Irkutsk": 28800, "Asia/Jakarta": 25200, "Asia/Jayapura": 32400, "Asia/Jerusalem": 10800, "Asia/Kabul": 16200, "Asia/Kamchatka": 43200, "Asia/Karachi": 18000, "Asia/Kathmandu": 20700, "Asia/Khandyga": 32400, "Asia/Kolkata": 19800, "Asia/Krasnoyarsk": 25200, "Asia/Kuala_Lumpur": 28800, "Asia/Kuching": 28800, "Asia/Kuwait": 10800, "Asia/Macau": 28800, "Asia/Magadan": 39600, "Asia/Makassar": 28800, "Asia/Manila": 28800, "Asia/Muscat": 14400, "Asia/Nicosia": 10800, "Asia/Novokuznetsk": 25200, "Asia/Novosibirsk": 25200, "Asia/Omsk": 21600, "Asia/Oral": 18000, "Asia/Phnom_Penh": 25200, "Asia/Pontianak": 25200, "Asia/Pyongyang": 32400, "Asia/Qatar": 10800, "Asia/Qostanay": 18000, "Asia/Qyzylorda": 18000, "Asia/Riyadh": 10800, "Asia/Sakhalin": 39600, "Asia/Samarkand": 18000, "Asia/Seoul": 32400, "Asia/Shanghai": 28800, "Asia/Singapore": 28800, "Asia/Srednekolymsk": 39600, "Asia/Taipei": 28800, "Asia/Tashkent": 18000, "Asia/Tbilisi": 14400, "Asia/Tehran": 12600, "Asia/Thimphu": 21600, "Asia/Tokyo": 32400, "Asia/Tomsk": 25200, "Asia/Ulaanbaatar": 28800, "Asia/Urumqi": 21600, "Asia/Ust-Nera": 36000, "Asia/Vientiane": 25200, "Asia/Vladivostok": 36000, "Asia/Yakutsk": 32400, "Asia/Yangon": 23400, "Asia/Yekaterinburg": 18000, "Asia/Yerevan": 14400, "Atlantic/Azores": 0, "Atlantic/Bermuda": -10800, "Atlantic/Canary": 3600, "Atlantic/Cape_Verde": -3600, "Atlantic/Faroe": 3600, "Atlantic/Madeira": 3600, "Atlantic/Reykjavik": 0, "Atlantic/South_Georgia": -7200, "Atlantic/Stanley": -10800, "Atlantic/St_Helena": 0, "Australia/Adelaide": 34200, "Australia/Brisbane": 36000, "Australia/Broken_Hill": 34200, "Australia/Darwin": 34200, "Australia/Eucla": 31500, "Australia/Hobart": 36000, "Australia/Lindeman": 36000, "Australia/Lord_Howe": 37800, "Australia/Melbourne": 36000, "Australia/Perth": 28800, "Australia/Sydney": 36000, "Europe/Amsterdam": 7200, "Europe/Andorra": 7200, "Europe/Astrakhan": 14400, "Europe/Athens": 10800, "Europe/Belgrade": 7200, "Europe/Berlin": 7200, "Europe/Bratislava": 7200, "Europe/Brussels": 7200, "Europe/Bucharest": 10800, "Europe/Budapest": 7200, "Europe/Busingen": 7200, "Europe/Chisinau": 10800, "Europe/Copenhagen": 7200, "Europe/Dublin": 3600, "Europe/Gibraltar": 7200, "Europe/Guernsey": 3600, "Europe/Helsinki": 10800, "Europe/Isle_of_Man": 3600, "Europe/Istanbul": 10800, "Europe/Jersey": 3600, "Europe/Kaliningrad": 7200, "Europe/Kirov": 10800, "Europe/Kyiv": 10800, "Europe/Lisbon": 3600, "Europe/Ljubljana": 7200, "Europe/London": 3600, "Europe/Luxembourg": 7200, "Europe/Madrid": 7200, "Europe/Malta": 7200, "Europe/Mariehamn": 10800, "Europe/Minsk": 10800, "Europe/Monaco": 7200, "Europe/Moscow": 10800, "Europe/Oslo": 7200, "Europe/Paris": 7200, "Europe/Podgorica": 7200, "Europe/Prague": 7200, "Europe/Riga": 10800, "Europe/Rome": 7200, "Europe/Samara": 14400, "Europe/San_Marino": 7200, "Europe/Sarajevo": 7200, "Europe/Saratov": 14400, "Europe/Simferopol": 10800, "Europe/Skopje": 7200, "Europe/Sofia": 10800, "Europe/Stockholm": 7200, "Europe/Tallinn": 10800, "Europe/Tirane": 7200, "Europe/Ulyanovsk": 14400, "Europe/Vaduz": 7200, "Europe/Vatican": 7200, "Europe/Vienna": 7200, "Europe/Vilnius": 10800, "Europe/Volgograd": 10800, "Europe/Warsaw": 7200, "Europe/Zagreb": 7200, "Europe/Zurich": 7200, "Indian/Antananarivo": 10800, "Indian/Chagos": 21600, "Indian/Christmas": 25200, "Indian/Cocos": 23400, "Indian/Comoro": 10800, "Indian/Kerguelen": 18000, "Indian/Mahe": 14400, "Indian/Maldives": 18000, "Indian/Mauritius": 14400, "Indian/Mayotte": 10800, "Indian/Reunion": 14400, "Pacific/Apia": 46800, "Pacific/Auckland": 43200, "Pacific/Bougainville": 39600, "Pacific/Chatham": 45900, "Pacific/Chuuk": 36000, "Pacific/Easter": -21600, "Pacific/Efate": 39600, "Pacific/Fakaofo": 46800, "Pacific/Fiji": 43200, "Pacific/Funafuti": 43200, "Pacific/Galapagos": -21600, "Pacific/Gambier": -32400, "Pacific/Guadalcanal": 39600, "Pacific/Guam": 36000, "Pacific/Honolulu": -36000, "Pacific/Kanton": 46800, "Pacific/Kiritimati": 50400, "Pacific/Kosrae": 39600, "Pacific/Kwajalein": 43200, "Pacific/Majuro": 43200, "Pacific/Marquesas": -34200, "Pacific/Midway": -39600, "Pacific/Nauru": 43200, "Pacific/Niue": -39600, "Pacific/Norfolk": 39600, "Pacific/Noumea": 39600, "Pacific/Pago_Pago": -39600, "Pacific/Palau": 32400, "Pacific/Pitcairn": -28800, "Pacific/Pohnpei": 39600, "Pacific/Port_Moresby": 36000, "Pacific/Rarotonga": -36000, "Pacific/Saipan": 36000, "Pacific/Tahiti": -36000, "Pacific/Tarawa": 43200, "Pacific/Tongatapu": 46800, "Pacific/Wake": 43200, 14 | "Etc/GMT-12": 12*60*60, 15 | "Etc/GMT-11": 11*60*60, 16 | "Etc/GMT-10": 10*60*60, 17 | "Etc/GMT-9": 9*60*60, 18 | "Etc/GMT-8": 8*60*60, 19 | "Etc/GMT-7": 7*60*60, 20 | "Etc/GMT-6": 6*60*60, 21 | "Etc/GMT-5": 5*60*60, 22 | "Etc/GMT-4": 4*60*60, 23 | "Etc/GMT-3": 3*60*60, 24 | "Etc/GMT-2": 2*60*60, 25 | "Etc/GMT-1": 1*60*60, 26 | "Etc/GMT": 0*60*60, 27 | "Etc/GMT+1": -1*60*60, 28 | "Etc/GMT+2": -2*60*60, 29 | "Etc/GMT+3": -3*60*60, 30 | "Etc/GMT+4": -4*60*60, 31 | "Etc/GMT+5": -5*60*60, 32 | "Etc/GMT+6": -6*60*60, 33 | "Etc/GMT+7": -7*60*60, 34 | "Etc/GMT+8": -8*60*60, 35 | "Etc/GMT+9": -9*60*60, 36 | "Etc/GMT+10": -10*60*60, 37 | "Etc/GMT+11": -11*60*60, 38 | "Etc/GMT+12": -12*60*60 39 | } 40 | 41 | export class TimeZoneMap{ 42 | tzcolormap=[["Africa/Abidjan","Africa/Accra","Africa/Addis_Ababa","Africa/Algiers","Africa/Asmara","Africa/Bamako","Africa/Bangui","Africa/Banjul","Africa/Bissau","Africa/Blantyre","Africa/Brazzaville","Africa/Bujumbura","Africa/Cairo","Africa/Casablanca","Africa/Ceuta","Africa/Conakry","Africa/Dakar","Africa/Dar_es_Salaam","Africa/Djibouti","Africa/Douala","Africa/El_Aaiun","Africa/Freetown","Africa/Gaborone","Africa/Harare","Africa/Johannesburg","Africa/Juba","Africa/Kampala","Africa/Khartoum","Africa/Kigali","Africa/Kinshasa","Africa/Lagos","Africa/Libreville","Africa/Lome","Africa/Luanda","Africa/Lubumbashi","Africa/Lusaka","Africa/Malabo","Africa/Maputo","Africa/Maseru","Africa/Mbabane","Africa/Mogadishu","Africa/Monrovia","Africa/Nairobi","Africa/Ndjamena","Africa/Niamey","Africa/Nouakchott","Africa/Ouagadougou","Africa/Porto-Novo","Africa/Sao_Tome","Africa/Tripoli","Africa/Tunis","Africa/Windhoek","America/Adak","America/Anchorage","America/Anguilla","America/Antigua","America/Aruba","America/Araguaina","America/Argentina/Buenos_Aires","America/Argentina/Catamarca","America/Argentina/Cordoba","America/Argentina/Jujuy","America/Argentina/La_Rioja","America/Argentina/Mendoza","America/Argentina/Rio_Gallegos","America/Argentina/Salta","America/Argentina/San_Juan","America/Argentina/San_Luis","America/Argentina/Tucuman","America/Argentina/Ushuaia","America/Asuncion","America/Atikokan","America/Bahia","America/Bahia_Banderas","America/Barbados","America/Belem","America/Belize","America/Blanc-Sablon","America/Boa_Vista","America/Bogota","America/Boise","America/Cambridge_Bay","America/Campo_Grande","America/Cancun","America/Caracas","America/Cayenne","America/Cayman","America/Chicago","America/Chihuahua","America/Ciudad_Juarez","America/Costa_Rica","America/Creston","America/Cuiaba","America/Curacao","America/Danmarkshavn","America/Dawson","America/Dawson_Creek","America/Denver","America/Detroit","America/Dominica","America/Edmonton","America/Eirunepe","America/El_Salvador","America/Fort_Nelson","America/Fortaleza","America/Glace_Bay","America/Goose_Bay","America/Grand_Turk","America/Grenada","America/Guadeloupe","America/Guatemala","America/Guayaquil","America/Guyana","America/Halifax","America/Havana","America/Hermosillo","America/Indiana/Indianapolis","America/Indiana/Knox","America/Indiana/Marengo","America/Indiana/Petersburg","America/Indiana/Tell_City","America/Indiana/Vevay","America/Indiana/Vincennes","America/Indiana/Winamac","America/Inuvik","America/Iqaluit","America/Jamaica","America/Juneau","America/Kentucky/Louisville","America/Kentucky/Monticello","America/Kralendijk","America/La_Paz","America/Lima","America/Los_Angeles","America/Lower_Princes","America/Maceio","America/Managua","America/Manaus","America/Marigot","America/Martinique","America/Matamoros","America/Mazatlan","America/Miquelon","America/Menominee","America/Merida","America/Metlakatla","America/Mexico_City","America/Moncton","America/Monterrey","America/Montevideo","America/Montserrat","America/Nassau","America/New_York","America/Nome","America/Noronha","America/North_Dakota/Beulah","America/North_Dakota/Center","America/North_Dakota/New_Salem","America/Nuuk","America/Ojinaga","America/Panama","America/Paramaribo","America/Phoenix","America/Port-au-Prince","America/Port_of_Spain","America/Porto_Velho","America/Puerto_Rico","America/Punta_Arenas","America/Rankin_Inlet","America/Recife","America/Regina","America/Resolute","America/Rio_Branco","America/Santarem","America/Santiago","America/Santo_Domingo","America/Sao_Paulo","America/Scoresbysund","America/Sitka","America/St_Barthelemy","America/St_Johns","America/St_Kitts","America/St_Lucia","America/St_Thomas","America/St_Vincent","America/Swift_Current","America/Tegucigalpa","America/Thule","America/Tijuana","America/Toronto","America/Tortola","America/Vancouver","America/Whitehorse","America/Winnipeg","America/Yakutat","Antarctica/Casey","Antarctica/Davis","Antarctica/DumontDUrville","Antarctica/Macquarie","Antarctica/Mawson","Antarctica/McMurdo","Antarctica/Palmer","Antarctica/Rothera","Antarctica/Syowa","Antarctica/Troll","Antarctica/Vostok","Arctic/Longyearbyen","Asia/Aden","Asia/Almaty","Asia/Amman","Asia/Anadyr","Asia/Aqtau","Asia/Aqtobe","Asia/Ashgabat","Asia/Atyrau","Asia/Baghdad","Asia/Bahrain","Asia/Baku","Asia/Bangkok","Asia/Barnaul","Asia/Beirut","Asia/Bishkek","Asia/Brunei","Asia/Chita","Asia/Choibalsan","Asia/Colombo","Asia/Damascus","Asia/Dhaka","Asia/Dili","Asia/Dubai","Asia/Dushanbe","Asia/Famagusta","Asia/Gaza","Asia/Hebron","Asia/Ho_Chi_Minh","Asia/Hong_Kong","Asia/Hovd","Asia/Irkutsk","Asia/Jakarta","Asia/Jayapura","Asia/Jerusalem","Asia/Kabul","Asia/Kamchatka","Asia/Karachi","Asia/Kathmandu","Asia/Khandyga","Asia/Kolkata","Asia/Krasnoyarsk","Asia/Kuala_Lumpur","Asia/Kuching","Asia/Kuwait","Asia/Macau","Asia/Magadan","Asia/Makassar","Asia/Manila","Asia/Muscat"],["Asia/Nicosia","Asia/Novokuznetsk","Asia/Novosibirsk","Asia/Omsk","Asia/Oral","Asia/Phnom_Penh","Asia/Pontianak","Asia/Pyongyang","Asia/Qatar","Asia/Qostanay","Asia/Qyzylorda","Asia/Riyadh","Asia/Sakhalin","Asia/Samarkand","Asia/Seoul","Asia/Shanghai","Asia/Singapore","Asia/Srednekolymsk","Asia/Taipei","Asia/Tashkent","Asia/Tbilisi","Asia/Tehran","Asia/Thimphu","Asia/Tokyo","Asia/Tomsk","Asia/Ulaanbaatar","Asia/Urumqi","Asia/Ust-Nera","Asia/Vientiane","Asia/Vladivostok","Asia/Yakutsk","Asia/Yangon","Asia/Yekaterinburg","Asia/Yerevan","Atlantic/Azores","Atlantic/Bermuda","Atlantic/Canary","Atlantic/Cape_Verde","Atlantic/Faroe","Atlantic/Madeira","Atlantic/Reykjavik","Atlantic/South_Georgia","Atlantic/St_Helena","Atlantic/Stanley","Australia/Adelaide","Australia/Brisbane","Australia/Broken_Hill","Australia/Darwin","Australia/Eucla","Australia/Hobart","Australia/Lindeman","Australia/Lord_Howe","Australia/Melbourne","Australia/Perth","Australia/Sydney","Etc/UTC","Europe/Amsterdam","Europe/Andorra","Europe/Astrakhan","Europe/Athens","Europe/Belgrade","Europe/Berlin","Europe/Bratislava","Europe/Brussels","Europe/Bucharest","Europe/Budapest","Europe/Busingen","Europe/Chisinau","Europe/Copenhagen","Europe/Dublin","Europe/Gibraltar","Europe/Guernsey","Europe/Helsinki","Europe/Isle_of_Man","Europe/Istanbul","Europe/Jersey","Europe/Kaliningrad","Europe/Kyiv","Europe/Kirov","Europe/Lisbon","Europe/Ljubljana","Europe/London","Europe/Luxembourg","Europe/Madrid","Europe/Malta","Europe/Mariehamn","Europe/Minsk","Europe/Monaco","Europe/Moscow","Europe/Oslo","Europe/Paris","Europe/Podgorica","Europe/Prague","Europe/Riga","Europe/Rome","Europe/Samara","Europe/San_Marino","Europe/Sarajevo","Europe/Saratov","Europe/Simferopol","Europe/Skopje","Europe/Sofia","Europe/Stockholm","Europe/Tallinn","Europe/Tirane","Europe/Ulyanovsk","Europe/Vaduz","Europe/Vatican","Europe/Vienna","Europe/Vilnius","Europe/Volgograd","Europe/Warsaw","Europe/Zagreb","Europe/Zurich","Indian/Antananarivo","Indian/Chagos","Indian/Christmas","Indian/Cocos","Indian/Comoro","Indian/Kerguelen","Indian/Mahe","Indian/Maldives","Indian/Mauritius","Indian/Mayotte","Indian/Reunion","Pacific/Apia","Pacific/Auckland","Pacific/Bougainville","Pacific/Chatham","Pacific/Chuuk","Pacific/Easter","Pacific/Efate","Pacific/Fakaofo","Pacific/Fiji","Pacific/Funafuti","Pacific/Galapagos","Pacific/Gambier","Pacific/Guadalcanal","Pacific/Guam","Pacific/Honolulu","Pacific/Kanton","Pacific/Kiritimati","Pacific/Kosrae","Pacific/Kwajalein","Pacific/Majuro","Pacific/Marquesas","Pacific/Midway","Pacific/Nauru","Pacific/Niue","Pacific/Norfolk","Pacific/Noumea","Pacific/Pago_Pago","Pacific/Palau","Pacific/Pitcairn","Pacific/Pohnpei","Pacific/Port_Moresby","Pacific/Rarotonga","Pacific/Saipan","Pacific/Tahiti","Pacific/Tarawa","Pacific/Tongatapu","Pacific/Wake","Pacific/Wallis","Etc/GMT-12","Etc/GMT-11","Etc/GMT-10","Etc/GMT-9","Etc/GMT-8","Etc/GMT-7","Etc/GMT-6","Etc/GMT-5","Etc/GMT-4","Etc/GMT-3","Etc/GMT-2","Etc/GMT-1","Etc/GMT","Etc/GMT+1","Etc/GMT+2","Etc/GMT+3","Etc/GMT+4","Etc/GMT+5","Etc/GMT+6","Etc/GMT+7","Etc/GMT+8","Etc/GMT+9","Etc/GMT+10","Etc/GMT+11","Etc/GMT+12"]]; 43 | canvas; 44 | ctx; 45 | img; 46 | scale=10; 47 | initialized=false; 48 | 49 | constructor(){ 50 | this.init(); 51 | } 52 | 53 | init(){ 54 | this.img=document.createElement("img"); 55 | this.img.src="tzmap.png"; 56 | this.img.addEventListener("load",this.init2.bind(this),false); 57 | } 58 | 59 | init2(){ 60 | this.canvas=document.createElement("canvas"); 61 | this.canvas.width=this.img.width; 62 | this.canvas.height=this.img.height; 63 | this.ctx=this.canvas.getContext("2d",{willReadFrequently:true}); 64 | this.ctx.drawImage(this.img,0,0); 65 | this.initialized=true; 66 | } 67 | 68 | getTZFromLatLon(lat,lon){ 69 | if(!this.initialized) return {id: "America/New_York", offset: -14400}; 70 | const d=this.ctx.getImageData((lon+180)*this.scale,this.img.height-(lat+90)*this.scale,1,1).data; 71 | const tzid=this.tzcolormap[d[1]][d[0]]; 72 | return {id: tzid, offset: tzoffsetlist[tzid]}; 73 | } 74 | } -------------------------------------------------------------------------------- /UI.js: -------------------------------------------------------------------------------- 1 | /* 2 | Greg Miller gmiller@gregmiller.net 2022 3 | https://www.celestialprogramming.com/ 4 | Released as public domain 5 | */ 6 | 7 | import {hoursToTime,prettyTime,UnixTimeFromJulianDate} from "./astro.js"; 8 | import {drawOverlay} from "./overlay.js"; 9 | import {computeEclipseData,displayShadowOutlines} from "./EclipseMapping.js"; 10 | import {getElementCoeffs, getLocalCircumstances } from "./eclipse.js"; 11 | import {TimeZoneMap} from "./TimeZoneMap.js"; 12 | 13 | const timeZoneMap=new TimeZoneMap(); 14 | let timeZone={id: "America/New_York", offset: -14400}; 15 | //populateTimeZones(); 16 | 17 | const canvas=document.getElementById("overlayCanvas"); 18 | const ctx = canvas.getContext("2d"); 19 | canvas.width=window.innerWidth; 20 | canvas.height=window.innerHeight; 21 | let markerList=[]; 22 | let lineList=[]; 23 | let currentPostionMarker=null; 24 | 25 | let lat=localStorage.getItem("eclipsemap_lat"); 26 | let lon=localStorage.getItem("eclipsemap_lon"); 27 | let zoom=localStorage.getItem("eclipsemap_zoom"); 28 | if(!lat || !lon || !zoom){ 29 | lat=38.2738; 30 | lon=-86.414; 31 | zoom=3; 32 | } 33 | 34 | var map = L.map('map',{ zoomControl: false, renderer: L.canvas() }).setView([lat,lon], zoom); 35 | let mapURL="https://tile.openstreetmap.org/{z}/{x}/{y}.png"; 36 | let mapAttrib='© OpenStreetMap'; 37 | let airialURL="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"; 38 | let airialAttrib="Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" 39 | let mapLayer=L.tileLayer(mapURL,{attribution: mapAttrib}) 40 | let airialLayer=L.tileLayer(airialURL,{ 41 | attribution: airialAttrib, 42 | maxNativeZoom:19, 43 | maxZoom:25 44 | }); 45 | 46 | var OpenTopoMapLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', { 47 | maxZoom: 25, 48 | attribution: 'Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap (CC-BY-SA)' 49 | }); 50 | 51 | var Esri_WorldGrayCanvas = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', { 52 | attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ', 53 | maxNativeZoom:16, 54 | maxZoom: 25 55 | }); 56 | 57 | /* 58 | TODO: Many more map styles at: 59 | https://leaflet-extras.github.io/leaflet-providers/preview/ 60 | */ 61 | 62 | const eclipseData=computeEclipseData(map,workerMessage); 63 | map.addLayer(mapLayer); 64 | 65 | // Add in a crosshair for the map 66 | var crosshairIcon = L.icon({ 67 | iconUrl: 'images/circle site.png', 68 | iconSize: [20, 20], // size of the icon 69 | iconAnchor: [10, 10], // point of the icon which will correspond to marker's location 70 | }); 71 | 72 | const p=map.getCenter(); 73 | const crosshairs=L.marker(p,{icon:crosshairIcon}).addTo(map); 74 | 75 | map.on('move', updateDisplay, this); 76 | map.on('click', mapClick,this); 77 | 78 | function mapClick(e){ 79 | const p=e.latlng; 80 | const c=getLocalCircumstances(p.lat, p.lng,0); 81 | let s="Not Total"; 82 | if(c.UTThirdContact-c.UTSecondContact>0) s=prettyTime(c.UTThirdContact-c.UTSecondContact); 83 | 84 | let m=L.circleMarker(p,{radius: 5, color: "#00000099"}); 85 | m.addTo(map).bindTooltip(s, {permanent: true}); 86 | 87 | markerList.push(m); 88 | if(markerList.length>1){ 89 | const t=markerList[markerList.length-2]; 90 | let dist=(t.getLatLng().distanceTo(m.getLatLng()) * 0.0006213712); 91 | 92 | if(dist<.25){ 93 | dist=Math.floor(dist*5280)+"ft"; 94 | } else { 95 | dist=dist.toFixed(2)+"mi"; 96 | } 97 | 98 | const line=new L.Polyline([t.getLatLng(),m.getLatLng()], {color: 'black', weight: 1}); 99 | line.addTo(map); 100 | line.bindTooltip(dist, {permanent: true}); 101 | line.openTooltip(); 102 | lineList.push(line); 103 | } 104 | } 105 | 106 | function removeAllMapLayers(){ 107 | map.removeLayer(mapLayer); 108 | map.removeLayer(airialLayer); 109 | map.removeLayer(OpenTopoMapLayer); 110 | map.removeLayer(Esri_WorldGrayCanvas); 111 | } 112 | 113 | function setMap(){ 114 | removeAllMapLayers(); 115 | map.addLayer(mapLayer); 116 | } 117 | 118 | function setSat(){ 119 | removeAllMapLayers(); 120 | map.addLayer(airialLayer); 121 | } 122 | 123 | function setTopo(){ 124 | removeAllMapLayers(); 125 | map.addLayer(OpenTopoMapLayer); 126 | } 127 | 128 | function setBwMap(){ 129 | removeAllMapLayers(); 130 | map.addLayer(Esri_WorldGrayCanvas); 131 | } 132 | 133 | function displayLocalCircumstances(){ 134 | const p=map.getCenter(); 135 | const c=getLocalCircumstances(p.lat, p.lng,0); 136 | const t=document.getElementById("localCircumstancesTable"); 137 | t.rows[1].cells[1].innerHTML=(p.lat-0).toFixed(4); 138 | t.rows[2].cells[1].innerHTML=(p.lng-0).toFixed(4); 139 | t.rows[3].cells[1].innerHTML=(c.h-0).toFixed(1); 140 | t.rows[4].cells[1].innerHTML=(c.mag*100).toFixed(1)+"%"; 141 | if(c.mag>0){ 142 | t.rows[5].cells[1].innerHTML=hoursToTime(c.UTFirstContact,timeZone); 143 | t.rows[6].cells[1].innerHTML=hoursToTime(c.UTLastContact,timeZone); 144 | t.rows[7].cells[1].innerHTML=prettyTime(c.UTLastContact-c.UTFirstContact); 145 | } else { 146 | t.rows[5].cells[1].innerHTML=""; 147 | t.rows[6].cells[1].innerHTML=""; 148 | t.rows[7].cells[1].innerHTML=""; 149 | } 150 | 151 | if(c.mag>=1){ 152 | t.rows[8].cells[1].innerHTML=hoursToTime(c.UTSecondContact,timeZone); 153 | t.rows[10].cells[1].innerHTML=hoursToTime(c.UTThirdContact,timeZone); 154 | t.rows[11].cells[1].innerHTML=prettyTime(c.UTThirdContact-c.UTSecondContact); 155 | } else { 156 | t.rows[8].cells[1].innerHTML=""; 157 | t.rows[10].cells[1].innerHTML=""; 158 | t.rows[11].cells[1].innerHTML=""; 159 | } 160 | t.rows[9].cells[1].innerHTML=hoursToTime(c.UTMaximum,timeZone); 161 | } 162 | 163 | function formatUTCOffset(tz){ 164 | let s=tz.offset; 165 | let pm=""; 166 | if(s>=0) pm="+"; 167 | let hours=Math.floor(s/60/60); 168 | let t=s-hours*60*60; 169 | t=t/60; 170 | let ez=""; 171 | if (t<10) ez="0" 172 | return pm+hours+":"+ez+t; 173 | } 174 | 175 | function updateDisplay(e){ 176 | const center=map.getCenter(); 177 | 178 | timeZone=timeZoneMap.getTZFromLatLon(center.lat,center.lng); 179 | document.getElementById("timeZoneDisplay").innerText="GMT " + formatUTCOffset(timeZone); 180 | const t=displayLocalCircumstances(); 181 | displayShadowOutlines(document.getElementById("timeSlider").value-0); 182 | crosshairs.setLatLng(center); 183 | drawOverlay(ctx,map,eclipseData); 184 | updateTimeLabel(animhour); 185 | localStorage.setItem("eclipsemap_lat",center.lat); 186 | localStorage.setItem("eclipsemap_lon",center.lng); 187 | localStorage.setItem("eclipsemap_zoom",map.getZoom()); 188 | } 189 | 190 | function workerMessage(e){ 191 | const lines=e.data; 192 | eclipseData.durationLines=lines; 193 | document.getElementById("durationLinesLoadingSpinner").classList.remove("loadingSpinner"); 194 | document.getElementById("showDurationLinesCheckbox").style.visibility="visible"; 195 | updateDisplay(); 196 | } 197 | 198 | function clearMarkers(e){ 199 | for(let i=0;i=0){ 221 | o.text="UTC + "+i; 222 | } else { 223 | o.text="UTC - "+Math.abs(i); 224 | } 225 | if(i==tz) o.selected="selected"; 226 | e.add(o,null); 227 | } 228 | 229 | } 230 | 231 | function sliderInput(e){ 232 | runAmination=false; 233 | animhour=document.getElementById("timeSlider").value-0; 234 | displayShadowOutlines(animhour); 235 | updateTimeLabel(animhour); 236 | } 237 | 238 | function updateTimeLabel(hour){ 239 | const e=getElementCoeffs(); 240 | let time=e.T0 + hour - e.Δt/60/60 241 | if(time < eclipseData.beginTime){ 242 | document.getElementById("timeSliderLabel").innerText="N/A"; 243 | } else { 244 | document.getElementById("timeSliderLabel").innerText=hoursToTime(time,timeZone); 245 | } 246 | } 247 | 248 | function animate(){ 249 | displayShadowOutlines(animhour); 250 | updateTimeLabel(animhour); 251 | 252 | if(runAmination && animhour{ 270 | navigator.geolocation.getCurrentPosition(positionListener); 271 | },1000); 272 | } 273 | } 274 | 275 | function autoPositionListener(p){ 276 | if(p!=null){ 277 | displayCurrentPositionMarker(p.coords.latitude, p.coords.longitude); 278 | } 279 | if(document.getElementById("autoUpdatePosition").checked){ 280 | window.setTimeout(()=>{ 281 | navigator.geolocation.getCurrentPosition(autoPositionListener); 282 | },1000); 283 | } 284 | } 285 | 286 | function displayCurrentPositionMarker(lat,lon){ 287 | if(currentPostionMarker==null){ 288 | const icon=L.icon({ 289 | iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAWRJREFUOE+1VEtSg0AQ7T6A7s1G2HkAXQNewAtY5bD0FBlOAqnyAp4AstYD6AqyyQF0T+vrMNSESgUJsauooufz5vW86cc0CBGJiagcjh9Yt2HmnJkzf45dIiIBEeVEBEBqmm8qig9ar7dUVVsKgovuu6QouiJjbtxW64P6gDURBQ4oy96Pklwub8naO7emYeYQiQKKCEqMwSRJXseq7efBOs/vKY4XGCuYOWX/zsLwRUudEgCt60e3RQEFGZiB4SmB+8zzBJVuFHBqqYcOLcsHLV0BrX2jMRHGmDuRFDBNS30icwLswFIBTxFjeLgT52yAu+f3vGM4R2HH1Cn9P6Kc9dm0bYs+vJ6jtCtXe1lEDFwGLYe7nNl6iTMH2JaZCjowh4qZe0B4IRznz/bllwn73LOvzsIA+vT7b5FDqNXqk5rma89go2ih5gp2XSgzl/QG6zm3QXtDqGOtqM7CbJi58tf9AArJ62Vg0Th6AAAAAElFTkSuQmCC', 290 | iconAnchor: [10,10] 291 | }) 292 | currentPostionMarker=L.marker([lat,lon],{icon: icon,zIndexOffset: -1}); 293 | currentPostionMarker.addTo(map); 294 | } else { 295 | currentPostionMarker.setLatLng([lat,lon]); 296 | } 297 | } 298 | 299 | function autoUpdatePositionClick(e){ 300 | autoPositionListener(null); 301 | } 302 | 303 | function realTimeClick(e){ 304 | runAmination=false; 305 | document.getElementById("timeSlider").disabled=true; 306 | 307 | } 308 | 309 | function alarm(time,cutoff){ 310 | if(Math.abs(time-cutoff*1000)<=1000){ 311 | beep(); 312 | } 313 | } 314 | 315 | function alarms(first, second, third, fourth){ 316 | if(document.getElementById("tenSecondWarning").checked){ 317 | alarm(first,10); 318 | alarm(second,10); 319 | alarm(third,10); 320 | alarm(fourth,10); 321 | } 322 | 323 | if(document.getElementById("oneMinuteWarning").checked){ 324 | alarm(first,60); 325 | alarm(second,60); 326 | alarm(third,60); 327 | alarm(fourth,60); 328 | } 329 | 330 | if(document.getElementById("fiveMinuteWarning").checked){ 331 | alarm(first,300); 332 | alarm(second,300); 333 | alarm(fourth,300); 334 | } 335 | } 336 | 337 | function updateCountdown(){ 338 | const e=getElementCoeffs(); 339 | const p=map.getCenter(); 340 | const c=getLocalCircumstances(p.lat, p.lng,0); 341 | const t=document.getElementById("countdownTable"); 342 | const time=new Date().getTime()+testTimeOffset; 343 | const fc=UnixTimeFromJulianDate(c.UTFirstContact/24.0+c.jd)-time; 344 | const lc=UnixTimeFromJulianDate(c.UTLastContact/24.0+c.jd)-time; 345 | const sc=UnixTimeFromJulianDate(c.UTSecondContact/24.0+c.jd)-time; 346 | const tc=UnixTimeFromJulianDate(c.UTThirdContact/24.0+c.jd)-time; 347 | alarms(fc,sc,tc,lc); 348 | 349 | if(c.mag>0){ 350 | t.rows[0].cells[1].innerHTML=prettyTime(fc/1000/60/60); 351 | t.rows[3].cells[1].innerHTML=prettyTime(lc/1000/60/60); 352 | } else { 353 | t.rows[0].cells[1].innerHTML=""; 354 | t.rows[3].cells[1].innerHTML=""; 355 | } 356 | 357 | if(c.mag>=1){ 358 | t.rows[1].cells[1].innerHTML=prettyTime(sc/1000/60/60); 359 | t.rows[2].cells[1].innerHTML=prettyTime(tc/1000/60/60); 360 | } else { 361 | t.rows[1].cells[1].innerHTML=""; 362 | t.rows[2].cells[1].innerHTML=""; 363 | } 364 | } 365 | 366 | function setRealTimeDisplay(){ 367 | const e=getElementCoeffs(); 368 | const t0=UnixTimeFromJulianDate(e.jd) + (e.T0 - e.Δt/60/60)*60*60*1000; 369 | const now=new Date().getTime()+testTimeOffset; 370 | const diff=(now-t0)/1000/60/60; 371 | animhour=diff; 372 | document.getElementById("timeSlider").value=animhour; 373 | 374 | displayShadowOutlines(animhour); 375 | updateTimeLabel(animhour); 376 | } 377 | 378 | function displayCountdown(){ 379 | if(document.getElementById("realTimeCheckbox").checked==true){ 380 | setRealTimeDisplay(); 381 | } 382 | if(document.getElementById("showCountdownCheckbox").checked==true){ 383 | updateCountdown(); 384 | } 385 | 386 | window.setTimeout(()=>{ 387 | displayCountdown(); 388 | },100); 389 | } 390 | 391 | function countdownClick(e){ 392 | const d=document.getElementById("countdownDiv"); 393 | if(document.getElementById("showCountdownCheckbox").checked==true){ 394 | d.style.display="inline-block"; 395 | } else { 396 | d.style.display="none"; 397 | } 398 | } 399 | 400 | function beep() { 401 | beepSound.play(); 402 | } 403 | 404 | function simulationModeClick(){ 405 | const e=getElementCoeffs(); 406 | const t0=UnixTimeFromJulianDate(e.jd) + (e.T0 - e.Δt/60/60)*60*60*1000; 407 | testTimeOffset=t0+eclipseData.beginTime*60*60*1000 - new Date().getTime() + 1*60*60*1000; 408 | 409 | document.getElementById("simulationModeDiv").style.display="block"; 410 | document.getElementById("realTimeCheckbox").checked=true; 411 | realTimeClick(null); 412 | } 413 | 414 | export function simulationTimeClick(v){ 415 | testTimeOffset+=v*1000*60; 416 | } 417 | 418 | export function initUI(){ 419 | animate(); 420 | 421 | document.getElementById("setMapImg").addEventListener("click",setMap); 422 | document.getElementById("setSatImg").addEventListener("click",setSat); 423 | document.getElementById("setTopoImg").addEventListener("click",setTopo); 424 | document.getElementById("setBwMapImg").addEventListener("click",setBwMap); 425 | document.getElementById("clearMarkersButton").addEventListener("click",clearMarkers); 426 | document.getElementById("showDurationLinesCheckbox").addEventListener("click",durationLinesCheck); 427 | //document.getElementById("timeZoneSelect").addEventListener("input",updateDisplay); 428 | document.getElementById("timeSlider").addEventListener("input",sliderInput); 429 | document.getElementById("locationButton").addEventListener("click",locationClick); 430 | document.getElementById("showCountdownCheckbox").addEventListener("click",countdownClick); 431 | document.getElementById("realTimeCheckbox").addEventListener("click",realTimeClick); 432 | document.getElementById("warningButton").addEventListener("click",beep); 433 | document.getElementById("simulationHref").addEventListener("click",simulationModeClick); 434 | document.getElementById("autoUpdatePosition").addEventListener("click",autoUpdatePositionClick); 435 | 436 | window.addEventListener("resize",updateDisplay); 437 | window.setTimeout(()=>{ 438 | displayCountdown(); 439 | },100); 440 | updateDisplay(null); 441 | } 442 | 443 | const beepSound = new Audio("alarm.wav"); 444 | 445 | let runAmination=true; 446 | let animhour=eclipseData.beginTime; 447 | let testTimeOffset=0; 448 | //let testTimeOffset=((32*24 + 14)*60 + 35)*60*1000; 449 | -------------------------------------------------------------------------------- /WebGLEarth/astro.js: -------------------------------------------------------------------------------- 1 | export function hoursToTime(hours,tz){ 2 | hours+=tz.offset; //Convert to EDT 3 | let h=Math.trunc(hours); 4 | let m=(hours-h)*60; 5 | let s=Math.trunc((m-Math.trunc(m))*60); 6 | m=Math.trunc(m); 7 | 8 | if(h<10){h="0"+h;} 9 | if(m<10){m="0"+m;} 10 | if(s<10){s="0"+s;} 11 | 12 | return h+":"+m+":"+s+" "+tz.name; 13 | } 14 | 15 | export function prettyTime(hours){ 16 | let h=Math.trunc(hours); 17 | let m=(hours-h)*60; 18 | let s=Math.trunc((m-Math.trunc(m))*60); 19 | m=Math.trunc(m); 20 | 21 | let t=s+"s"; 22 | if(m>0) t=m+"m "+t; 23 | if(h>0) t=h+"h "+t; 24 | return t; 25 | 26 | } 27 | 28 | export function convertTDBToUTC(jd_tdb){ 29 | //Conversion already performed using deltaT in eclipse module 30 | throw("TDB to UTC Not needed"); 31 | const jd_tt = jd_tdb; //differenece in TT is negligable 32 | const jd_tai = jd_tt - 32.184 / 60 / 60 / 24; 33 | const jd_utc = jd_tai - getLeapSeconds(jd_tai) / 60 / 60 / 24; 34 | 35 | return jd_utc; 36 | } 37 | 38 | export function getLeapSeconds(jd) { 39 | //Source IERS Resolution B1 and http://maia.usno.navy.mil/ser7/tai-utc.dat 40 | //This function must be updated any time a new leap second is introduced 41 | 42 | if (jd > 2457754.5) return 37.0; 43 | if (jd > 2457204.5) return 36.0; 44 | if (jd > 2456109.5) return 35.0; 45 | if (jd > 2454832.5) return 34.0; 46 | if (jd > 2453736.5) return 33.0; 47 | if (jd > 2451179.5) return 32.0; 48 | if (jd > 2450630.5) return 31.0; 49 | if (jd > 2450083.5) return 30.0; 50 | if (jd > 2449534.5) return 29.0; 51 | if (jd > 2449169.5) return 28.0; 52 | if (jd > 2448804.5) return 27.0; 53 | if (jd > 2448257.5) return 26.0; 54 | if (jd > 2447892.5) return 25.0; 55 | if (jd > 2447161.5) return 24.0; 56 | if (jd > 2446247.5) return 23.0; 57 | if (jd > 2445516.5) return 22.0; 58 | if (jd > 2445151.5) return 21.0; 59 | if (jd > 2444786.5) return 20.0; 60 | if (jd > 2444239.5) return 19.0; 61 | if (jd > 2443874.5) return 18.0; 62 | if (jd > 2443509.5) return 17.0; 63 | if (jd > 2443144.5) return 16.0; 64 | if (jd > 2442778.5) return 15.0; 65 | if (jd > 2442413.5) return 14.0; 66 | if (jd > 2442048.5) return 13.0; 67 | if (jd > 2441683.5) return 12.0; 68 | if (jd > 2441499.5) return 11.0; 69 | if (jd > 2441317.5) return 10.0; 70 | if (jd > 2439887.5) return 4.21317 + (jd - 2439126.5) * 0.002592; 71 | if (jd > 2439126.5) return 4.31317 + (jd - 2439126.5) * 0.002592; 72 | if (jd > 2439004.5) return 3.84013 + (jd - 2438761.5) * 0.001296; 73 | if (jd > 2438942.5) return 3.74013 + (jd - 2438761.5) * 0.001296; 74 | if (jd > 2438820.5) return 3.64013 + (jd - 2438761.5) * 0.001296; 75 | if (jd > 2438761.5) return 3.54013 + (jd - 2438761.5) * 0.001296; 76 | if (jd > 2438639.5) return 3.44013 + (jd - 2438761.5) * 0.001296; 77 | if (jd > 2438486.5) return 3.34013 + (jd - 2438761.5) * 0.001296; 78 | if (jd > 2438395.5) return 3.24013 + (jd - 2438761.5) * 0.001296; 79 | if (jd > 2438334.5) return 1.945858 + (jd - 2437665.5) * 0.0011232; 80 | if (jd > 2437665.5) return 1.845858 + (jd - 2437665.5) * 0.0011232; 81 | if (jd > 2437512.5) return 1.372818 + (jd - 2437300.5) * 0.001296; 82 | if (jd > 2437300.5) return 1.422818 + (jd - 2437300.5) * 0.001296; 83 | return 0.0; 84 | } 85 | 86 | -------------------------------------------------------------------------------- /WebGLEarth/backgroundworker.js: -------------------------------------------------------------------------------- 1 | 2 | import {getLocalCircumstances} from "./eclipse.js"; 3 | 4 | function findNorthLatForDuration(lon,max,min,target){ 5 | let result=9999; 6 | let count=0; 7 | let total; 8 | 9 | while(((max-min)>.00001) && count<10000){ 10 | const guess=(max-min)/2+min; 11 | const c=getLocalCircumstances(guess, lon,0); 12 | total=(c.UTThirdContact-c.UTSecondContact)*60; 13 | 14 | result=guess; 15 | if(result==target){ 16 | max=min; 17 | } else if(total>target){ 18 | min=guess; 19 | } else { 20 | max=guess; 21 | } 22 | count++; 23 | } 24 | if(total+.01.00001) && count<10000){ 34 | const guess=(max-min)/2+min; 35 | const c=getLocalCircumstances(guess, lon,0); 36 | total=(c.UTThirdContact-c.UTSecondContact)*60; 37 | 38 | result=guess; 39 | if(result==target){ 40 | max=min; 41 | } else if(total=0 && cos>=0){return Math.asin(sin);} 17 | if(sin<0 && cos>=0){return Math.asin(sin);} 18 | if(sin<0 && cos<0){return -Math.acos(cos);} 19 | if(sin>=0 && cos<0){return Math.acos(cos);} 20 | } 21 | export function getElementCoeffs(){ 22 | return getElements2024(); 23 | } 24 | 25 | function getElements2024(){ 26 | const elements={}; 27 | elements.jd=2460408.5; 28 | elements.Δt=69; 29 | elements.T0=18; 30 | 31 | elements.X0=-0.3182440; 32 | elements.X1=0.5117116; 33 | elements.X2=0.0000326; 34 | elements.X3=-0.0000084; 35 | 36 | elements.Y0=0.2197640; 37 | elements.Y1=0.2709589; 38 | elements.Y2=-0.0000595; 39 | elements.Y3=-0.0000047; 40 | 41 | elements.d0=7.5862002; 42 | elements.d1=0.0148440; 43 | elements.d2=-0.0000020; 44 | elements.d3=0.0000000; 45 | 46 | elements.L10=0.5358140; 47 | elements.L11=0.0000618; 48 | elements.L12=-0.0000128; 49 | elements.L13=0.0000000; 50 | 51 | elements.L20=-0.0102720; 52 | elements.L21=0.0000615; 53 | elements.L22=-0.0000127; 54 | elements.L23=0.0000000; 55 | 56 | elements.M0=89.591217; 57 | elements.M1=15.004080; 58 | elements.M2=0.000000; 59 | elements.M3=0.000000; 60 | 61 | elements.tanf1 = 0.0046683; 62 | elements.tanf2 = 0.0046450; 63 | 64 | return elements; 65 | 66 | } 67 | 68 | function getElements1996(){ 69 | //Example data for Oct 12, 1996 70 | const elements={}; 71 | elements.Δt=63; 72 | elements.T0=14; 73 | 74 | elements.X0=0.296103; 75 | elements.X1=0.5060364; 76 | elements.X2=0.0000145; 77 | elements.X3=-0.00000644; 78 | 79 | elements.Y0=1.083058; 80 | elements.Y1=-0.1515218; 81 | elements.Y2=-0.0000102; 82 | elements.Y3=0.00000184; 83 | 84 | elements.d0=-7.63950; 85 | elements.d1=-0.015234; 86 | elements.d2=0.000002; 87 | 88 | elements.M0=33.40582; 89 | elements.M1=15.003782; 90 | 91 | elements.L10=0.559341; 92 | elements.L11=-0.0001067; 93 | elements.L12=-0.0000107; 94 | 95 | elements.L20=0.013151; 96 | elements.L21=-0.0001062; 97 | elements.L22=-0.0000106; 98 | 99 | elements.tanf1=0.0046865; 100 | elements.tanf2=0.0046632; 101 | 102 | return elements; 103 | } 104 | 105 | export function getElements1994(){ 106 | //Example data for May 10, 1994 107 | const elements={}; 108 | elements.Δt=61; 109 | elements.T0=17; 110 | 111 | elements.X0=-0.173367; 112 | elements.X1=0.4990629; 113 | elements.X2=0.0000296; 114 | elements.X3=-0.00000563; 115 | 116 | elements.Y0=0.383484; 117 | elements.Y1=0.0869393; 118 | elements.Y2=-0.0001183; 119 | elements.Y3=-0.00000092; 120 | 121 | elements.d0=17.68613; 122 | elements.d1=0.010642; 123 | elements.d2=-0.000004; 124 | 125 | elements.M0=75.90923; 126 | elements.M1=15.001621; 127 | 128 | elements.L10=0.566906; 129 | elements.L11=-0.0000318; 130 | elements.L12=-0.0000098; 131 | 132 | elements.L20=0.020679; 133 | elements.L21=-0.0000317; 134 | elements.L22=-0.0000097; 135 | 136 | elements.tanf1=0.0046308; 137 | elements.tanf2=0.0046077; 138 | 139 | return elements; 140 | } 141 | function getElements(e,t,Φ,λ,height){ 142 | const o={}; 143 | o.X=e.X0 + e.X1*t + e.X2*t*t + e.X3*t*t*t; 144 | o.Y=e.Y0 + e.Y1*t + e.Y2*t*t + e.Y3*t*t*t; 145 | o.d=e.d0 + e.d1*t + e.d2*t*t; 146 | o.M=e.M0 + e.M1*t; 147 | o.Xp=e.X1 + 2*e.X2*t + 3*e.X3*t*t; 148 | o.Yp=e.Y1 + 2*e.Y2*t + 3*e.Y3*t*t; 149 | o.L1=e.L10 + e.L11*t + e.L12*t*t; 150 | o.L2=e.L20 + e.L21*t + e.L22*t*t; 151 | 152 | o.H = o.M - λ - 0.00417807 * e.Δt; 153 | 154 | o.u1 = Math.atan(0.99664719*Math.tan(Φ*rad))/rad; 155 | o.ρsinΦp=0.99664719 * Math.sin(o.u1*rad)+height/6378140*Math.sin(Φ*rad); 156 | o.ρcosΦp=Math.cos(o.u1*rad) + height/6378140 * Math.cos(Φ*rad); 157 | 158 | o.ξ = o.ρcosΦp * Math.sin(o.H*rad); 159 | o.η = o.ρsinΦp * Math.cos(o.d*rad) - o.ρcosΦp * Math.cos(o.H*rad) * Math.sin(o.d*rad); 160 | o.ζ = o.ρsinΦp * Math.sin(o.d*rad) + o.ρcosΦp * Math.cos(o.H*rad) * Math.cos(o.d*rad); 161 | o.ξp = 0.01745329 * e.M1 * o.ρcosΦp * Math.cos(o.H*rad); 162 | o.ηp = 0.01745329 * (e.M1 * o.ξ * Math.sin(o.d*rad) - o.ζ * e.d1); 163 | o.L1p = o.L1 - o.ζ * e.tanf1; 164 | o.L2p = o.L2 - o.ζ * e.tanf2; 165 | 166 | o.u = o.X - o.ξ; 167 | o.v = o.Y - o.η; 168 | o.a = o.Xp - o.ξp; 169 | o.b = o.Yp - o.ηp; 170 | o.n = Math.sqrt(o.a*o.a + o.b*o.b); 171 | 172 | return o; 173 | 174 | } 175 | 176 | export function getLocalCircumstances(Φ,λ,height){ 177 | const e=getElementCoeffs(); 178 | λ=-λ; 179 | 180 | let t=0; 181 | let τm=10000; 182 | 183 | let iterations=0; 184 | let o; 185 | while(Math.abs(τm)>.00001 && iterations 90){ 451 | Φ1 = 180 - β + d1; 452 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*raD))/rad; 453 | λ = - (M - 180 - 0.00417807 * e.Δt); 454 | } else if(β + d1 < -90){ 455 | Φ1 = -(180 + β + d1); 456 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*rad))/rad; 457 | λ = - (M + 180 - 0.00417807 * e.Δt); 458 | } else { 459 | Φ1 = β + d1; 460 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*rad))/rad; 461 | λ = - (M - 0.00417807 * e.Δt); 462 | } 463 | if(λ<-180){λ+=360;} 464 | 465 | const UT = e.T0 + t - e.Δt/60/60; 466 | return {UT: UT, lat: Φ, lon: λ}; 467 | } 468 | 469 | export function getCenterLineByLongitude(e,λ){ 470 | return getLimitsForLogitude(e,λ,0,1,0); 471 | } 472 | 473 | export function getTotalityLimitsByLongitudeList(e,northsouth,startLon,endLon){ 474 | return getLimitsByLongitudeAsList(e,northsouth,1,startLon,endLon); 475 | } 476 | 477 | export function getPartialLimitsByLogitudeList(e,northsouth,startLon,endLon){ 478 | return getLimitsByLongitudeAsList(e,northsouth,0,startLon,endLon); 479 | } 480 | 481 | function getLimitsByLongitudeAsList(e,northsouth,G,startLon,endLon){ 482 | const eqPoints=new Array(); 483 | const polarPoints=new Array(); 484 | 485 | for(let i=startLon;i<=endLon;i+=.01){ 486 | const eq=getLimitsForLogitude(e,i,northsouth,G,0); 487 | const polar=getLimitsForLogitude(e,i,northsouth,G,89.9 * Math.sign(e.Y0)); 488 | 489 | if(polar!=null && eq!=null){ 490 | if(Math.abs(eq.lat-polar.lat)<.1){ 491 | eqPoints.push(eq); 492 | } else { 493 | eqPoints.push(eq); 494 | polarPoints.push(polar); 495 | } 496 | } else { 497 | eqPoints.push(null); 498 | polarPoints.push(null); 499 | } 500 | } 501 | 502 | return [eqPoints,polarPoints]; 503 | } 504 | 505 | function getLimitsForLogitude(e,λ,northsouth,G,startΦ){ 506 | 507 | let t=0; 508 | let Φ=startΦ; 509 | 510 | let i=0; 511 | let ΔΦ=1000; 512 | let τ=1000; 513 | while((Math.abs(τ)>.0001 || Math.abs(ΔΦ)>.0001) && i<20){ 514 | const X=e.X0 + e.X1*t + e.X2*t*t + e.X3*t*t*t; 515 | const Y=e.Y0 + e.Y1*t + e.Y2*t*t + e.Y3*t*t*t; 516 | const d=e.d0 + e.d1*t + e.d2*t*t; 517 | const M=e.M0 + e.M1*t; 518 | const Xp=e.X1 + 2*e.X2*t + 3*e.X3*t*t; 519 | const Yp=e.Y1 + 2*e.Y2*t + 3*e.Y3*t*t; 520 | const L1=e.L10 + e.L11*t + e.L12*t*t; 521 | const L2=e.L20 + e.L21*t + e.L22*t*t; 522 | 523 | const H = M + λ - 0.00417807 * e.Δt; 524 | 525 | const height=0; 526 | const u1 = Math.atan(0.99664719*Math.tan(Φ*rad))/rad; 527 | const ρsinΦp=0.99664719 * Math.sin(u1*rad)+height/6378140*Math.sin(Φ*rad); 528 | const ρcosΦp=Math.cos(u1*rad) + height/6378140 * Math.cos(Φ*rad); 529 | 530 | const ξ = ρcosΦp * Math.sin(H*rad); 531 | const η = ρsinΦp * Math.cos(d*rad) - ρcosΦp * Math.cos(H*rad) * Math.sin(d*rad); 532 | const ζ = ρsinΦp * Math.sin(d*rad) + ρcosΦp * Math.cos(H*rad) * Math.cos(d*rad); 533 | const ξp = 0.01745329 * e.M1 * ρcosΦp * Math.cos(H*rad); 534 | const ηp = 0.01745329 * (e.M1 * ξ * Math.sin(d*rad) - ζ * e.d1); 535 | const L1p = L1 - ζ * e.tanf1; 536 | const L2p = L2 - ζ * e.tanf2; 537 | 538 | const u = X - ξ; 539 | const v = Y - η; 540 | const a = Xp - ξp; 541 | const b = Yp - ηp; 542 | const n = Math.sqrt(a*a + b*b); 543 | 544 | τ = - (u*a + v*b)/(n*n); 545 | const W = (v*a - u*b)/n; 546 | const Q = ((b * Math.sin(H*rad) * ρsinΦp + a*(Math.cos(H*rad) * Math.sin(d*rad) * ρsinΦp + Math.cos(d*rad)*ρcosΦp))) 547 | / (57.29578 * n); 548 | 549 | //northsouth = 1 for northnet limit, -1 for southern limit 550 | const E=L1p - G * (L1p + L2p); 551 | ΔΦ = (W + northsouth * Math.abs(E))/Q; 552 | 553 | t = t + τ; 554 | Φ = Φ + ΔΦ; 555 | i++; 556 | } 557 | if (Math.abs(τ)>.0001 || Math.abs(ΔΦ)>.0001){return null;} 558 | 559 | const UT= e.T0 + t; 560 | 561 | Φ=(90+Φ)%180; 562 | if(Φ<0)Φ+=180; 563 | Φ-=90; 564 | return {t: t, lat: Φ, lon: λ}; 565 | } -------------------------------------------------------------------------------- /WebGLEarth/images/bwmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/WebGLEarth/images/bwmap.png -------------------------------------------------------------------------------- /WebGLEarth/images/circle site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/WebGLEarth/images/circle site.png -------------------------------------------------------------------------------- /WebGLEarth/images/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/WebGLEarth/images/map.png -------------------------------------------------------------------------------- /WebGLEarth/images/satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/WebGLEarth/images/satellite.png -------------------------------------------------------------------------------- /WebGLEarth/images/topo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/WebGLEarth/images/topo.png -------------------------------------------------------------------------------- /WebGLEarth/overlay.js: -------------------------------------------------------------------------------- 1 | export function drawOverlay(ctx,map,eclipseData){ 2 | const zoom=map.getZoom(); 3 | 4 | ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); 5 | const central=drawLineOverlay(ctx,map,eclipseData.centralCurve, {color: 'black', weight: 2}); 6 | const limitNorthT=drawLineOverlay(ctx,map,eclipseData.northTotalityLimits, {color: 'red', weight: 2}); 7 | const limitSouthT=drawLineOverlay(ctx,map,eclipseData.southTotalityLimits, {color: 'red', weight: 2}); 8 | const limitNorthP=drawLineOverlay(ctx,map,eclipseData.northPartialLimits, {color: 'black', weight: 2,}); 9 | const limitSouthP=drawLineOverlay(ctx,map,eclipseData.southPartialLimits, {color: 'black', weight: 2}); 10 | 11 | if(zoom>=4){ 12 | ctx.font = "1em Georgia"; 13 | ctx.textAlign="center"; 14 | ctx.textBaseline="bottom"; 15 | drawLabel(ctx,map,"Totality Center Line",central); 16 | 17 | ctx.textBaseline="top"; 18 | drawLabel(ctx,map,"Partial Edge",limitSouthP); 19 | drawLabel(ctx,map,"Totality Edge",limitSouthT); 20 | 21 | ctx.textBaseline="bottom"; 22 | drawLabel(ctx,map,"Totality Edge",limitNorthT); 23 | drawLabel(ctx,map,"Partial Edge",limitNorthP); 24 | } 25 | 26 | if(eclipseData.durationLines!=null && document.getElementById("showDurationLinesCheckbox").checked){ 27 | for(let i=0;i=7){ 31 | ctx.font = ".75em Georgia"; 32 | ctx.textBaseline="top"; 33 | drawLabel(ctx,map,""+(i+1)+" minute",l1); 34 | ctx.textBaseline="bottom"; 35 | drawLabel(ctx,map,""+(i+1)+" minute",l2); 36 | } 37 | 38 | } 39 | } 40 | 41 | 42 | } 43 | 44 | function drawLabel(ctx,map,text,points){ 45 | const o=map._getMapPanePos(); 46 | const o1=map.getPixelOrigin(); 47 | const p1=map.project(points.first); 48 | const p2=map.project(points.last); 49 | 50 | const x1=p1.x+(o.x-o1.x); 51 | const x2=p2.x+(o.x-o1.x); 52 | const y1=p1.y+(o.y-o1.y); 53 | const y2=p2.y+(o.y-o1.y); 54 | 55 | const angle=Math.atan2(y2-y1,x2-x1); 56 | 57 | ctx.save(); 58 | ctx.translate(x1,y1); 59 | ctx.rotate(angle); 60 | 61 | ctx.fillText(text,0,0); 62 | 63 | ctx.restore() 64 | } 65 | 66 | 67 | function drawLineOverlay(ctx,map,points,options){ 68 | const bounds=map.getBounds(); 69 | const n=bounds._northEast.lat; 70 | const e=bounds._northEast.lng; 71 | const s=bounds._southWest.lat; 72 | const w=bounds._southWest.lng; 73 | 74 | let firstPoint=null; 75 | let lastPoint=null; 76 | 77 | const o=map._getMapPanePos(); 78 | const o1=map.getPixelOrigin(); 79 | 80 | ctx.beginPath(); 81 | ctx.strokeStyle=options.color; 82 | ctx.lineWidth=options.weight; 83 | 84 | const p=map.project(points[0]); 85 | ctx.moveTo(p.x+(o.x-o1.x),p.y+(o.y-o1.y)); 86 | 87 | for(let i=1;i=s && t.lng<=e && t.lng>=w){ 90 | if(firstPoint==null) firstPoint=i; 91 | lastPoint=i; 92 | const p=map.project(points[i]); 93 | ctx.lineTo(p.x+(o.x-o1.x),p.y+(o.y-o1.y)); 94 | } 95 | } 96 | ctx.stroke(); 97 | const mid=Math.floor((lastPoint-firstPoint)/2)+firstPoint; 98 | return {first: points[mid], last: points[mid+1]}; 99 | } 100 | -------------------------------------------------------------------------------- /WebGLEarth/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 17 | WebGL Earth API: Hello World example 18 | 19 | 20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /WebGLEarth/viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Solar Eclipse Map 5 | 6 | 7 | 92 | 93 | 94 |
Alpha application under active development, will contain bugs.
95 |
96 | 99 | 100 | 103 | 104 | 105 | 106 |
107 | 108 | 109 | 110 | 111 |
112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
Time Zone:
Lat:
Lon:
Sun Alt:
Magnitude:
Partial Start:
Partial End:
Partial Duration:
Totality Start:
Maximum Eclipse:
Totality End:
Totality Duration:
127 |
128 |
129 |
130 |
131 | 132 |
133 |
134 |
135 | 136 | 137 | 426 | 427 | -------------------------------------------------------------------------------- /alarm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/alarm.wav -------------------------------------------------------------------------------- /astro.js: -------------------------------------------------------------------------------- 1 | export function hoursToTime(hours,tz){ 2 | hours+=tz.offset/60/60; //Convert to EDT 3 | let h=Math.trunc(hours); 4 | let m=(hours-h)*60; 5 | let s=Math.trunc((m-Math.trunc(m))*60); 6 | m=Math.trunc(m); 7 | 8 | if(h<10){h="0"+h;} 9 | if(m<10){m="0"+m;} 10 | if(s<10){s="0"+s;} 11 | 12 | let ap="am"; 13 | if(h>=12) ap="pm"; 14 | if(h>=13) h-=12; 15 | return h+":"+m+":"+s+" "+ap; 16 | } 17 | 18 | export function prettyTime(hours){ 19 | let sign=""; 20 | if(hours<0){ 21 | sign="-"; 22 | hours=-hours; 23 | } 24 | let days=0; 25 | if (hours>24){ 26 | days=Math.trunc(hours/24); 27 | hours-=days*24; 28 | } 29 | let h=Math.trunc(hours); 30 | let m=(hours-h)*60; 31 | let s=((m-Math.trunc(m))*60).toFixed(1); 32 | m=Math.trunc(m); 33 | 34 | if(h>0 || days >0){ 35 | if(s<10) s="0"+s; 36 | if(m<10) m="0"+m; 37 | } 38 | if(days>0){ 39 | if(h<10) h="0"+h; 40 | } 41 | 42 | let t=s+"s"; 43 | if(m!=0) t=m+"m "+t; 44 | if(h!=0 || days >0) t=h+"h "+t; 45 | if(days>0) t=days+"d "+t; 46 | return sign+t; 47 | 48 | } 49 | 50 | export function convertTDBToUTC(jd_tdb){ 51 | //Conversion already performed using deltaT in eclipse module 52 | throw("TDB to UTC Not needed"); 53 | const jd_tt = jd_tdb; //differenece in TT is negligable 54 | const jd_tai = jd_tt - 32.184 / 60 / 60 / 24; 55 | const jd_utc = jd_tai - getLeapSeconds(jd_tai) / 60 / 60 / 24; 56 | 57 | return jd_utc; 58 | } 59 | 60 | export function getLeapSeconds(jd) { 61 | //Source IERS Resolution B1 and http://maia.usno.navy.mil/ser7/tai-utc.dat 62 | //This function must be updated any time a new leap second is introduced 63 | 64 | if (jd > 2457754.5) return 37.0; 65 | if (jd > 2457204.5) return 36.0; 66 | if (jd > 2456109.5) return 35.0; 67 | if (jd > 2454832.5) return 34.0; 68 | if (jd > 2453736.5) return 33.0; 69 | if (jd > 2451179.5) return 32.0; 70 | if (jd > 2450630.5) return 31.0; 71 | if (jd > 2450083.5) return 30.0; 72 | if (jd > 2449534.5) return 29.0; 73 | if (jd > 2449169.5) return 28.0; 74 | if (jd > 2448804.5) return 27.0; 75 | if (jd > 2448257.5) return 26.0; 76 | if (jd > 2447892.5) return 25.0; 77 | if (jd > 2447161.5) return 24.0; 78 | if (jd > 2446247.5) return 23.0; 79 | if (jd > 2445516.5) return 22.0; 80 | if (jd > 2445151.5) return 21.0; 81 | if (jd > 2444786.5) return 20.0; 82 | if (jd > 2444239.5) return 19.0; 83 | if (jd > 2443874.5) return 18.0; 84 | if (jd > 2443509.5) return 17.0; 85 | if (jd > 2443144.5) return 16.0; 86 | if (jd > 2442778.5) return 15.0; 87 | if (jd > 2442413.5) return 14.0; 88 | if (jd > 2442048.5) return 13.0; 89 | if (jd > 2441683.5) return 12.0; 90 | if (jd > 2441499.5) return 11.0; 91 | if (jd > 2441317.5) return 10.0; 92 | if (jd > 2439887.5) return 4.21317 + (jd - 2439126.5) * 0.002592; 93 | if (jd > 2439126.5) return 4.31317 + (jd - 2439126.5) * 0.002592; 94 | if (jd > 2439004.5) return 3.84013 + (jd - 2438761.5) * 0.001296; 95 | if (jd > 2438942.5) return 3.74013 + (jd - 2438761.5) * 0.001296; 96 | if (jd > 2438820.5) return 3.64013 + (jd - 2438761.5) * 0.001296; 97 | if (jd > 2438761.5) return 3.54013 + (jd - 2438761.5) * 0.001296; 98 | if (jd > 2438639.5) return 3.44013 + (jd - 2438761.5) * 0.001296; 99 | if (jd > 2438486.5) return 3.34013 + (jd - 2438761.5) * 0.001296; 100 | if (jd > 2438395.5) return 3.24013 + (jd - 2438761.5) * 0.001296; 101 | if (jd > 2438334.5) return 1.945858 + (jd - 2437665.5) * 0.0011232; 102 | if (jd > 2437665.5) return 1.845858 + (jd - 2437665.5) * 0.0011232; 103 | if (jd > 2437512.5) return 1.372818 + (jd - 2437300.5) * 0.001296; 104 | if (jd > 2437300.5) return 1.422818 + (jd - 2437300.5) * 0.001296; 105 | return 0.0; 106 | } 107 | 108 | export function JulianDateFromUnixTime(t){ 109 | //Not valid for dates before Oct 15, 1582 110 | return (t / 86400000) + 2440587.5; 111 | } 112 | 113 | export function UnixTimeFromJulianDate(jd){ 114 | //Not valid for dates before Oct 15, 1582 115 | return (jd-2440587.5)*86400000; 116 | } -------------------------------------------------------------------------------- /backgroundworker.js: -------------------------------------------------------------------------------- 1 | 2 | import {getLocalCircumstances} from "./eclipse.js"; 3 | 4 | function findNorthLatForDuration(lon,max,min,target){ 5 | let result=9999; 6 | let count=0; 7 | let total; 8 | 9 | while(((max-min)>.00001) && count<10000){ 10 | const guess=(max-min)/2+min; 11 | const c=getLocalCircumstances(guess, lon,0); 12 | total=(c.UTThirdContact-c.UTSecondContact)*60; 13 | 14 | result=guess; 15 | if(result==target){ 16 | max=min; 17 | } else if(total>target){ 18 | min=guess; 19 | } else { 20 | max=guess; 21 | } 22 | count++; 23 | } 24 | if(total+.01.00001) && count<10000){ 34 | const guess=(max-min)/2+min; 35 | const c=getLocalCircumstances(guess, lon,0); 36 | total=(c.UTThirdContact-c.UTSecondContact)*60; 37 | 38 | result=guess; 39 | if(result==target){ 40 | max=min; 41 | } else if(total=0 && cos>=0){return Math.asin(sin);} 27 | if(sin<0 && cos>=0){return Math.asin(sin);} 28 | if(sin<0 && cos<0){return -Math.acos(cos);} 29 | if(sin>=0 && cos<0){return Math.acos(cos);} 30 | } 31 | export function getElementCoeffs(){ 32 | return getElements2024(); 33 | //return getElements2017(); 34 | //return getElements1994(); 35 | //return getElements1996(); 36 | } 37 | 38 | function getElements2017(){ 39 | const elements={}; 40 | elements.jd=2457987.5; 41 | elements.Δt=69.1; 42 | elements.T0=18; 43 | 44 | elements.X0=-0.1295710 ; 45 | elements.X1= 0.5406426 ; 46 | elements.X2=-0.0000294 ; 47 | elements.X3=-0.0000081; 48 | 49 | elements.Y0=0.4854160 ; 50 | elements.Y1=-0.1416400; 51 | elements.Y2=-0.0000905; 52 | elements.Y3= 0.0000020; 53 | 54 | elements.d0=11.8669596; 55 | elements.d1=-0.0136220; 56 | elements.d2=-0.0000020; 57 | elements.d3=0.0000000; 58 | 59 | elements.L10=0.5420930; 60 | elements.L11=0.0001241; 61 | elements.L12=-0.0000118; 62 | elements.L13=0.0000000; 63 | 64 | elements.L20=-0.0040250; 65 | elements.L21=0.0001234 ; 66 | elements.L22=-0.0000117 ; 67 | elements.L23=0.0000000 ; 68 | 69 | elements.M0=89.245430; 70 | elements.M1=15.003940; 71 | elements.M2=0.000000; 72 | elements.M3=0.000000; 73 | 74 | elements.tanf1 = 0.0046222; 75 | elements.tanf2 = 0.0045992; 76 | 77 | return elements; 78 | 79 | } 80 | 81 | function getElements2024_2(){ 82 | //Agrees closely with 83 | //https://ssp.imcce.fr/forms/solar-eclipses/2024-04-08/local-circumstances#map=13.05/38.63866/-85.78188&observer=38.64866465157928/-85.78373848702137 84 | const elements={}; 85 | elements.jd=2460408.5; 86 | elements.Δt=69.1; 87 | elements.T0=18; 88 | 89 | elements.X0=-0.3182588; 90 | elements.X1=0.5117224; 91 | elements.X2=0.0000330; 92 | elements.X3=-0.0000085; 93 | 94 | elements.Y0=0.2197690; 95 | elements.Y1=0.2709652; 96 | elements.Y2=-0.0000592; 97 | elements.Y3=-0.0000047; 98 | 99 | elements.d0=7.5861809; 100 | elements.d1=0.0148443; 101 | elements.d2=-0.0000017; 102 | elements.d3=0.0000000; 103 | 104 | elements.L10=0.5357259; 105 | elements.L11=0.0000620; 106 | elements.L12=-0.0000128; 107 | elements.L13=0.0000000; 108 | 109 | elements.L20=-0.0106071; 110 | elements.L21=0.0000617; 111 | elements.L22=-0.0000127; 112 | elements.L23=0.0000000; 113 | 114 | elements.M0=89.5912142; 115 | elements.M1=15.0040824; 116 | elements.M2=-0.0000008; 117 | elements.M3=0.0000000; 118 | 119 | elements.tanf1 = 0.0046663; 120 | elements.tanf2 = 0.0046430; 121 | 122 | return elements; 123 | } 124 | 125 | function getElements2024(){ 126 | const elements={}; 127 | elements.jd=2460408.5; 128 | elements.Δt=69.1; 129 | elements.T0=18; 130 | 131 | elements.X0=-0.3182440; 132 | elements.X1=0.5117116; 133 | elements.X2=0.0000326; 134 | elements.X3=-0.0000084; 135 | 136 | elements.Y0=0.2197640; 137 | elements.Y1=0.2709589; 138 | elements.Y2=-0.0000595; 139 | elements.Y3=-0.0000047; 140 | 141 | elements.d0=7.5862002; 142 | elements.d1=0.0148440; 143 | elements.d2=-0.0000020; 144 | elements.d3=0.0000000; 145 | 146 | elements.L10=0.5358140; 147 | elements.L11=0.0000618; 148 | elements.L12=-0.0000128; 149 | elements.L13=0.0000000; 150 | 151 | elements.L20=-0.0102720; 152 | elements.L21=0.0000615; 153 | elements.L22=-0.0000127; 154 | elements.L23=0.0000000; 155 | 156 | elements.M0=89.591217; 157 | elements.M1=15.004080; 158 | elements.M2=0.000000; 159 | elements.M3=0.000000; 160 | 161 | elements.tanf1 = 0.0046683; 162 | elements.tanf2 = 0.0046450; 163 | 164 | return elements; 165 | 166 | } 167 | 168 | function getElements1996(){ 169 | //Example data for Oct 12, 1996 170 | const elements={}; 171 | elements.Δt=63; 172 | elements.T0=14; 173 | 174 | elements.X0=0.296103; 175 | elements.X1=0.5060364; 176 | elements.X2=0.0000145; 177 | elements.X3=-0.00000644; 178 | 179 | elements.Y0=1.083058; 180 | elements.Y1=-0.1515218; 181 | elements.Y2=-0.0000102; 182 | elements.Y3=0.00000184; 183 | 184 | elements.d0=-7.63950; 185 | elements.d1=-0.015234; 186 | elements.d2=0.000002; 187 | 188 | elements.M0=33.40582; 189 | elements.M1=15.003782; 190 | 191 | elements.L10=0.559341; 192 | elements.L11=-0.0001067; 193 | elements.L12=-0.0000107; 194 | 195 | elements.L20=0.013151; 196 | elements.L21=-0.0001062; 197 | elements.L22=-0.0000106; 198 | 199 | elements.tanf1=0.0046865; 200 | elements.tanf2=0.0046632; 201 | 202 | return elements; 203 | } 204 | 205 | export function getElements1994(){ 206 | //Example data for May 10, 1994 207 | const elements={}; 208 | elements.Δt=61; 209 | elements.T0=17; 210 | 211 | elements.X0=-0.173367; 212 | elements.X1=0.4990629; 213 | elements.X2=0.0000296; 214 | elements.X3=-0.00000563; 215 | 216 | elements.Y0=0.383484; 217 | elements.Y1=0.0869393; 218 | elements.Y2=-0.0001183; 219 | elements.Y3=-0.00000092; 220 | 221 | elements.d0=17.68613; 222 | elements.d1=0.010642; 223 | elements.d2=-0.000004; 224 | 225 | elements.M0=75.90923; 226 | elements.M1=15.001621; 227 | 228 | elements.L10=0.566906; 229 | elements.L11=-0.0000318; 230 | elements.L12=-0.0000098; 231 | 232 | elements.L20=0.020679; 233 | elements.L21=-0.0000317; 234 | elements.L22=-0.0000097; 235 | 236 | elements.tanf1=0.0046308; 237 | elements.tanf2=0.0046077; 238 | 239 | return elements; 240 | } 241 | 242 | function getElements(e,t,Φ,λ,height){ 243 | //From Meeus - Elements of Solar Eclipses 244 | const o={}; 245 | o.X=e.X0 + e.X1*t + e.X2*t*t + e.X3*t*t*t; 246 | o.Y=e.Y0 + e.Y1*t + e.Y2*t*t + e.Y3*t*t*t; 247 | o.d=e.d0 + e.d1*t + e.d2*t*t; 248 | o.M=e.M0 + e.M1*t; 249 | o.Xp=e.X1 + 2*e.X2*t + 3*e.X3*t*t; 250 | o.Yp=e.Y1 + 2*e.Y2*t + 3*e.Y3*t*t; 251 | o.Mp=e.M1; 252 | o.L1=e.L10 + e.L11*t + e.L12*t*t; 253 | o.L2=e.L20 + e.L21*t + e.L22*t*t; 254 | o.tanf1=e.tanf1; 255 | o.tanf2=e.tanf2; 256 | 257 | o.H = o.M - λ - 0.00417807 * e.Δt; 258 | 259 | o.u1 = Math.atan(0.99664719*Math.tan(Φ*rad))/rad; 260 | o.ρsinΦp=0.99664719 * Math.sin(o.u1*rad)+height/6378140*Math.sin(Φ*rad); 261 | o.ρcosΦp=Math.cos(o.u1*rad) + height/6378140 * Math.cos(Φ*rad); 262 | 263 | o.ξ = o.ρcosΦp * Math.sin(o.H*rad); 264 | o.η = o.ρsinΦp * Math.cos(o.d*rad) - o.ρcosΦp * Math.cos(o.H*rad) * Math.sin(o.d*rad); 265 | o.ζ = o.ρsinΦp * Math.sin(o.d*rad) + o.ρcosΦp * Math.cos(o.H*rad) * Math.cos(o.d*rad); 266 | o.ξp = 0.01745329 * e.M1 * o.ρcosΦp * Math.cos(o.H*rad); 267 | o.ηp = 0.01745329 * (e.M1 * o.ξ * Math.sin(o.d*rad) - o.ζ * e.d1); 268 | o.L1p = o.L1 - o.ζ * e.tanf1; 269 | o.L2p = o.L2 - o.ζ * e.tanf2; 270 | 271 | o.u = o.X - o.ξ; 272 | o.v = o.Y - o.η; 273 | o.a = o.Xp - o.ξp; 274 | o.b = o.Yp - o.ηp; 275 | o.n = Math.sqrt(o.a*o.a + o.b*o.b); 276 | 277 | return o; 278 | 279 | } 280 | 281 | export function getLocalCircumstances(Φ,λ,height){ 282 | //From Meeus - Elements of Solar Eclipses 283 | const e=getElementCoeffs(); 284 | λ=-λ; 285 | 286 | let t=0; 287 | let τm=10000; 288 | 289 | let iterations=0; 290 | let o; 291 | while(Math.abs(τm)>.00001 && iterations 90){ 571 | Φ1 = 180 - β + d1; 572 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*raD))/rad; 573 | λ = - (M - 180 - 0.00417807 * e.Δt); 574 | } else if(β + d1 < -90){ 575 | Φ1 = -(180 + β + d1); 576 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*rad))/rad; 577 | λ = - (M + 180 - 0.00417807 * e.Δt); 578 | } else { 579 | Φ1 = β + d1; 580 | Φ = Math.atan(1.00336409 * Math.tan(Φ1*rad))/rad; 581 | λ = - (M - 0.00417807 * e.Δt); 582 | } 583 | if(λ<-180){λ+=360;} 584 | 585 | const UT = e.T0 + t - e.Δt/60/60; 586 | return {UT: UT, lat: Φ, lon: λ}; 587 | } 588 | 589 | /* 590 | * Determines the center line at a given longitude for the particular eclipse 591 | * Inputs: 592 | * e: Local Circumstances at the given time 593 | * λ: Longitude where one wishes to find the center line 594 | */ 595 | export function getCenterLineByLongitude(e,λ){ 596 | return getLimitsForLogitude(e,λ,0,1,0); 597 | } 598 | 599 | /* 600 | * Determines totality limits for a range of longitudes 601 | * Inputs: 602 | * e: Local Circumstances at the given time 603 | * northsouth: Degrees north/south 604 | * startLon: Start Longitude 605 | * endLon: End Longitude 606 | */ 607 | export function getTotalityLimitsByLongitudeList(e,northsouth,startLon,endLon){ 608 | return getLimitsByLongitudeAsList(e,northsouth,1,startLon,endLon); 609 | } 610 | 611 | /* 612 | * Determines partial limits for a range of longitudes 613 | * Inputs: 614 | * e: Local Circumstances at the given time 615 | * northsouth: Degrees north/south 616 | * startLon: Start Longitude 617 | * endLon: End Longitude 618 | */ 619 | export function getPartialLimitsByLogitudeList(e,northsouth,startLon,endLon){ 620 | return getLimitsByLongitudeAsList(e,northsouth,0,startLon,endLon); 621 | } 622 | 623 | function getLimitsByLongitudeAsList(e,northsouth,G,startLon,endLon){ 624 | const eqPoints=new Array(); 625 | const polarPoints=new Array(); 626 | 627 | for(let i=startLon;i<=endLon;i+=.01){ 628 | const eq=getLimitsForLogitude(e,i,northsouth,G,0); 629 | const polar=getLimitsForLogitude(e,i,northsouth,G,89.9 * Math.sign(e.Y0)); 630 | 631 | if(polar!=null && eq!=null){ 632 | if(Math.abs(eq.lat-polar.lat)<.1){ 633 | eqPoints.push(eq); 634 | } else { 635 | eqPoints.push(eq); 636 | polarPoints.push(polar); 637 | } 638 | } else { 639 | eqPoints.push(null); 640 | polarPoints.push(null); 641 | } 642 | } 643 | 644 | return [eqPoints,polarPoints]; 645 | } 646 | 647 | function getLimitsForLogitude(e,λ,northsouth,G,startΦ){ 648 | //From Meeus - Elements of Solar Eclipses 649 | let t=0; 650 | let Φ=startΦ; 651 | 652 | let i=0; 653 | let ΔΦ=1000; 654 | let τ=1000; 655 | while((Math.abs(τ)>.0001 || Math.abs(ΔΦ)>.0001) && i<20){ 656 | const X=e.X0 + e.X1*t + e.X2*t*t + e.X3*t*t*t; 657 | const Y=e.Y0 + e.Y1*t + e.Y2*t*t + e.Y3*t*t*t; 658 | const d=e.d0 + e.d1*t + e.d2*t*t; 659 | const M=e.M0 + e.M1*t; 660 | const Xp=e.X1 + 2*e.X2*t + 3*e.X3*t*t; 661 | const Yp=e.Y1 + 2*e.Y2*t + 3*e.Y3*t*t; 662 | const L1=e.L10 + e.L11*t + e.L12*t*t; 663 | const L2=e.L20 + e.L21*t + e.L22*t*t; 664 | 665 | const H = M + λ - 0.00417807 * e.Δt; 666 | 667 | const height=0; 668 | const u1 = Math.atan(0.99664719*Math.tan(Φ*rad))/rad; 669 | const ρsinΦp=0.99664719 * Math.sin(u1*rad)+height/6378140*Math.sin(Φ*rad); 670 | const ρcosΦp=Math.cos(u1*rad) + height/6378140 * Math.cos(Φ*rad); 671 | 672 | const ξ = ρcosΦp * Math.sin(H*rad); 673 | const η = ρsinΦp * Math.cos(d*rad) - ρcosΦp * Math.cos(H*rad) * Math.sin(d*rad); 674 | const ζ = ρsinΦp * Math.sin(d*rad) + ρcosΦp * Math.cos(H*rad) * Math.cos(d*rad); 675 | const ξp = 0.01745329 * e.M1 * ρcosΦp * Math.cos(H*rad); 676 | const ηp = 0.01745329 * (e.M1 * ξ * Math.sin(d*rad) - ζ * e.d1); 677 | const L1p = L1 - ζ * e.tanf1; 678 | const L2p = L2 - ζ * e.tanf2; 679 | 680 | const u = X - ξ; 681 | const v = Y - η; 682 | const a = Xp - ξp; 683 | const b = Yp - ηp; 684 | const n = Math.sqrt(a*a + b*b); 685 | 686 | τ = - (u*a + v*b)/(n*n); 687 | const W = (v*a - u*b)/n; 688 | const Q = ((b * Math.sin(H*rad) * ρsinΦp + a*(Math.cos(H*rad) * Math.sin(d*rad) * ρsinΦp + Math.cos(d*rad)*ρcosΦp))) 689 | / (57.29578 * n); 690 | 691 | //northsouth = 1 for northnet limit, -1 for southern limit 692 | const E=L1p - G * (L1p + L2p); 693 | ΔΦ = (W + northsouth * Math.abs(E))/Q; 694 | 695 | t = t + τ; 696 | Φ = Φ + ΔΦ; 697 | i++; 698 | } 699 | if (Math.abs(τ)>.0001 || Math.abs(ΔΦ)>.0001){return null;} 700 | 701 | const UT= e.T0 + t; 702 | 703 | Φ=(90+Φ)%180; 704 | if(Φ<0)Φ+=180; 705 | Φ-=90; 706 | return {t: t, lat: Φ, lon: λ}; 707 | } 708 | 709 | function computeOutlinePoint(be,Q,umbra){ 710 | //The Explanatory Supplement to the Astronomical Ephemeris 1961 711 | //Prediction and Analysis of Solar Eclipse Circumstances - Williams 1971 712 | //P220 713 | const e=Math.sqrt(0.00672267); 714 | const sind=Math.sin(be.d*rad); 715 | const cosd=Math.cos(be.d*rad); 716 | const ρ1=Math.sqrt(1-e*e*cosd*cosd); 717 | const ρ2=Math.sqrt(1-e*e*sind*sind); 718 | const sind1=sind/ρ1; 719 | const cosd1=Math.sqrt(1-e*e)*cosd/ρ1; 720 | 721 | const sind1d2=e*e*sind*cosd/(ρ1*ρ2); //P220 (bottom) 722 | const cosd1d2=Math.sqrt(1-e*e)/(ρ1*ρ2); 723 | 724 | Q*=rad; 725 | 726 | const sinQ=Math.sin(Q); 727 | const cosQ=Math.cos(Q); 728 | 729 | let tanf=be.tanf1; 730 | let l=be.l1; 731 | if(umbra){ 732 | l=be.l2; 733 | tanf=be.tanf2; 734 | } 735 | 736 | let ξ=be.x - l*sinQ; 737 | let η=(be.y - l*cosQ)/ρ1; 738 | let ζ1=Math.sqrt(1 - ξ*ξ - η*η); 739 | 740 | let ζ=ρ2*(ζ1*cosd1d2 - η*sind1d2); 741 | let L = l - ζ*tanf; 742 | 743 | ξ=be.x - L*sinQ; 744 | η=(be.y - L*cosQ)/ρ1; 745 | ζ1=Math.sqrt(1 - ξ*ξ - η*η); 746 | 747 | //for(let i=0;i<100;i++){ 748 | ζ=ρ2*(ζ1*cosd1d2 - η*sind1d2); 749 | L = l - ζ*tanf; 750 | 751 | ξ=be.x - L*sinQ; 752 | η=(be.y - L*cosQ)/ρ1; 753 | ζ1=Math.sqrt(1 - ξ*ξ - η*η); 754 | //} 755 | 756 | const cosϕ1sinθ=ξ; 757 | const cosϕ1cosθ=ζ1*cosd1 - η*sind1; 758 | 759 | const θ=Math.atan2(cosϕ1sinθ,cosϕ1cosθ)/rad; //C.51 760 | 761 | let λ=be.H - θ; //C.53, C.22 762 | const ϕ1=Math.asin(η*cosd1 + ζ1*sind1); //C.52 763 | let ϕ=Math.atan((1/Math.sqrt(1-e*e))*Math.tan(ϕ1))/rad; //C.54 764 | if(ϕ>90) ϕ-=180; 765 | if(ϕ<-90) ϕ+=180; 766 | 767 | if(λ>180) λ=λ-360; 768 | return {lat: ϕ, lon: -λ}; 769 | } 770 | 771 | function propperAngle(d){ 772 | //return d; 773 | let t=d; 774 | if(t<0){t+=360;} 775 | if(t>=360) {t-=360;} 776 | return t; 777 | } 778 | 779 | function getOutlineCurveQRange(be,l){ 780 | //Exp Sup 1961 781 | const msq=be.x*be.x + be.y*be.y; //Derived from x=mSinM, y=mCosM p228 782 | const m=Math.sqrt(msq); 783 | const tanM=be.x/be.y; //Derived from x=mSinM, y=mCosM p228 784 | const M=Math.atan2(be.x,be.y); 785 | const denom=2*l*m; 786 | const numer=m*m + l*l - 1; 787 | const cosQM=(m*m + l*l - 1)/(2*l*m); 788 | let Q1=propperAngle((Math.acos(cosQM)+M)/rad); 789 | let Q2=propperAngle((-Math.acos(cosQM)+M)/rad); 790 | 791 | //TODO: This probably only works for the 2024 eclipse 792 | if(isNaN(Q1) || isNaN(Q2)){ 793 | Q2=0; 794 | Q1=360; 795 | } 796 | 797 | if(Q1maxlat){ maxlat=p[0].lat; nStart=p[0].lon;} 885 | if(p[0].latmaxlat){ maxlat=p[0].lat; nEnd=p[0].lon;} 909 | if(p[0].latmaxlat){ maxlat=p[1].lat; nEnd=p[1].lon;} 914 | if(p[1].lat=4){ 18 | ctx.font = "1em Georgia"; 19 | ctx.textAlign="center"; 20 | ctx.textBaseline="bottom"; 21 | drawLabel(ctx,map,"Totality Center Line",central); 22 | 23 | ctx.textBaseline="top"; 24 | drawLabel(ctx,map,"Partial Edge",limitSouthP); 25 | drawLabel(ctx,map,"Totality Edge",limitSouthT); 26 | 27 | ctx.textBaseline="bottom"; 28 | drawLabel(ctx,map,"Totality Edge",limitNorthT); 29 | drawLabel(ctx,map,"Partial Edge",limitNorthP); 30 | } 31 | 32 | if(eclipseData.durationLines!=null && document.getElementById("showDurationLinesCheckbox").checked){ 33 | for(let i=0;i=7){ 37 | ctx.font = ".75em Georgia"; 38 | ctx.textBaseline="top"; 39 | drawLabel(ctx,map,""+(i+1)+" minute",l1); 40 | ctx.textBaseline="bottom"; 41 | drawLabel(ctx,map,""+(i+1)+" minute",l2); 42 | } 43 | 44 | } 45 | } 46 | } 47 | 48 | function drawLabel(ctx,map,text,points){ 49 | const o=map._getMapPanePos(); 50 | const o1=map.getPixelOrigin(); 51 | const p1=map.project(points.first); 52 | const p2=map.project(points.last); 53 | 54 | const x1=p1.x+(o.x-o1.x); 55 | const x2=p2.x+(o.x-o1.x); 56 | const y1=p1.y+(o.y-o1.y); 57 | const y2=p2.y+(o.y-o1.y); 58 | 59 | const angle=Math.atan2(y2-y1,x2-x1); 60 | //ctx.strokeStyle="rgba(255,255,255,.75)"; 61 | 62 | ctx.save(); 63 | ctx.translate(x1,y1); 64 | ctx.rotate(angle); 65 | 66 | //ctx.strokeText(text,0,0); 67 | ctx.fillText(text,0,0); 68 | 69 | ctx.restore() 70 | } 71 | 72 | function drawLineOverlay(ctx,map,points,options){ 73 | const bounds=map.getBounds(); 74 | const n=bounds._northEast.lat+.01; 75 | const e=bounds._northEast.lng+.01; 76 | const s=bounds._southWest.lat-.01; 77 | const w=bounds._southWest.lng-.01; 78 | 79 | let firstPoint=null; 80 | let lastPoint=null; 81 | 82 | const o=map._getMapPanePos(); 83 | const o1=map.getPixelOrigin(); 84 | 85 | ctx.beginPath(); 86 | ctx.strokeStyle=options.color; 87 | ctx.lineWidth=options.weight; 88 | 89 | const p=map.project(points[0]); 90 | ctx.moveTo(p.x+(o.x-o1.x),p.y+(o.y-o1.y)); 91 | 92 | for(let i=1;i=s && t.lng<=e && t.lng>=w){ 95 | if(firstPoint==null) firstPoint=i; 96 | lastPoint=i; 97 | const p=map.project(points[i]); 98 | ctx.lineTo(p.x+(o.x-o1.x),p.y+(o.y-o1.y)); 99 | } 100 | } 101 | ctx.stroke(); 102 | const mid=Math.floor((lastPoint-firstPoint)/2)+firstPoint; 103 | return {first: points[mid], last: points[mid+1]}; 104 | } 105 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | Standalone countdown timer 2 | Save markers in local storage 3 | Jitter on penumbra shadow animation 4 | 5 | Weather 6 | Cloud forecast overlay (display % in info list) 7 | 8 | Search for major cities 9 | Window resize doesn't work well - Appears to be an issue with map bounds checking 10 | Allow link to location, time, eclipse, etc (and Time Zone) 11 | Time zone lookup fails after crossing the international date line 12 | Account for partial limit crossing pole 13 | Rise and set curves need to account for Earth flattening 14 | 15 | -------------------------------------------------------------------------------- /tzmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmiller123456/solareclipseviewer/5c1cc8d1fc0f8d7902f15eca821e3e941d62dc65/tzmap.png -------------------------------------------------------------------------------- /viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Solar Eclipse Map 5 | 6 | 7 | 8 | 9 | 158 | 159 | 160 |
161 | 164 | 165 | 168 | 169 | 170 |
171 | 172 | 173 | 174 | 175 |
176 |
177 |
178 | 179 |
180 | 181 |
182 |
183 |
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
Time Zone:
Lat:
Lon:
Sun Alt:
Magnitude:
Partial Start:
Partial End:
Partial Duration:
Totality Start:
Maximum Eclipse:
Totality End:
Totality Duration:
198 |
199 |
200 |
201 |
202 |
203 | 204 | 205 | 206 | 207 | 208 |

209 |
210 |
211 |
212 |
213 | 214 | 215 | 216 | 217 | 218 |
Partial Start:
Totality Start:
Totality End:
Partial End:
219 |

220 |
221 | 222 | 223 | 224 | 225 |
226 |

227 | Simulation Mode 228 | 236 |
237 |
238 |
239 | 240 | 254 | 266 | 267 | --------------------------------------------------------------------------------