From c02d3281ec5a98fb2e7399f36034897ba264fcfc Mon Sep 17 00:00:00 2001 From: Cameron Kruse <14115927+cameronkruse@users.noreply.github.com> Date: Wed, 15 Mar 2023 22:44:09 -0700 Subject: [PATCH] Create storytelling.js --- hostedcontent/pk-flood/storytelling.js | 390 +++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 hostedcontent/pk-flood/storytelling.js diff --git a/hostedcontent/pk-flood/storytelling.js b/hostedcontent/pk-flood/storytelling.js new file mode 100644 index 0000000..e697ba3 --- /dev/null +++ b/hostedcontent/pk-flood/storytelling.js @@ -0,0 +1,390 @@ +var initLoad = true; +var layerTypes = { + fill: ["fill-opacity"], + line: ["line-opacity"], + circle: ["circle-opacity", "circle-stroke-opacity"], + symbol: ["icon-opacity", "text-opacity"], + raster: ["raster-opacity"], + "fill-extrusion": ["fill-extrusion-opacity"], + heatmap: ["heatmap-opacity"], +}; +// custom +let floodDays = ["ff_230", "ff_234", "ff_239", "ff_242", "ff_246", "ff_249", "ff_251", "ff_253", "ff_254", "ff_258", "ff_261", "ff_263", "ff_265", "ff_266"]; + +function animateFlood(day) { + if (day == "flat") { + map.setPaintProperty("flood3d", "fill-extrusion-height", 0); + } + let fillString = ["+"]; + for (let i = 0; i < day; i++) { + fillString.push(["get", floodDays[i]]); + } + console.log(fillString); + map.setPaintProperty("flood3d", "fill-extrusion-height", fillString); +} +// custom end +var alignments = { + left: "lefty", + center: "centered", + right: "righty", + full: "fully", +}; + +function getLayerPaintType(layer) { + var layerType = map.getLayer(layer).type; + return layerTypes[layerType]; +} + +function setLayerOpacity(layer) { + if (layer["fill-extrusion-height"]) { + console.log(layer["fill-extrusion-height"]); + animateFlood(layer["fill-extrusion-height"]); + } else { + var paintProps = getLayerPaintType(layer.layer); + // console.log(paintProps); + paintProps.forEach(function (prop) { + var options = {}; + if (layer.duration) { + var transitionProp = prop + "-transition"; + options = { duration: layer.duration }; + map.setPaintProperty(layer.layer, transitionProp, options); + } + map.setPaintProperty(layer.layer, prop, layer.opacity, options); + }); + } +} + +var story = document.getElementById("story"); +var features = document.createElement("div"); +features.setAttribute("id", "features"); + +var header = document.createElement("div"); + +if (config.title) { + var titleText = document.createElement("h1"); + titleText.innerText = config.title; + header.appendChild(titleText); +} + +if (config.subtitle) { + var subtitleText = document.createElement("h2"); + subtitleText.innerText = config.subtitle; + header.appendChild(subtitleText); +} + +if (config.byline) { + var bylineText = document.createElement("p"); + bylineText.innerText = config.byline; + header.appendChild(bylineText); +} + +if (header.innerText.length > 0) { + header.classList.add(config.theme); + header.setAttribute("id", "header"); + story.appendChild(header); +} + +config.chapters.forEach((record, idx) => { + var container = document.createElement("div"); + var chapter = document.createElement("div"); + + if (record.title) { + var title = document.createElement("h3"); + title.innerText = record.title; + chapter.appendChild(title); + } + + if (record.image) { + var image = new Image(); + image.src = record.image; + chapter.appendChild(image); + } + + if (record.description) { + var story = document.createElement("p"); + story.innerHTML = record.description; + chapter.appendChild(story); + } + + container.setAttribute("id", record.id); + container.classList.add("step"); + if (idx === 0) { + container.classList.add("active"); + } + + chapter.classList.add(config.theme); + container.appendChild(chapter); + container.classList.add(alignments[record.alignment] || "centered"); + if (record.hidden) { + container.classList.add("hidden"); + } + features.appendChild(container); +}); + +story.appendChild(features); + +var footer = document.createElement("div"); + +if (config.footer) { + var footerText = document.createElement("p"); + footerText.innerHTML = config.footer; + footer.appendChild(footerText); +} + +if (footer.innerText.length > 0) { + footer.classList.add(config.theme); + footer.setAttribute("id", "footer"); + story.appendChild(footer); +} + +mapboxgl.accessToken = config.accessToken; + +const transformRequest = (url) => { + const hasQuery = url.indexOf("?") !== -1; + const suffix = hasQuery ? "&pluginName=scrollytellingV2" : "?pluginName=scrollytellingV2"; + return { + url: url + suffix, + }; +}; + +var map = new mapboxgl.Map({ + container: "map", + style: config.style, + center: config.chapters[0].location.center, + zoom: config.chapters[0].location.zoom, + bearing: config.chapters[0].location.bearing, + pitch: config.chapters[0].location.pitch, + interactive: false, + transformRequest: transformRequest, + projection: config.projection, +}); + +// Create a inset map if enabled in config.js +if (config.inset) { + var insetMap = new mapboxgl.Map({ + container: "mapInset", // container id + style: "mapbox://styles/mapbox/dark-v10", //hosted style id + center: config.chapters[0].location.center, + // Hardcode above center value if you want insetMap to be static. + zoom: 3, // starting zoom + hash: false, + interactive: false, + attributionControl: false, + //Future: Once official mapbox-gl-js has globe view enabled, + //insetmap can be a globe with the following parameter. + //projection: 'globe' + }); +} + +if (config.showMarkers) { + var marker = new mapboxgl.Marker({ color: config.markerColor }); + marker.setLngLat(config.chapters[0].location.center).addTo(map); +} + +// instantiate the scrollama +var scroller = scrollama(); + +map.on("load", function () { + if (config.use3dTerrain) { + map.addSource("mapbox-dem", { + type: "raster-dem", + url: "mapbox://mapbox.mapbox-terrain-dem-v1", + tileSize: 512, + maxzoom: 14, + }); + // add the DEM source as a terrain layer with exaggerated height + map.setTerrain({ source: "mapbox-dem", exaggeration: 1.5 }); + + // add a sky layer that will show when the map is highly pitched + map.addLayer({ + id: "sky", + type: "sky", + paint: { + "sky-type": "atmosphere", + "sky-atmosphere-sun": [0.0, 0.0], + "sky-atmosphere-sun-intensity": 15, + }, + }); + } + // ADD Custom layers + map.addSource("flood", { + type: "vector", + url: "mapbox://highestroad.blex374p", + }); + map.addLayer({ + id: "flood3d", + type: "fill-extrusion", + source: "flood", + "source-layer": "firstflood-grid-cleaned-c04q87", + paint: { + "fill-extrusion-opacity": 0, + "fill-extrusion-color": ["interpolate", ["linear"], ["get", "flood_sum"], 10323, "#d8e4ee", 64140, "#11304b"], + "fill-extrusion-height": 0, + }, + }, "agflood0-grid"); + + // As the map moves, grab and update bounds in inset map. + if (config.inset) { + map.on("move", getInsetBounds); + } + // setup the instance, pass callback functions + scroller + .setup({ + step: ".step", + offset: 0.5, + progress: true, + }) + .onStepEnter(async (response) => { + var current_chapter = config.chapters.findIndex((chap) => chap.id === response.element.id); + var chapter = config.chapters[current_chapter]; + if (chapter.fullPhoto) { + document.getElementById("full-photo").style["background-image"] = "url(" + chapter.fullPhoto + ")"; + // document.getElementById("photo").style["background-position"] = chapter.photoposition; + document.getElementById("full-photo").style.opacity = "100"; + document.getElementById("map").style.opacity = "0"; + } else { + document.getElementById("full-photo").style.opacity = "0"; + document.getElementById("map").style.opacity = "100"; + } + if (chapter.map3d) { + // make map 3d + map.setTerrain({ source: "mapbox-dem", exaggeration: 1.5 }); + } else { + map.setTerrain(); + } + if (chapter.date) { + // const stepDivs = + document.querySelectorAll('.step div').forEach(div => { + div.style.backgroundColor = '#eee9e000'; + div.style.webkitBoxShadow = '5px 5px 10px 0px rgba(0, 0, 0, 0.0)'; + }); + + } else { + document.querySelectorAll('.step div').forEach(div => { + div.style.backgroundColor = '#eee9e0'; + div.style.webkitBoxShadow = '5px 5px 10px 0px rgba(0, 0, 0, 0.5)'; + }); + } + + response.element.classList.add("active"); + map[chapter.mapAnimation || "flyTo"](chapter.location); + + // Incase you do not want to have a dynamic inset map, + // rather want to keep it a static view but still change the + // bbox as main map move: comment out the below if section. + if (config.inset) { + if (chapter.location.zoom < 5) { + insetMap.flyTo({ center: chapter.location.center, zoom: 0 }); + } else { + insetMap.flyTo({ center: chapter.location.center, zoom: 3 }); + } + } + if (config.showMarkers) { + marker.setLngLat(chapter.location.center); + } + if (chapter.onChapterEnter.length > 0) { + chapter.onChapterEnter.forEach(setLayerOpacity); + } + if (chapter.callback) { + window[chapter.callback](); + } + if (chapter.rotateAnimation) { + map.once("moveend", () => { + const rotateNumber = map.getBearing(); + map.rotateTo(rotateNumber + 180, { + duration: 30000, + easing: function (t) { + return t; + }, + }); + }); + } + if (config.auto) { + var next_chapter = (current_chapter + 1) % config.chapters.length; + map.once("moveend", () => { + document.querySelectorAll('[data-scrollama-index="' + next_chapter.toString() + '"]')[0].scrollIntoView(); + }); + } + }) + .onStepExit((response) => { + var chapter = config.chapters.find((chap) => chap.id === response.element.id); + response.element.classList.remove("active"); + if (chapter.onChapterExit.length > 0) { + chapter.onChapterExit.forEach(setLayerOpacity); + } + }); + + if (config.auto) { + document.querySelectorAll('[data-scrollama-index="0"]')[0].scrollIntoView(); + } +}); + +//Helper functions for insetmap +function getInsetBounds() { + let bounds = map.getBounds(); + + let boundsJson = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + properties: {}, + geometry: { + type: "Polygon", + coordinates: [ + [ + [bounds._sw.lng, bounds._sw.lat], + [bounds._ne.lng, bounds._sw.lat], + [bounds._ne.lng, bounds._ne.lat], + [bounds._sw.lng, bounds._ne.lat], + [bounds._sw.lng, bounds._sw.lat], + ], + ], + }, + }, + ], + }; + + if (initLoad) { + addInsetLayer(boundsJson); + initLoad = false; + } else { + updateInsetLayer(boundsJson); + } +} + +function addInsetLayer(bounds) { + insetMap.addSource("boundsSource", { + type: "geojson", + data: bounds, + }); + + insetMap.addLayer({ + id: "boundsLayer", + type: "fill", + source: "boundsSource", // reference the data source + layout: {}, + paint: { + "fill-color": "#fff", // blue color fill + "fill-opacity": 0.2, + }, + }); + // // Add a black outline around the polygon. + insetMap.addLayer({ + id: "outlineLayer", + type: "line", + source: "boundsSource", + layout: {}, + paint: { + "line-color": "#000", + "line-width": 1, + }, + }); +} + +function updateInsetLayer(bounds) { + insetMap.getSource("boundsSource").setData(bounds); +} + +// setup resize event +window.addEventListener("resize", scroller.resize);