let ttPACCESSTOKEN = "sk.eyJ1IjoidGFzdGluZ3RvbW9ycm93IiwiYSI6ImNsOTY2NnNpMjEwbXczdXFtOGtld3A1dXUifQ.0w55T5WlkSFnd2JFBKmcGg"; let ttACCESSTOKEN = "pk.eyJ1IjoidGFzdGluZ3RvbW9ycm93IiwiYSI6ImNsOTY2MGs5MTJsNjQzdnBjYXNwcnNrN2sifQ.oxO7o4sYn9MpS87XZnv5DQ"; mapboxgl.accessToken = ttACCESSTOKEN; let clientAccess = mapboxSdk({ accessToken: ttPACCESSTOKEN, }); let client = new MapboxClient(ttACCESSTOKEN); let center = [5.7419, 39.9826]; let map; let ttDATASET = "cl9xkjbsm01t827ru3dmjjldo"; let ttanaDATASET = "cl9xkiqie26zz20qj3iroq2r9"; let entireDATASET = "clax6fpkr000y26ldrqc7aze3"; let dataFeatures; let analogFeatures; let allDataFeatures; let dataSizeRec = 0; let actualDataSize; let lastID; let mapZoomInit = 2; let respMinZoom = 0.9; let flaggedFeatures = { type: "FeatureCollection", features: [], }; // let flaggedLocation = getCookie("location"); // let flID = getCookie("id"); //https://dev.to/nirazanbasnet/dont-use-100vh-for-mobile-responsive-3o97 let h; let w; $('document').ready(function(){ calcVh(); }); window.addEventListener("resize", calcVh); function calcVh() { h = window.innerHeight; w = window.innerWidth; const doc = document.documentElement; doc.style.setProperty("--doc-height", `${window.innerHeight}px`); if (w < 480){ mapZoomInit = 0.4; respMinZoom = 0.2; } } function checkDatasetSize() { // "https://api.mapbox.com/datasets/v1/tastingtomorrow/{dataset_id}?access_token=pk.eyJ1IjoidGFzdGluZ3RvbW9ycm93IiwiYSI6ImNsOTY2MGs5MTJsNjQzdnBjYXNwcnNrN2sifQ.oxO7o4sYn9MpS87XZnv5DQ" return new Promise((resolve) => { fetch( "https://api.mapbox.com/datasets/v1/tastingtomorrow/" + entireDATASET + "?access_token=" + ttPACCESSTOKEN ) .then((response) => response.json()) .then((json) => { // here's where you access your data // console.log(json); actualDataSize = json.features; console.log("Dataset length= " + json.features); resolve(); }) .catch((error) => { // handle data errors console.log(error); resolve(); }); }); } function testGet() { return new Promise((resolve) => { fetch( "https://api.mapbox.com/datasets/v1/tastingtomorrow/" + entireDATASET + "/features?access_token=" + ttPACCESSTOKEN ) .then((response) => response.json()) .then((json) => { // here's where you access your data // console.log(json); let currDataSizeRec = Object.keys(json.features).length; allDataFeatures = json; allDataFeatures = JSON.stringify(allDataFeatures); allDataFeatures = JSON.parse(allDataFeatures); dataSizeRec = Object.keys(allDataFeatures.features).length; lastID = json.features[currDataSizeRec - 1].id; console.log("ID of last data retrieved= " + lastID); resolve(); }) .catch((error) => { // handle data errors console.log(error); resolve(); }); }); } function testGetMore() { console.log("getting more features.."); return new Promise((resolve) => { fetch( "https://api.mapbox.com/datasets/v1/tastingtomorrow/" + entireDATASET + "/features?start=" + lastID + "&access_token=" + ttPACCESSTOKEN ) .then((response) => response.json()) .then((json) => { // here's where you access your data // console.log(json); let currDataSizeRec = Object.keys(json.features).length; lastID = json.features[currDataSizeRec - 1].id; console.log("ID of last data retrieved= " + lastID); json.features.map(function (val) { allDataFeatures.features.push(val); }); // console.log(allDataFeatures) dataSizeRec = Object.keys(allDataFeatures.features).length; console.log("Updated Feature List Amount" + dataSizeRec); resolve(); }) .catch((error) => { // handle data errors console.log(error); resolve(); }); }); } function storeflaggedlocations() { return new Promise((resolve) => { allDataFeatures.features.map(function (val) { if (val.properties.flagged == true) { flaggedFeatures.features.push(val); } }); resolve(); }); } function idflaggedloc() { return new Promise((resolve) => { flaggedFeatures.features.forEach(function (location, i) { location.properties.id = i; location.properties.distance = null; }); flaggedFeatures = JSON.stringify(flaggedFeatures); flaggedFeatures = JSON.parse(flaggedFeatures); sortFeatures(flaggedFeatures); // console.log(JSON.stringify(flaggedFeatures)); resolve(); }); } function sortFeatures(unsortedFeat) { unsortedFeat.features.sort((a, b) => { if (a.properties.locationName > b.properties.locationName) { return 1; } if (a.properties.locationName < b.properties.locationName) { return -1; } return 0; // a must be equal to b }); unsortedFeat.features.sort((a, b) => { if ( a.properties.locationName.substring( a.properties.locationName.lastIndexOf("_") ) > b.properties.locationName.substring( b.properties.locationName.lastIndexOf("_") ) ) { return 1; } if ( a.properties.locationName.substring( a.properties.locationName.lastIndexOf("_") ) < b.properties.locationName.substring( b.properties.locationName.lastIndexOf("_") ) ) { return -1; } return 0; // a must be equal to b }); } async function appEnter() { // renderListings([]); jQuery(".progress-bar").css("display", "block"); loadingProgress(); // await first(); await checkDatasetSize(); // await second(); await testGet(); while (dataSizeRec != actualDataSize) { await testGetMore(); } await storeflaggedlocations(); await idflaggedloc(); buildLocationList(flaggedFeatures.features); // await fourth(); // await fifth(); createMap(); openSidebar(); } // START OF SIDEBAR // Holds visible features for filtering let visibleLocations = []; // Create a popup, but don't add it to the map yet. const popup = new mapboxgl.Popup({ closeButton: true, }); const filterEl = document.getElementById("feature-filter"); const listingEl = document.getElementById("feature-listing"); function buildLocationList(features) { // console.log("building location list features: " + JSON.stringify(features) + "features length: "+ features.length); const empty = document.createElement("p"); // Clear any existing listings listingEl.innerHTML = ""; for (const feature of features) { /* Add a new listing section to the sidebar. */ const listing = listingEl.appendChild(document.createElement("div")); /* Assign a unique `id` to the listing. */ listing.id = `listing-${feature.properties.id}`; /* Assign the `item` class to each listing for styling. */ listing.className = "item"; /* Add the link to the individual listing created above. */ const filterlabel = feature.properties.locationName.replaceAll("_", ", "); const locationTitle = listing.appendChild(document.createElement("a")); locationTitle.textContent = filterlabel; locationTitle.href = "#"; locationTitle.id = `link-${feature.properties.id}`; locationTitle.className = "location"; /* Add details to the individual listing. */ const details = listing.appendChild(document.createElement("div")); if (feature.properties.distance) { const roundedDistance = Math.round(feature.properties.distance * 100) / 100; details.innerHTML += `

${roundedDistance} miles away

`; } const coordinates = feature.geometry.coordinates; const loc = feature.properties.locationName; const locID = getLocationIDbyName(loc); const popUpDiv = document.createElement("div"); const locNamePop = document.createElement("h3"); locNamePop.innerHTML = filterlabel; const description = document.createElement("p"); // description.innerHTML = locID; // const description = formattedLoc + "
" + locID; const enterDataBtn = document.createElement("button"); enterDataBtn.id = "enter-data-button"; enterDataBtn.innerText = "Enter Data"; enterDataBtn.addEventListener("click", function () { sessionStorage.clear(); sessionStorage.setItem("location", loc); sessionStorage.setItem("id", locID); window.location = "/pages/app.html"; }); popUpDiv.append(locNamePop); popUpDiv.append(description); popUpDiv.append(enterDataBtn); locationTitle.addEventListener("click", function () { const activeItem = document.getElementsByClassName("active"); if (activeItem[0]) { activeItem[0].classList.remove("active"); } this.parentNode.classList.add("active"); this.parentNode.scrollIntoView(); // jQuery(this).addClass('active'); const popups = document.getElementsByClassName("mapboxgl-popup"); if (popups.length) { let i = 0; while (i <= popups.length) { popups[0].remove(); i++; } } map.flyTo({ center: feature.geometry.coordinates, }); // Highlight corresponding feature on the map popup .setLngLat(feature.geometry.coordinates) .setDOMContent(popUpDiv) // .setText(filterlabel) .addTo(map); }); listingEl.appendChild(listing); } // Show the filter input filterEl.parentNode.style.display = "block"; } //Creates sidebar of listings function renderListings(features) { const empty = document.createElement("p"); // Clear any existing listings listingEl.innerHTML = ""; if (features.length) { for (const feature of features) { const locationTitle = document.createElement("a"); const filterlabel = feature.properties.locationName.replaceAll("_", ", "); locationTitle.textContent = filterlabel; locationTitle.href = "#"; const coordinates = feature.geometry.coordinates; const loc = feature.properties.locationName; const locID = getLocationIDbyName(loc); const popUpDiv = document.createElement("div"); const locNamePop = document.createElement("h3"); locNamePop.innerHTML = filterlabel; const description = document.createElement("p"); // description.innerHTML = locID; // const description = formattedLoc + "
" + locID; const enterDataBtn = document.createElement("button"); enterDataBtn.id = "enter-data-button"; enterDataBtn.innerText = "Enter Data"; enterDataBtn.addEventListener("click", function () { sessionStorage.clear(); sessionStorage.setItem("location", loc); sessionStorage.setItem("id", locID); window.location = "/pages/app.html"; }); popUpDiv.append(locNamePop); popUpDiv.append(description); popUpDiv.append(enterDataBtn); locationTitle.addEventListener("click", function () { const popups = document.getElementsByClassName("mapboxgl-popup"); if (popups.length) { let i = 0; while (i <= popups.length) { popups[0].remove(); i++; } } map.flyTo({ center: feature.geometry.coordinates, }); // Highlight corresponding feature on the map popup .setLngLat(feature.geometry.coordinates) .setDOMContent(popUpDiv) // .setText(filterlabel) .addTo(map); }); listingEl.appendChild(locationTitle); } // Show the filter input filterEl.parentNode.style.display = "block"; } else if (features.length === 0 && filterEl.value !== "") { empty.textContent = "No results found"; listingEl.appendChild(empty); } else { empty.textContent = "Enter a location or drag map to populate results"; listingEl.appendChild(empty); // Hide the filter input filterEl.parentNode.style.display = "none"; } } function normalize(string) { return string.trim().toLowerCase(); } // Because features come from tiled vector data, // feature geometries may be split // or duplicated across tile boundaries. // As a result, features may appear // multiple times in query results. function getUniqueFeatures(features) { const uniqueIds = new Set(); const uniqueFeatures = []; for (const feature of features) { const id = feature.properties.locationName; if (!uniqueIds.has(id)) { uniqueIds.add(id); uniqueFeatures.push(feature); } } return uniqueFeatures; } function createMap() { jQuery(".progress-bar").css("display", "none"); updateProgress(0); clearInterval(interval); map = new mapboxgl.Map({ container: "map", style: "mapbox://styles/tastingtomorrow/cl966vgy3005215mz52p0jvjk", // style URL zoom: mapZoomInit, center: center, maxPitchPreference: 45, projection: "globe", interactive: true, minZoom: respMinZoom, // maxZoom: 2.5 }); map.on("ready", function () {}); map.on("dragstart", function () { const popup = document.getElementsByClassName("mapboxgl-popup"); if (popup.length) { let i = 0; while (i <= popup.length) { popup[0].remove(); i++; } } }); map.on("load", function () { let openBtn = document.getElementsByClassName("open-btn")[0]; openBtn.style.display = "block"; const geocoder = new MapboxGeocoder({ accessToken: mapboxgl.accessToken, marker: false, placeholder: "Go to a location", flyTo: { bearing: 0, // Control the flight curve, making it move slowly and // zoom out almost completely before starting to pan. speed: 1, // Make the flying slow. curve: 1, // Change the speed at which it zooms out. zoom: 4, // This can be any easing function: it takes a number between // 0 and 1 and returns another number between 0 and 1. easing: function (t) { return t; }, }, mapboxgl: mapboxgl, }); // Add the geocoder to the map/ let currentMarkers = []; let geoMarker; document.getElementById("geocoder").appendChild(geocoder.onAdd(map)); geocoder.on("result", (event) => { // When the user selects a place from the list of returned locations, save the coordinates in a variable called searchResult const searchResult = event.result.geometry; const options = { units: "miles" }; if (currentMarkers !== null) { for (var i = currentMarkers.length - 1; i >= 0; i--) { currentMarkers[i].remove(); } } geoMarker = new mapboxgl.Marker({ draggable: false, color: "#CA4900" }) .setLngLat(event.result.center) .addTo(map); currentMarkers.push(geoMarker); // find the distances between your new point and each of the flagged locations for (const flaggedFeature of flaggedFeatures.features) { flaggedFeature.properties.distance = turf.distance( searchResult, flaggedFeature.geometry, options ); console.log( JSON.stringify( "distance from point:" + flaggedFeature.properties.distance ) ); } //sort the flagged features by distance flaggedFeatures.features.sort((a, b) => { if (a.properties.distance > b.properties.distance) { return 1; } if (a.properties.distance < b.properties.distance) { return -1; } return 0; // a must be equal to b }); const listings = document.getElementById("feature-listing"); while (listings.firstChild) { listings.removeChild(listings.firstChild); } buildLocationList(flaggedFeatures.features); const activeListing = document.getElementById( `listing-${flaggedFeatures.features[0].properties.id}` ); activeListing.classList.add("active"); activeListing.scrollIntoView(); }); map.on("dragend", () => { geocoder.clear(); if (geoMarker) { geoMarker.remove(); } flaggedFeatures.features.forEach(function (location, i) { location.properties.distance = null; }); sortFeatures(flaggedFeatures); buildLocationList(flaggedFeatures.features); }); map.on("moveend", () => { //adds locations to the sidebar if in the window. // const features = map.queryRenderedFeatures({ // layers: ['analog-flagged-points','flagged-updated-points'] // }); // if (features) { // const uniqueFeatures = getUniqueFeatures(features); // // Populate features for the listing overlay. // renderListings(uniqueFeatures); // // Clear the input container // filterEl.value = ''; // // Store the current features in sn `airports` variable to // // later use for filtering on `keyup`. // visibleLocations = uniqueFeatures; // } }); // filters visible locations by user input filterEl.addEventListener("keyup", (e) => { const value = normalize(e.target.value); // Filter visible features that match the input value. const filtered = []; for (const feature of flaggedFeatures.features) { const name = normalize(feature.properties.locationName); if (name.includes(value)) { filtered.push(feature); } } // Populate the sidebar with filtered results buildLocationList(filtered); }); // analogFeatures.features = analogFeatures.features.filter(feature => feature.properties.flagged === true); map.addSource("flagged-data", { type: "geojson", data: flaggedFeatures, cluster: false, clusterMaxZoom: 5, // Max zoom to cluster points on clusterRadius: 10, // Radius of each cluster when clustering points (defaults to 50) filter: ["==", ["get", "flagged"], true], }); // TODO: updated flagged updated data when done testing map.addSource("flagged-updated-data", { type: "geojson", data: allDataFeatures, cluster: false, clusterMaxZoom: 14, // Max zoom to cluster points on clusterRadius: 20, // Radius of each cluster when clustering points (defaults to 50) filter: [ "all", ["==", ["get", "flaggedUpdated"], true], ["==", ["get", "flagged"], true], ], }); // map.addLayer({ id: "analog-flagged-points", type: "circle", source: "flagged-data", filter: ["!", ["has", "point_count"]], paint: { "circle-radius": 6, "circle-color": "#994301", "circle-stroke-color": "#000000", "circle-stroke-width": 0.5, "circle-stroke-opacity": 0.5, }, }); map.addLayer({ id: "flagged-updated-points", type: "circle", source: "flagged-updated-data", filter: ["!", ["has", "point_count"]], paint: { "circle-radius": 6, "circle-color": "#1FB386", "circle-stroke-color": "#000000", "circle-stroke-width": 0.5, "circle-stroke-opacity": 0.5, }, }); //CLUSTERS BEGIN //flagged analog locations map.addLayer({ id: "clusters-flagged", type: "circle", source: "flagged-data", filter: ["has", "point_count"], paint: { // Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step) // with three steps to implement three types of circles: // * Blue, 20px circles when point count is less than 100 // * Yellow, 30px circles when point count is between 100 and 750 // * Pink, 40px circles when point count is greater than or equal to 750 "circle-color": [ "step", ["get", "point_count"], "#994301", 100, "#B36200", 750, "#EF8A62", ], "circle-radius": ["step", ["get", "point_count"], 10, 100, 20, 750, 30], }, }); //user updated flagged locations map.addLayer({ id: "clusters-flagged-updated", type: "circle", source: "flagged-updated-data", filter: ["has", "point_count"], paint: { // Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step) // with three steps to implement three types of circles: // * Blue, 20px circles when point count is less than 100 // * Yellow, 30px circles when point count is between 100 and 750 // * Pink, 40px circles when point count is greater than or equal to 750 "circle-stroke-width": 1, "circle-stroke-opacity": 0.5, "circle-color": [ "step", ["get", "point_count"], "#1FB386", 100, "#1FB386", 750, "#1FB386", ], "circle-radius": ["step", ["get", "point_count"], 10, 100, 20, 750, 30], }, }); //number inside cluster map.addLayer({ id: "cluster-count-flagged", type: "symbol", source: "flagged-data", filter: ["has", "point_count"], layout: { "text-field": "{point_count_abbreviated}", "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"], "text-size": 12, }, paint: { "text-color": "#ffffff", }, }); // inspect a cluster on click map.on("click", "clusters-flagged", (e) => { const features = map.queryRenderedFeatures(e.point, { layers: ["clusters-flagged"], }); const clusterId = features[0].properties.cluster_id; map .getSource("flagged-data") .getClusterExpansionZoom(clusterId, (err, zoom) => { if (err) return; map.easeTo({ center: features[0].geometry.coordinates, zoom: zoom, }); }); }); map.on("click", "clusters-flagged-updated", (e) => { const features = map.queryRenderedFeatures(e.point, { layers: ["clusters-flagged-updated"], }); const clusterId = features[0].properties.cluster_id; map .getSource("flagged-updated-data") .getClusterExpansionZoom(clusterId, (err, zoom) => { if (err) return; map.easeTo({ center: features[0].geometry.coordinates, zoom: zoom, }); }); }); // When a click event occurs on a feature in the places layer, open a popup at the // location of the feature, with description HTML from its properties. map.on("click", "analog-flagged-points", (e) => { // Copy coordinates array. // console.log("hello" + JSON.stringify(e.features)); const coordinates = e.features[0].geometry.coordinates.slice(); const loc = e.features[0].properties.locationName; const locID = getLocationIDbyName(loc); const formattedLoc = loc.replaceAll("_", ", "); /* Highlight listing in sidebar (and remove highlight for all other listings) */ const activeItem = document.getElementsByClassName("active"); if (activeItem[0]) { activeItem[0].classList.remove("active"); } const listing = document.getElementById( `listing-${e.features[0].properties.id}` ); listing.classList.add("active"); listing.scrollIntoView(); let popUpDiv = document.createElement("div"); let locName = document.createElement("h3"); locName.innerHTML = formattedLoc; let description = document.createElement("p"); // description.innerHTML = locID; // const description = formattedLoc + "
" + locID; let enterDataBtn = document.createElement("button"); enterDataBtn.id = "enter-data-button"; enterDataBtn.innerText = "Enter Data"; enterDataBtn.addEventListener("click", function () { sessionStorage.clear(); sessionStorage.setItem("location", loc); sessionStorage.setItem("id", locID); window.location = "/pages/app.html"; }); popUpDiv.append(locName); popUpDiv.append(description); popUpDiv.append(enterDataBtn); // Ensure that if the map is zoomed out such that multiple // copies of the feature are visible, the popup appears // over the copy being pointed to. while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; } map.flyTo({ center: e.features[0].geometry.coordinates, }); new mapboxgl.Popup() .setLngLat(coordinates) // .setHTML(description) .setDOMContent(popUpDiv) .addTo(map); }); // TODO: enable updated flagged points for click // Change the cursor to a pointer when the mouse is over the places layer. map.on("mouseenter", "analog-flagged-points", () => { map.getCanvas().style.cursor = "pointer"; }); // Change it back to a pointer when it leaves. map.on("mouseleave", "analog-flagged-points", () => { map.getCanvas().style.cursor = ""; }); // Change the cursor to a pointer when the mouse is over the places layer. map.on("mouseenter", "flagged-updated-points", () => { map.getCanvas().style.cursor = "pointer"; }); // Change it back to a pointer when it leaves. map.on("mouseleave", "flagged-updated-points", () => { map.getCanvas().style.cursor = ""; }); }); } function getLocationIDbyName(name) { let id; // console.log("Getting Location ID of " + name); name = name.toString(); let location = allDataFeatures.features.find((el) => el.properties.locationName.includes(name) ); id = location["id"]; // => {name: "Albania", code: "AL"} return id; } // let getActive = browser.tabs.query({ // active: true, // currentWindow: true // }); let sideOpen = false; function openSidebar() { let mapDiv = document.getElementById("map"); let sidebarDiv = document.getElementById("map-overlay"); let mapResizer; if (w > 480) { if (!sideOpen) { sidebarDiv.style.height = "100%"; mapDiv.style.height = "100%"; sidebarDiv.style.display = "flex"; setTimeout(() => { sidebarDiv.style.width = "25%"; mapDiv.style.width = "75%"; }, "300"); sideOpen = true; } else if (sideOpen) { sidebarDiv.style.width = "0%"; mapDiv.style.width = "100%"; setTimeout(() => { sidebarDiv.style.display = "none"; }, "300"); sideOpen = false; } //source: https://stackoverflow.com/questions/61490901/mapbox-smooth-transition-of-resizing-map } else if (w <= 480) { if (!sideOpen) { sidebarDiv.style.width = "100%"; mapDiv.style.width = "100%"; sidebarDiv.style.display = "flex"; setTimeout(() => { sidebarDiv.style.height = "50%"; mapDiv.style.height = "50%"; }, "300"); sideOpen = true; } else if (sideOpen) { sidebarDiv.style.height = "0%"; mapDiv.style.height = "100%"; setTimeout(() => { sidebarDiv.style.display = "none"; }, "300"); sideOpen = false; } } mapDiv.addEventListener("transitionstart", checkDivSize); function checkDivSize() { mapResizer = setInterval(resizeMap, 10); let mapW = (mapDiv.clientWidth / mapDiv.parentElement.clientWidth) * 100; let mapH = (mapDiv.clientHeight / mapDiv.parentElement.clientHeight) * 100; if (w <= 480) { mapH = Math.round(mapH).toFixed(2); if (mapH == 50) { clearInterval(mapResizer); } } else { mapW = Math.round(mapW).toFixed(2); // console.log(mapW); if (mapW == 75) { clearInterval(mapResizer); } } } function resizeMap() { map.resize(); } // window.setTimeout(() => map.resize(), 500); } //progress bar variables const progress = document.getElementById("progress"); const stepCircles = document.querySelectorAll(".circle"); let currentActiveStep = 1; function updateProgress(currentActive) { stepCircles.forEach((circle, i) => { if (i < currentActive) { circle.classList.add("active"); } else { circle.classList.remove("active"); } }); const activeCircles = document.querySelectorAll(".active"); progress.style.width = ((activeCircles.length - 1) / (stepCircles.length - 1)) * 100 + "%"; } function loadingProgress() { let loadDots = 1; interval = window.setInterval(function () { updateProgress(loadDots); loadDots++; if (loadDots > stepCircles.length) { loadDots = 1; } }, 500); // initProgressBar(); } appEnter();