1window.addEventListener("DOMContentLoaded", function () { 2 window.addEventListener("offline", function () { 3 const container = document.querySelector(".container"); 4 container.innerHTML = 5 '<H1>NO INTERNET</H1>'; 6 }); 7 if (!navigator.onLine) { 8 const container = document.querySelector(".container"); 9 container.innerHTML = '<h1>NO INTERNET</h1> <button id="refresh-button" onClick="refreshPage()" class="buttoncurrent ghost tiempo">Refresh Page</button>'; 10 } 11 12 13 let sourceMarker; 14 let destinationMarker; 15 var sourceIcon = L.icon({ 16 iconUrl: "source.png", 17 iconSize: [32, 32], 18 }); 19 var destinationIcon = L.icon({ 20 iconUrl: "destination.png", 21 iconSize: [32, 32], 22 }); 23 24 fetchCurrentTime(); 25 setupAutocomplete("source", { delay: 500 }); 26 setupAutocomplete("destination", { delay: 500 }); 27 28 const map = initializeMap(); 29 30 var myAttrControl = L.control.attribution().addTo(map); 31 myAttrControl.setPrefix('<a href="https://leafletjs.com/">Leaflet</a>'); 32 33 const fetchpage = sessionStorage.getItem("fetchpage"); 34 console.log(fetchpage); 35 36 if (fetchpage == "true") { 37 38 39 const sourceLocation = sessionStorage.getItem("sourceLocation"); 40 const destinationLocation = sessionStorage.getItem("destinationLocation"); 41 const date = sessionStorage.getItem("date"); 42 const time = sessionStorage.getItem("time"); 43 44 // Retrieve the saved marker coordinates 45 const sourceMarkerLat = sessionStorage.getItem("sourceMarkerLat"); 46 const sourceMarkerLng = sessionStorage.getItem("sourceMarkerLng"); 47 const destMarkerLat = sessionStorage.getItem("destMarkerLat"); 48 const destMarkerLng = sessionStorage.getItem("destMarkerLng"); 49 50 if (sourceLocation) { 51 document.getElementById("source").value = sourceLocation; 52 } 53 if (destinationLocation) { 54 document.getElementById("destination").value = destinationLocation; 55 } 56 if (date) { 57 document.getElementById("date").value = date; 58 } 59 if (time) { 60 document.getElementById("time").value = time; 61 } 62 if (sourceMarkerLat && sourceMarkerLng) { 63 sourceMarker = L.marker([parseFloat(sourceMarkerLat), parseFloat(sourceMarkerLng)], { 64 title: sourceLocation, 65 draggable: true, 66 icon: sourceIcon, 67 }); 68 sourceMarker.addTo(map).bindPopup(sourceLocation); 69 } 70 if (destMarkerLat && destMarkerLng) { 71 destinationMarker = L.marker([parseFloat(destMarkerLat), parseFloat(destMarkerLng)], { 72 title: destinationLocation, 73 draggable: true, 74 icon: destinationIcon, 75 }); 76 destinationMarker.addTo(map).bindPopup(destinationLocation); 77 } 78 } 79 80 document 81 .getElementById("submit-button") 82 .addEventListener("click", async function () { 83 84 const sourceLocation = document.getElementById("source").value; 85 const destinationLocation = document.getElementById("destination").value; 86 const date = document.getElementById("date").value; 87 const time = document.getElementById("time").value; 88 89 90 sessionStorage.setItem("sourceLocation", sourceLocation); 91 sessionStorage.setItem("destinationLocation", destinationLocation); 92 sessionStorage.setItem("date", date); 93 sessionStorage.setItem("time", time); 94 95 96 sessionStorage.setItem("sourceMarkerLat", sourceMarker.getLatLng().lat); 97 sessionStorage.setItem("sourceMarkerLng", sourceMarker.getLatLng().lng); 98 99 sessionStorage.setItem("destMarkerLat", destinationMarker.getLatLng().lat); 100 sessionStorage.setItem("destMarkerLng", destinationMarker.getLatLng().lng); 101 102 103 104 sessionStorage.setItem("fetchpage", false); 105 106 const sourceMarkerCoords = sourceMarker 107 ? sourceMarker.getLatLng() 108 : await geocodeAddress(document.getElementById("source").value); 109 const destMarkerCoords = destinationMarker 110 ? destinationMarker.getLatLng() 111 : await geocodeAddress(document.getElementById("destination").value); 112 113 if (sourceMarkerCoords && destMarkerCoords) { 114 initRouting(sourceMarkerCoords, destMarkerCoords);
115 const container = document.querySelector(".container"); 116 container.innerHTML = 117 '<div class="spinner"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div>'; 118 } 119 }); 120 function setupAutocomplete(inputId, options) { 121 const inputElement = document.getElementById(inputId); 122 123 new Autocomplete(inputId, { 124 delay: options.delay , 125 selectFirst: options.selectFirst || true, 126 howManyCharacters: options.howManyCharacters || 2, 127 128 onSearch: function ({ currentValue }) { 129 const photonApi = `https://photon.komoot.io/api/?q=${encodeURI(currentValue)}&limit=5`; 130 const nominatimApi = `https://nominatim.openstreetmap.org/search?format=geojson&limit=5&q=${encodeURI(currentValue)}`; 131 132 return new Promise((resolve) => { 133 fetch(photonApi) 134 .then((response) => response.json()) 135 .then((data) => { 136 if (data.features && data.features.length > 0) { 137 // Transform Photon results to match the expected format 138 const transformedFeatures = data.features.map(feature => ({ 139 type: "Feature", 140 geometry: feature.geometry, 141 properties: { 142 ...feature.properties, 143 display_name: [ 144 feature.properties.name, 145 feature.properties.state, 146 feature.properties.country 147 ].filter(Boolean).join(', ') 148 } 149 })); 150 resolve(transformedFeatures); 151 } else { 152 // If Photon returns no results, fall back to Nominatim 153 return fetch(nominatimApi); 154 } 155 }) 156 .then((response) => response ? response.json() : null) 157 .then((data) => { 158 if (data && data.features) { 159 resolve(data.features); 160 } else { 161 resolve([]); 162 } 163 }) 164 .catch((error) => { 165 console.error(error); 166 resolve([]); 167 }); 168 }); 169 }, 170 171 onResults: ({ currentValue, matches, template }) => { 172 const regex = new RegExp(currentValue, "i"); 173 return matches === 0 174 ? template 175 : matches 176 .map((element) => { 177 return ` 178 <li class="loupe" role="option"> 179 ${element.properties.display_name.replace(regex, (str) => `<b>${str}</b>`)} 180 </li> `; 181 }) 182 .join(""); 183 }, 184 185 onSubmit: ({ object }) => { 186 const displayName = object.properties.display_name; 187 const cord = object.geometry.coordinates; 188 const customId = Math.random(); 189 190 if (inputId === "source") { 191 if (sourceMarker) { 192 map.removeLayer(sourceMarker); 193 } 194 195 sourceMarker = L.marker([cord[1], cord[0]], { 196 title: displayName, 197 id: customId, 198 draggable: true, 199 icon: sourceIcon, 200 }); 201 sourceMarker.addTo(map).bindPopup(displayName); 202 } else if (inputId === "destination") { 203 if (destinationMarker) { 204 map.removeLayer(destinationMarker); 205 } 206 destinationMarker = L.marker([cord[1], cord[0]], { 207 title: displayName, 208 id: customId, 209 draggable: true, 210 icon: destinationIcon, 211 }); 212 destinationMarker.addTo(map).bindPopup(displayName); 213 } 214 map.panTo([cord[1], cord[0]]); 215 216 inputElement.value = displayName; 217 }, 218 219 onSelectedItem: ({ index, element, object }) => { 220 // console.log("onSelectedItem:", index, element, object); 221 }, 222 223 noResults: ({ currentValue, template }) => 224 template(`<li>No results found: "${currentValue}"</li>`), 225 }); 226 } 227 228 function initializeMap() { 229 const config = { 230 minZoom: 7, 231 maxZoom: 18, 232 }; 233 234 const zoom = 15; 235 const lat = 52.23397; 236 const lng = 21.01489; 237 238 const map = L.map("map", { attributionControl: false }
238).setView([lat, lng], zoom); 239 240 L.tileLayer( 241 // "https://tile.openstreetmap.org/{z}/{x}/{y}.png", 242 "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png?key=cb1_3vel_1_12acf6fb072e7b9124c21e4f", 243 { 244 attribution: 245 'Created by <a href="https://amithv.xyz">amithv.xyz</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/attributions">CARTO</a> ', 246 } 247 ).addTo(map); 248 249 return map; 250 } 251 252 async function initRouting(sourceLatLng, destLatLng) { 253 const date = document.getElementById("date").value; 254 const time = document.getElementById("time").value; 255 const timezone = document.getElementById("timezone").value; 256 var hours = parseInt(document.getElementById('hour-input').value); 257 var minutes = parseInt(document.getElementById('min-input').value); 258 259 // Calculate the total duration in minutes 260 console.log(hours, minutes); 261 let duration = (isNaN(hours) ? 0 : hours * 3600) + (isNaN(minutes) ? 0 : minutes * 60); 262 if (Number.isNaN(duration)) 263 duration = 0; 264 265 result = await callSunlightAPI( 266 sourceLatLng, 267 destLatLng, 268 date, 269 time, 270 timezone, 271 duration 272 ); 273 if (!result) { 274 container = document.querySelector(".container"); 275 container.innerHTML = `<div style="color: #fff">No route exist</div> <button id="refresh-button" onClick="refreshPage()" class="buttoncurrent ghost tiempo">Refresh Page</button>`; 276 return; 277 } 278 279 resultRouting = await getcoodrinates( 280 sourceLatLng.lat, 281 sourceLatLng.lng, 282 destLatLng.lat, 283 destLatLng.lng 284 ); 285 var polyLine = L.multiOptionsPolyline(resultRouting, { 286 multiOptions: { 287 optionIdxFn: function (latLng, prevLatLng, index, allLatlngs) { 288 return result.segments[index] + 1; 289 }, 290 options: [ 291 { 292 color: "#FFA500", 293 weight: 6, 294 295 }, 296 { 297 color: "grey", 298 weight: 6, 299 300 }, 301 { 302 color: "#4A90E2", 303 weight: 6, 304 stroke: true, 305 306 }, 307 ], 308 }, 309 weight: 3, 310 }).addTo(map); 311 312 var bounds = L.latLngBounds( 313 L.latLng(sourceLatLng.lat, sourceLatLng.lng), 314 L.latLng(destLatLng.lat, destLatLng.lng) 315 ); 316 317 map.flyToBounds(bounds, { duration: 3 }); 318 319 // routingControl.route(); 320 } 321 322 async function geocodeAddress(address) { 323 const api = `https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURI( 324 address 325 )}`; 326 327 try { 328 const response = await fetch(api); 329 const data = await response.json(); 330 331 if (data.length > 0) { 332 const latlng = L.latLng(data[0].lat, data[0].lon); 333 return latlng; 334 } else { 335 console.error(`Geocoding failed for address: ${address}`); 336 return null; 337 } 338 } catch (error) { 339 console.error(error); 340 return null; 341 } 342 } 343 344 async function callSunlightAPI( 345 sourceLatLng, 346 destLatLng, 347 date, 348 time, 349 timezone, 350 duration 351 ) { 352 const apiURL = `https://sun.amithv.workers.dev/?lat1=${sourceLatLng.lat}&lon1=${sourceLatLng.lng}&lat2=${destLatLng.lat}&lon2=${destLatLng.lng}&date=${date}&time=${time}&tz=${timezone}&duration=${duration}`; 353 354 const container = document.querySelector(".container"); 355 container.innerHTML = 356 '<div class="spinner"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div>'; 357 let segments; 358 359 await fetch(apiURL) 360 .then((response) => response.json()) 361 .then(async (data) => { 362 if (data) { 363 if (Object.keys(data).length === 0) { 364 container.innerHTML = `<div style="color: #fff">An error occurred while loading data.</div> <button id="refresh-button" onClick="refreshPage()" class="buttoncurrent ghost tiempo">Refresh Page</button>`; 365 return; 366 } 367 segments = data; 368 // console.log(segments); 369 let preferredSeat = ""; 370 if (data.leftPercentage >
370 data.rightPercentage) { 371 preferredSeat = "Right Side"; 372 } else if (data.leftPercentage < data.rightPercentage) { 373 preferredSeat = "Left Side"; 374 } else { 375 preferredSeat = "No Preference"; 376 } 377 container.innerHTML = ` 378 379 <div style="background-color:rgba(0,0,0,0.2);color: #fff; padding: 20px; font-family:'Lucida Grande',monospace; font-size: 18px;font-weight: bold;"> 380 <div style="font-size: 20px; font-weight: bold;">Preferred Seating :<span style="color:#FF5733"> ${preferredSeat}</span></div> 381 <div style="margin-top: 10px;"> 382 <div style="font-size: 16px;color:White;"><span style="font-size: 20px;">${formatDistance( 383 data.distance 384 )} / ${formatDistanceToMiles(data.distance)} :${secondsToReadableTime(data.duration)}</span></div> 385 386 <h4>Sun Exposure Data</h4> 387 <b> 388 <div style="font-size: 16px;color:#FFA500;">Sun Left: <span style="font-size: 20px;">${data.leftPercentage 389 }%</span></div> 390 <div style="font-size: 16px;color:#4A90E2;">Sun Right: <span style="font-size: 20px;">${data.rightPercentage 391 }%</span></div> 392 <div style="font-size: 16px;color:grey;">No Sun: <span style="font-size: 20px;">${data.noSunPercentage 393 }%</span></div> 394 </b> 395 </div> 396 <button id="refresh-button" onClick="newLocation()" class="buttoncurrent ghost tiempo">New Location</button> 397 <button id="load-previous-button" onClick="EditPage()" class="buttoncurrent ghost tiempo">Edit Search</button> 398 399 </div>`; 400 } 401 }) 402 .catch((error) => { 403 segments = null; 404 console.error("Error calling sunlight API:", error); 405 container.innerHTML = `<div style="color: #fff">An error occurred while loading data.</div> <button id="refresh-button" onClick="refreshPage()" class="buttoncurrent ghost tiempo">Refresh Page</button>`; 406 }); 407 return segments; 408 } 409 410 document 411 .getElementById("current-time-button") 412 .addEventListener("click", async function () { 413 fetchCurrentTime() 414 415 }) 416 417 document 418 .getElementById("current-location-button") 419 .addEventListener("click", async function () { 420 try { 421 // Get the user's current location using the Geolocation API 422 const position = await getCurrentLocation(); 423 424 // Update the source input field with the current location's address (optional) 425 const currentLocationAddress = await reverseGeocode( 426 position.coords.latitude, 427 position.coords.longitude 428 ); 429 if (currentLocationAddress) { 430 document.getElementById("source").value = currentLocationAddress; 431 } 432 433 // Update the sourceMarker on the map 434 if (sourceMarker) { 435 map.removeLayer(sourceMarker); 436 } 437 sourceMarker = L.marker( 438 [position.coords.latitude, position.coords.longitude], 439 { 440 title: "Current Location", 441 icon: sourceIcon, 442 } 443 ); 444 sourceMarker.addTo(map).bindPopup("Current Location"); 445 map.panTo([position.coords.latitude, position.coords.longitude]); 446 } catch (error) { 447 console.error("Error getting current location:", error); 448 } 449 }); 450 map.on("click", function (e) { 451 if (!sourceMarker) { 452 sourceMarker = L.marker(e.latlng, { 453 draggable: true, 454 icon: sourceIcon, 455 }).addTo(map); 456 sourceMarker.on("dragend", function (e) { 457 // Get the new marker position 458 const newPosition = e.target.getLatLng(); 459 // Do something with the new position, such as update the route 460 }); 461 } else if (!destinationMarker) { 462 destinationMarker = L.marker(e.latlng, { 463 draggable: true, 464 icon: destinationIcon, 465 }).addTo(map); 466 destinationMarker.on("dragend", function (e) { 467 // Get the new marker position 468 const newPosition = e.target.getLatLng(); 469 // Do something with the new position, such as update the route 470 }); 471 // Perform any necessary actions here, such as calculating the route between the markers 472 } 473 }); 474 475 476 let hourInput = document.getElementById('hour-input'); 477 let minInput = document.getElementById('min-input'); 478 let checkboxInput = document.getElementById('checkboxInput'); 479 480 hourInput.addEventListener('input', function() { 481 if (this.value !== '' || minInput.value !== '') { 482 checkboxInput.checked = false; 483 this.setCustomValidity(''); 484 minInput.setCustomValidity(''); 485 } else { 486 checkboxInput.checked = true; 487 } 488 }); 489 490 minInput.addEventListener('input', function() { 491 if (this.value !== '' || hourInput.value !== '') { 492 checkboxInput.checked = false; 493 this.setCustomValidity(''); 494 hourInput.setCustomValidity(''); 495 } else { 496 checkboxInput.checked = true; 497 } 498 }); 499 500 checkboxInput.addEventListener('click', function() { 501 hourInput.value = ''; 502 minInput.value = ''; 503 if (!this.checked) { 504 hourInput.setCustomValidity('Please enter the duration.'); 505 minInput.setCustomValidity('Please enter the duration.'); 506 hourInput.reportValidity(); 507 hourInput.focus(); 508 } else { 509 hourInput.setCustomValidity(''); 510 minInput.setCustomValidity(''); 511 } 512 }); 513}); 514 515 516function EditPage() { 517 sessionStorage.setItem("fetchpage", true); 518 refreshPage(); 519 520}; 521 522 523function fetchCurrentTime() { 524 const today = new Date(); 525 const currentDate = today.toISOString().split("T")[0]; 526 document.getElementById("date").value = currentDate; 527 528 const time = today.toLocaleTimeString(navigator.language, { 529 hour: "2-digit", 530 minute: "2-digit", 531 hour12: false, // Force 24-hour format 532 533 }); 534 document.getElementById("time").value = time; 535 536 const offsetInMinutes = today.getTimezoneOffset(); 537 const hours = Math.floor(Math.abs(offsetInMinutes) / 60); 538 const minutes = Math.abs(offsetInMinutes) % 60; 539 const sign = offsetInMinutes > 0 ? "-" : ""; 540 const offsetDecimal = hours + minutes / 60; 541 const timezone = `${sign}${offsetDecimal}`; 542 document.getElementById("timezone").value = timezone; 543} 544 545function getCurrentLocation() { 546 return new Promise((resolve, reject) => { 547 if ("geolocation" in navigator) { 548 navigator.geolocation.getCurrentPosition(resolve, reject); 549 } else { 550 reject("Geolocation is not available in this browser."); 551 } 552 }); 553} 554async function reverseGeocode(latitude, longitude) { 555 const api = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}`; 556 try { 557 const response = await fetch(api); 558 const data = await response.json(); 559 if (data.display_name) { 560 return data.display_name; 561 } else { 562 console.error("Geocoding response did not contain a valid address."); 563 return null; 564 } 565 } catch (error) { 566 console.error("Error during reverse geocoding:", error); 567 return null; 568 } 569} 570 571async function getcoodrinates(lat1, lon1, lat2, lon2) { 572 const osrmApiUrl = `https://routing.openstreetmap.de/routed-car/route/v1/driving/${lon1},${lat1};${lon2},${lat2}?overview=full&geometries=geojson`; 573 574 try { 575 const response = await fetch(osrmApiUrl); 576 const routedata = await response.json(); 577 578 if (routedata) { 579 const route = routedata.routes[0]; 580 const segments = route.geometry.coordinates.map((coordinate) => { 581 return { 582 lat: coordinate[1], 583 lng: coordinate[0], 584 }; 585 }); 586 return segments; 587 } 588 } catch (error) { 589 console.error("Error during reverse geocoding:", error); 590 return null; 591 } 592} 593function secondsToReadableTime(seconds) { 594 var seconds = parseInt(seconds, 10); 595 var hours = Math.floor(seconds / 3600); 596 var minutes = Math.floor((seconds - hours * 3600) / 60); 597 var seconds = seconds - hours * 3600 - minutes * 60; 598 if (!!hours) { 599 if (!!minutes) { 600 return `${hours}h ${minutes}m ${seconds}s`; 601 } else { 602 return `${hours}h ${seconds}s`; 603 } 604 } 605 if (!!minutes) { 606 return `${minutes}m ${seconds}s`; 607 } 608 return `${seconds}s`; 609} 610function formatDistance(meters) { 611 if (meters >= 1000) { 612 return Math.round(meters / 1000.0) + " km"; 613 } else if (meters >= 100) { 614 return Math.round(meters) + " m"; 615 } else { 616 return meters.toFixed(1) + " m"; 617 } 618} 619function formatDistanceToMiles(meters) { 620 const metersToMiles = 0.000621371; // 1 meter is approximately 0.000621371 miles 621 622 const miles = meters * metersToMiles; 623 624 if (miles >= 1) { 625 return miles.toFixed(2) + " miles"; 626 } else { 627 return (miles * 5280).toFixed(2) + " feet"; // Convert to feet if less than a mile 628 } 629} 630 631if ("serviceWorker" in navigator) { 632 // register service worker 633 navigator.serviceWorker.register("service-worker.js"); 634} 635// if ("serviceWorker" in navigator) { 636// // register service worker 637// navigator.serviceWorker.register("sw.js"); 638// } 639 640function refreshPage() { 641 642 window.location.reload(); 643} 644function newLocation() { 645 sessionStorage.setItem("fetchpage", false); 646 647 648 refreshPage() 649} 650 651 652let defferedPrompt; 653const addbtn = document.querySelector('.installbtn'); 654 655window.addEventListener('beforeinstallprompt', event => { 656 defferedPrompt = event 657 addbtn.style.display = 'block'; 658}); 659 660addbtn.addEventListener('click', event => { 661 defferedPrompt.prompt(); 662 663 defferedPrompt.userChoice.then(choice => { 664 if (choice.outcome === 'accepted') { 665 console.log('user accepted the prompt') 666 } 667 defferedPrompt = null; 668 }) 669}) 670 671 672 673 674 675 676 677 678 679 680// form js 681/* number selection*/ 682document 683 .querySelectorAll(".writing :is(input , textarea)") 684 .forEach((writinginput) => { 685 writinginput.addEventListener("input", () => { 686 if (writinginput.value.trim() !== "") { 687 writinginput.classList.add("has-text"); 688 } else { 689 writinginput.classList.remove("has-text"); 690 } 691 });
692 }); 693 694/* text area */ 695document.querySelectorAll(".textarea textarea").forEach((textarea) => { 696 textarea.addEventListener("input", () => { 697 textarea.style.height = "1em"; 698 const scrollHeight = textarea.scrollHeight; 699 textarea.style.height = `${scrollHeight}px`; 700 }); 701}); 702 703/* number input */ 704 705function decrementValue(button) { 706 const input = button.nextElementSibling; 707 if (parseInt(input.value) > parseInt(input.min)) { 708 input.value = parseInt(input.value) - 1; 709 } 710} 711function incrementValue(button) { 712 const input = button.previousElementSibling; 713 if (input.value === "") { 714 input.value = parseInt(input.min); 715 } else if (parseInt(input.value) < parseInt(input.max)) { 716 input.value = parseInt(input.value) + 1; 717 } 718} 719 720 721 722 723 724// drop down menu 725function applyOpenStyle(selectElement) { 726 if (selectElement.classList.contains("select-open")) { 727 selectElement.classList.remove("select-open"); 728 } else { 729 selectElement.classList.add("select-open"); 730 } 731} 732function removeOpenStyle(selectElement) { 733 selectElement.classList.remove("select-open"); 734} 735 736 737 738 739
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.