From b87134bd3a5bef419a05d4ec9f5b6347c97a77ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Barrenechea=20S=C3=A1nchez?= Date: Thu, 11 Aug 2022 16:58:46 +0200 Subject: [PATCH 1/9] Loss layer: using extension --- .../extensions/decode-extension.stories.tsx | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx diff --git a/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx b/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx new file mode 100644 index 0000000..f45963f --- /dev/null +++ b/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx @@ -0,0 +1,240 @@ + +import React, { useCallback, useMemo, useState } from 'react'; +import { Story } from '@storybook/react/types-6-0'; +// Layer manager +import { LayerManager, Layer, LayerProps } from '@vizzuality/layer-manager-react'; +import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl'; +import CartoProvider from '@vizzuality/layer-manager-provider-carto'; + +import GL from '@luma.gl/constants'; +import { TileLayer } from '@deck.gl/geo-layers'; +import { BitmapLayer } from '@deck.gl/layers'; +import { MapboxLayer } from '@deck.gl/mapbox'; +import { LayerExtension } from '@deck.gl/core'; + +// Map +import Map from '../../../components/map'; + +const cartoProvider = new CartoProvider(); + +export default { + title: 'Playground/Extensions', + argTypes: { + deck: { + table: { + disable: true + } + }, + tileUrl: { + name: 'tileUrl', + type: { name: 'Tile URL', required: true }, + defaultValue: 'https://storage.googleapis.com/wri-public/Hansen_16/tiles/hansen_world/v1/tc30/{z}/{x}/{y}.png', + control: { + type: 'text' + }, + }, + decodeParams: { + name: 'decodeParams', + type: { name: 'object', required: true }, + defaultValue: { + startYear: 2001, + endYear: 2017, + } + }, + decodeFunction: { + name: 'decodeFunction', + type: { name: 'string', required: true }, + defaultValue: `// values for creating power scale, domain (input), and range (output) +float domainMin = 0.; +float domainMax = 255.; +float rangeMin = 0.; +float rangeMax = 255.; + +float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; +float intensity = color.r * 255.; + +// get the min, max, and current values on the power scale +float minPow = pow(domainMin, exponent - domainMin); +float maxPow = pow(domainMax, exponent); +float currentPow = pow(intensity, exponent); + +// get intensity value mapped to range +float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; +// a value between 0 and 255 +color.a = zoom < 13. ? scaleIntensity / 255. : color.g; + +float year = 2000.0 + (color.b * 255.); + +// map to years +if (year >= startYear && year <= endYear && year >= 2001.) { + color.r = 220. / 255.; + color.g = (72. - zoom + 102. - 3. * scaleIntensity / zoom) / 255.; + color.b = (33. - zoom + 153. - intensity / zoom) / 255.; +} else { + color.a = 0.0; +} + `, + description: 'The decode function you will apply to each tile pixel', + control: { + type: 'text' + } + } + }, +}; + +class LossExtension extends LayerExtension { + getShaders() { + return { + inject: { + 'fs:#decl': ` + uniform float zoom; + uniform float startYear; + uniform float endYear; + `, + + 'fs:DECKGL_FILTER_COLOR': ` + ${this.props.decodeFunction} + ` + } + }; + } + + updateState({ props, changeFlags }) { + const { + decodeParams = {}, + zoom + } = props; + + if (changeFlags.extensionsChanged || changeFlags.somethingChanged.decodeFunction) { + const { gl } = this.context; + this.state.model?.delete(); + this.state.model = this._getModel(gl); + this.getAttributeManager().invalidateAll(); + } + + for (const model of this.getModels()) { + model.setUniforms({ + zoom, + ...decodeParams, + }); + } + } +} + +const Template: Story = (args: any) => { + const { id, tileUrl, decodeFunction, decodeParams } = args; + + const minZoom = 2; + const maxZoom = 20; + const [viewport, setViewport] = useState({}); + + const [bounds] = useState(null); + + const DECK_LAYERS = useMemo(() => { + return [ + new MapboxLayer( + { + id, + type: TileLayer, + data: tileUrl, + tileSize: 256, + visible: true, + opacity: 1, + refinementStrategy: 'no-overlap', + decodeFunction, + decodeParams, + renderSubLayers: (sl) => { + const { + id: subLayerId, + data, + tile, + visible, + opacity: _opacity, + decodeParams: dParams, + decodeFunction: dFunction, + } = sl; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + if (data) { + return new BitmapLayer({ + id: subLayerId, + image: data, + bounds: [west, south, east, north], + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity: _opacity, + decodeParams: dParams, + decodeFunction: dFunction, + extensions: [new LossExtension()], + updateTriggers: { + decodeParams: dParams, + decodeFunction: dFunction, + } + }); + } + return null; + }, + minZoom: 3, + maxZoom: 12, + } + ) + ] + }, [decodeFunction, decodeParams]); + + const handleViewportChange = useCallback((vw) => { + setViewport(vw); + }, []); + + return ( +
+ + {(map) => ( + + + + )} + +
+ ); +}; + +export const DecodedExtension = Template.bind({}); +DecodedExtension.args = { + id: 'deck-loss-raster-decode', + type: 'deck' +}; From 32a5b29a3c44771dd8b391963ae1a382c49bdcc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Barrenechea=20S=C3=A1nchez?= Date: Tue, 16 Aug 2022 20:54:44 +0200 Subject: [PATCH 2/9] Shaders: examples --- .../extensions/decode-extension.stories.tsx | 100 +++++--- .../extensions/test-extension.stories.tsx | 238 ++++++++++++++++++ 2 files changed, 307 insertions(+), 31 deletions(-) create mode 100644 applications/docs/src/stories/playground/extensions/test-extension.stories.tsx diff --git a/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx b/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx index f45963f..ad2b3d6 100644 --- a/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx +++ b/applications/docs/src/stories/playground/extensions/decode-extension.stories.tsx @@ -44,36 +44,7 @@ export default { decodeFunction: { name: 'decodeFunction', type: { name: 'string', required: true }, - defaultValue: `// values for creating power scale, domain (input), and range (output) -float domainMin = 0.; -float domainMax = 255.; -float rangeMin = 0.; -float rangeMax = 255.; - -float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; -float intensity = color.r * 255.; - -// get the min, max, and current values on the power scale -float minPow = pow(domainMin, exponent - domainMin); -float maxPow = pow(domainMax, exponent); -float currentPow = pow(intensity, exponent); - -// get intensity value mapped to range -float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; -// a value between 0 and 255 -color.a = zoom < 13. ? scaleIntensity / 255. : color.g; - -float year = 2000.0 + (color.b * 255.); - -// map to years -if (year >= startYear && year <= endYear && year >= 2001.) { - color.r = 220. / 255.; - color.g = (72. - zoom + 102. - 3. * scaleIntensity / zoom) / 255.; - color.b = (33. - zoom + 153. - intensity / zoom) / 255.; -} else { - color.a = 0.0; -} - `, + defaultValue: ``, description: 'The decode function you will apply to each tile pixel', control: { type: 'text' @@ -236,5 +207,72 @@ const Template: Story = (args: any) => { export const DecodedExtension = Template.bind({}); DecodedExtension.args = { id: 'deck-loss-raster-decode', - type: 'deck' + type: 'deck', + decodeFunction: `// values for creating power scale, domain (input), and range (output) + float domainMin = 0.; + float domainMax = 255.; + float rangeMin = 0.; + float rangeMax = 255.; + + float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; + float intensity = color.r * 255.; + + // get the min, max, and current values on the power scale + float minPow = pow(domainMin, exponent - domainMin); + float maxPow = pow(domainMax, exponent); + float currentPow = pow(intensity, exponent); + + // get intensity value mapped to range + float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; + // a value between 0 and 255 + color.a = zoom < 13. ? scaleIntensity / 255. : color.g; + + float year = 2000.0 + (color.b * 255.); + + // map to years + if (year >= startYear && year <= endYear && year >= 2001.) { + color.r = 220. / 255.; + color.g = (72. - zoom + 102. - 3. * scaleIntensity / zoom) / 255.; + color.b = (33. - zoom + 153. - intensity / zoom) / 255.; + } else { + discard; + }` }; + +export const LossByYear = Template.bind({}); +LossByYear.args = { + id: 'deck-loss-by-year-raster-decode', + type: 'deck', + decodeFunction: `// values for creating power scale, domain (input), and range (output) + float domainMin = 0.; + float domainMax = 255.; + float rangeMin = 0.; + float rangeMax = 255.; + + float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; + float intensity = color.r * 255.; + + // get the min, max, and current values on the power scale + float minPow = pow(domainMin, exponent - domainMin); + float maxPow = pow(domainMax, exponent); + float currentPow = pow(intensity, exponent); + + // get intensity value mapped to range + float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; + // a value between 0 and 255 + color.a = zoom < 13. ? scaleIntensity / 255. : color.g; + + float year = 2000.0 + (color.b * 255.); + float totalYears = 2017. - 2001.; + float yearFraction = (year - 2001.) / totalYears; + + // map to years + if (year >= startYear && year <= endYear && year >= 2001.) { + float b = (33. - zoom + 153. - intensity / zoom) / 255.; + color.r = 220. / 255.; + color.g = (72. - zoom + 102. - 3. * scaleIntensity / zoom) / 255.; + color.b = mix(b, 0., yearFraction); + } else { + discard; + }` +}; \ No newline at end of file diff --git a/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx b/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx new file mode 100644 index 0000000..4dce63a --- /dev/null +++ b/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx @@ -0,0 +1,238 @@ + +import React, { useCallback, useMemo, useState } from 'react'; +import { Story } from '@storybook/react/types-6-0'; +// Layer manager +import { LayerManager, Layer, LayerProps } from '@vizzuality/layer-manager-react'; +import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl'; +import CartoProvider from '@vizzuality/layer-manager-provider-carto'; + +import GL from '@luma.gl/constants'; +import { TileLayer } from '@deck.gl/geo-layers'; +import { BitmapLayer } from '@deck.gl/layers'; +import { MapboxLayer } from '@deck.gl/mapbox'; +import { LayerExtension } from '@deck.gl/core'; + +// Map +import Map from '../../../components/map'; + +const cartoProvider = new CartoProvider(); + +export default { + title: 'Playground/Extensions', + argTypes: { + deck: { + table: { + disable: true + } + }, + tileUrl: { + name: 'tileUrl', + type: { name: 'Tile URL', required: true }, + defaultValue: 'https://earthengine.google.org/static/hansen_2013/gain_alpha/{z}/{x}/{y}.png', + control: { + type: 'text' + }, + }, + decodeParams: { + name: 'decodeParams', + type: { name: 'object', required: true }, + defaultValue: { + startYear: 2001, + endYear: 2017, + } + }, + decodeFunction: { + name: 'decodeFunction', + type: { name: 'string', required: true }, + defaultValue: ``, + description: 'The decode function you will apply to each tile pixel', + control: { + type: 'text' + } + } + }, +}; + +class TestExtension extends LayerExtension { + getShaders() { + return { + inject: { + 'vs:#decl': ` + varying vec4 vTexWorld; + varying vec3 vTexWorldCommon; + `, + 'vs:#main-end': ` + vTexWorld = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position); + vTexWorldCommon = positions.xyz; + `, + 'fs:#decl': ` + varying vec4 vTexWorld; + varying vec3 vTexWorldCommon; + uniform float zoom; + uniform float startYear; + uniform float endYear; + `, + + 'fs:#main-end': ` + ${this.props.decodeFunction} + ` + } + }; + } + + updateState({ props, changeFlags }) { + const { + decodeParams = {}, + zoom + } = props; + + if (changeFlags.extensionsChanged || changeFlags.somethingChanged.decodeFunction) { + const { gl } = this.context; + this.state.model?.delete(); + this.state.model = this._getModel(gl); + this.getAttributeManager().invalidateAll(); + } + + for (const model of this.getModels()) { + model.setUniforms({ + zoom, + ...decodeParams, + }); + } + } +} + +const Template: Story = (args: any) => { + const { id, tileUrl, decodeFunction, decodeParams } = args; + + const minZoom = 2; + const maxZoom = 20; + const [viewport, setViewport] = useState({}); + + const [bounds] = useState(null); + + const DECK_LAYERS = useMemo(() => { + return [ + new MapboxLayer( + { + id, + type: TileLayer, + data: tileUrl, + tileSize: 256, + visible: true, + opacity: 1, + refinementStrategy: 'no-overlap', + decodeFunction, + decodeParams, + renderSubLayers: (sl) => { + const { + id: subLayerId, + data, + tile, + visible, + opacity: _opacity, + decodeParams: dParams, + decodeFunction: dFunction, + } = sl; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + if (data) { + return new BitmapLayer({ + id: subLayerId, + image: data, + bounds: [west, south, east, north], + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity: _opacity, + decodeParams: dParams, + decodeFunction: dFunction, + extensions: [new TestExtension()], + updateTriggers: { + decodeParams: dParams, + decodeFunction: dFunction, + } + }); + } + return null; + }, + minZoom: 3, + maxZoom: 12, + } + ) + ] + }, [decodeFunction, decodeParams]); + + const handleViewportChange = useCallback((vw) => { + setViewport(vw); + }, []); + + return ( +
+ + {(map) => ( + + + + )} + +
+ ); +}; + +export const TestVarying = Template.bind({}); +TestVarying.args = { + id: 'deck-loss-raster-decode', + type: 'deck', + decodeFunction: `// decode function + vec3 color = mix(bitmapColor.rgb, vec3(1.0,0.0,0.0), vTexWorld.x); + // vec3 color = mix(bitmapColor.rgb, vec3(1.0,0.0,0.0), (abs(vTexWorldCommon.x / 180.))); + gl_FragColor = vec4(color, bitmapColor.a); + ` +}; + +export const TestStep = Template.bind({}); +TestStep.args = { + id: 'deck-loss-raster-decode', + type: 'deck', + decodeFunction: `// decode function + float step = step(0., vTexWorldCommon.y); + vec3 color = mix(bitmapColor.rgb, vec3(1.0,0.0,0.0), step); + gl_FragColor = vec4(color, bitmapColor.a); + ` +}; + From 9c7fda59da1f4b9c357c58305f3968551e4b9151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Barrenechea=20S=C3=A1nchez?= Date: Fri, 16 Sep 2022 11:38:12 +0200 Subject: [PATCH 3/9] Deck gl presentation --- .../stories/layers/vector/default.stories.tsx | 22 +- .../apng-layer/deck-presentation.stories.tsx | 394 ++++++++++++++++++ .../decoded-raster-layer/default.stories.tsx | 2 +- .../extensions/test-extension.stories.tsx | 74 +++- 4 files changed, 462 insertions(+), 30 deletions(-) create mode 100644 applications/docs/src/stories/playground/apng-layer/deck-presentation.stories.tsx diff --git a/applications/docs/src/stories/layers/vector/default.stories.tsx b/applications/docs/src/stories/layers/vector/default.stories.tsx index d39b7bd..31f6069 100644 --- a/applications/docs/src/stories/layers/vector/default.stories.tsx +++ b/applications/docs/src/stories/layers/vector/default.stories.tsx @@ -82,36 +82,22 @@ Default.args = { type: 'vector', source: { type: 'vector', - url: 'mapbox://mapbox.country-boundaries-v1', + tiles: ['https://vectortileservices3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Santa_Monica_Mountains_Parcels_VTL/VectorTileServer/tile/{z}/{y}/{x}.pbf'], }, render: { layers: [ { interactive: true, type: 'fill', - 'source-layer': 'country_boundaries', + 'source-layer': 'Santa_Monica_Mountains_Parcels', paint: { - 'fill-color': [ - 'match', - ['get', 'region'], - 'Africa', - '#fbb03b', - 'Americas', - '#223b53', - 'Europe', - '#e55e5e', - 'Asia', - '#3bb2d0', - 'Oceania', - '#ffcc00', - /* other */ '#ccc' - ], + 'fill-color': '#ccc' } }, { interactive: true, type: 'line', - 'source-layer': 'country_boundaries', + 'source-layer': 'Santa_Monica_Mountains_Parcels', paint: { 'line-color': '#000', 'line-width': 1, diff --git a/applications/docs/src/stories/playground/apng-layer/deck-presentation.stories.tsx b/applications/docs/src/stories/playground/apng-layer/deck-presentation.stories.tsx new file mode 100644 index 0000000..92fc0d8 --- /dev/null +++ b/applications/docs/src/stories/playground/apng-layer/deck-presentation.stories.tsx @@ -0,0 +1,394 @@ + +import React, { useCallback, useMemo, useState } from 'react'; +import { Story } from '@storybook/react/types-6-0'; +// Layer manager +import { LayerManager, Layer, LayerProps } from '@vizzuality/layer-manager-react'; +import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl'; +import CartoProvider from '@vizzuality/layer-manager-provider-carto'; + +import GL from '@luma.gl/constants'; +import { MapboxLayer } from '@deck.gl/mapbox'; +import { TileLayer } from '@deck.gl/geo-layers'; +import { BitmapLayer } from '@deck.gl/layers'; +import { DecodedLayer } from '@vizzuality/layer-manager-layers-deckgl'; + +import parseAPNG from 'apng-js'; + +// Map +import Map from '../../../components/map'; +import useInterval from '../../layers/deck/utils'; + +const cartoProvider = new CartoProvider(); + +export default { + title: 'Playground/APNG-Layer', + argTypes: { + }, +}; + +const Template: Story = (args: LayerProps) => { + const [frame, setFrame] = useState(0); + const [delay, setDelay] = useState(null); + const [lossVisible, setLossVisible] = useState(true); + const minZoom = 0; + const maxZoom = 20; + const [viewport, setViewport] = useState({}); + const [bounds] = useState({ + bbox: [48.831181, -13.983606, 48.97221, -13.856368], + options: { + duration: 0, + } + }); + + useInterval(() => { + // 2001-2021 + const f = (frame === 20 - 1) ? 0 : frame + 1; + + setFrame(f); + }, delay); + + const DECK_LAYERS = useMemo(() => { + return [ + new MapboxLayer( + { + id: `prediction-animated`, + type: TileLayer, + frame, + getPolygonOffset: () => { + return [0, -50]; + }, + + + getTileData: (tile) => { + const { x, y, z, signal } = tile; + const url = `https://storage.googleapis.com/geo-ai/Redes/Tiles/Tsaratanana/APNGs/Prediction/${z}/${x}/${y}.png`; + const response = fetch(url, { signal }); + + if (signal.aborted) { + return null; + } + + return response + .then((res) => res.arrayBuffer()) + .then((buffer) => { + const apng = parseAPNG(buffer); + if (apng instanceof Error) { + throw apng; + } + + return apng.frames.map((frame) => { + return { + ...frame, + bitmapData: createImageBitmap(frame.imageData), + }; + }); + }); + }, + tileSize: 256, + visible: true, + opacity: 1, + refinementStrategy: 'no-overlap', + renderSubLayers: (sl) => { + if (!sl) return null; + + const { + id: subLayerId, + data, + tile, + visible, + opacity = 1, + frame: f + } = sl; + + if (!tile || !data) return null; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + const FRAME = data[f]; + + if (FRAME) { + return new BitmapLayer({ + id: subLayerId, + image: FRAME.bitmapData, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -50]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity, + }); + } + return null; + }, + minZoom: 10, + maxZoom: 14, + extent: bounds.bbox, + } + ), + new MapboxLayer( + { + id: `deck-loss-raster-decode-animated`, + type: TileLayer, + // data: 'https://storage.googleapis.com/wri-public/Hansen_16/tiles/hansen_world/v1/tc30/{z}/{x}/{y}.png', + data: 'https://tiles.globalforestwatch.org/umd_tree_cover_loss/v1.9/tcd_30/{z}/{x}/{y}.png', + tileSize: 256, + visible: true, + opacity: lossVisible ? 0.35 : 0, + refinementStrategy: 'no-overlap', + decodeParams: { + startYear: 2001, + endYear: 2001 + frame, + }, + getPolygonOffset: () => { + return [0, -100]; + }, + + renderSubLayers: (sl) => { + const { + id: subLayerId, + data, + tile, + visible, + opacity: _opacity, + decodeParams: _decodeParams, + } = sl; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + if (data) { + return new DecodedLayer({ + id: subLayerId, + image: data, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -100]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity: _opacity, + decodeParams: _decodeParams, + decodeFunction: ` + // values for creating power scale, domain (input), and range (output) + float domainMin = 0.; + float domainMax = 255.; + float rangeMin = 0.; + float rangeMax = 255.; + + float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; + float intensity = color.r * 255.; + + // get the min, max, and current values on the power scale + float minPow = pow(domainMin, exponent - domainMin); + float maxPow = pow(domainMax, exponent); + float currentPow = pow(intensity, exponent); + + // get intensity value mapped to range + float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; + // a value between 0 and 255 + alpha = zoom < 13. ? scaleIntensity / 255. : color.g; + + float year = 2000.0 + (color.b * 255.); + // map to years + if (year >= startYear && year <= endYear && year >= 2001.) { + color.r = 220. / 255.; + color.g = (72. - zoom + 102. - 3. * scaleIntensity / zoom) / 255.; + color.b = (33. - zoom + 153. - intensity / zoom) / 255.; + } else { + alpha = 0.; + } + ` + }); + } + return null; + }, + minZoom: 3, + maxZoom: 12, + } + ) + ] + }, [frame, lossVisible]); + + const handleViewportChange = useCallback((vw) => { + setViewport(vw); + }, []); + + return ( +
+
+ + { + setDelay(null); + setFrame(+e.target.value - 2001); + }} + /> + + {2001 + frame} + +
+ +
+
+ + { + setLossVisible(e.target.checked) + console.log(e.target.checked); + }} + /> +
+
+ + {(map) => ( + <> + + + + + + )} + +
+ ); +}; + + + +export const Presentation = Template.bind({}); +Presentation.args = { + id: 'presentation-layer', + type: 'deck', + source: { + parse: false, + }, + render: { + parse: false + }, + deck: [] +}; diff --git a/applications/docs/src/stories/playground/decoded-raster-layer/default.stories.tsx b/applications/docs/src/stories/playground/decoded-raster-layer/default.stories.tsx index 3c68afe..734cc58 100644 --- a/applications/docs/src/stories/playground/decoded-raster-layer/default.stories.tsx +++ b/applications/docs/src/stories/playground/decoded-raster-layer/default.stories.tsx @@ -169,7 +169,7 @@ const Template: Story = (args: any) => { minZoom={minZoom} maxZoom={maxZoom} viewState={viewport} - mapStyle="mapbox://styles/mapbox/light-v9" + mapStyle="mapbox://styles/layer-manager/cl7stzzqj004t14lfz0mhbkve" mapboxAccessToken={process.env.STORYBOOK_MAPBOX_API_TOKEN} onViewStateChange={handleViewportChange} > diff --git a/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx b/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx index 4dce63a..e1d556d 100644 --- a/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx +++ b/applications/docs/src/stories/playground/extensions/test-extension.stories.tsx @@ -58,19 +58,36 @@ class TestExtension extends LayerExtension { return { inject: { 'vs:#decl': ` - varying vec4 vTexWorld; - varying vec3 vTexWorldCommon; + uniform float u_mouseLng; + uniform float u_mouseLat; + varying vec4 v_texWorld; + varying vec3 v_texWorldCommon; + varying vec4 v_mousePosition; + varying vec3 v_mousePositionCommon; `, 'vs:#main-end': ` - vTexWorld = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position); - vTexWorldCommon = positions.xyz; + v_texWorld = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position); + v_texWorldCommon = positions.xyz; + v_mousePosition = project_position_to_clipspace(vec3(u_mouseLng, u_mouseLat, 0.0), positions64Low, vec3(0.0)); + v_mousePositionCommon = vec3(u_mouseLng, u_mouseLat, 0.0); `, 'fs:#decl': ` - varying vec4 vTexWorld; - varying vec3 vTexWorldCommon; uniform float zoom; uniform float startYear; uniform float endYear; + uniform float u_mouseLng; + uniform float u_mouseLat; + varying vec4 v_texWorld; + varying vec3 v_texWorldCommon; + varying vec4 v_mousePosition; + + float circle(vec2 pt, vec2 center, float radius, float edge_thickness){ + vec2 p = pt - center; + float len = length(p); + float result = 1.0-smoothstep(radius-edge_thickness, radius, len); + + return result; + } `, 'fs:#main-end': ` @@ -83,7 +100,9 @@ class TestExtension extends LayerExtension { updateState({ props, changeFlags }) { const { decodeParams = {}, - zoom + zoom, + u_mouseLng, + u_mouseLat, } = props; if (changeFlags.extensionsChanged || changeFlags.somethingChanged.decodeFunction) { @@ -94,8 +113,11 @@ class TestExtension extends LayerExtension { } for (const model of this.getModels()) { + console.log(u_mouseLat, u_mouseLng); model.setUniforms({ zoom, + u_mouseLng, + u_mouseLat, ...decodeParams, }); } @@ -108,6 +130,10 @@ const Template: Story = (args: any) => { const minZoom = 2; const maxZoom = 20; const [viewport, setViewport] = useState({}); + const [mouseLngLat, setMouseLngLat] = useState({ + lng: 0, + lat: 0 + }); const [bounds] = useState(null); @@ -124,12 +150,16 @@ const Template: Story = (args: any) => { refinementStrategy: 'no-overlap', decodeFunction, decodeParams, + u_mouseLng: mouseLngLat.lng, + u_mouseLat: mouseLngLat.lat, renderSubLayers: (sl) => { const { id: subLayerId, data, tile, visible, + u_mouseLng, + u_mouseLat, opacity: _opacity, decodeParams: dParams, decodeFunction: dFunction, @@ -156,6 +186,8 @@ const Template: Story = (args: any) => { zoom: z, visible, opacity: _opacity, + u_mouseLng, + u_mouseLat, decodeParams: dParams, decodeFunction: dFunction, extensions: [new TestExtension()], @@ -172,12 +204,18 @@ const Template: Story = (args: any) => { } ) ] - }, [decodeFunction, decodeParams]); + }, [decodeFunction, decodeParams, mouseLngLat]); const handleViewportChange = useCallback((vw) => { setViewport(vw); }, []); + const handleMouseMove = useCallback((e) => { + if (e.lngLat) { + setMouseLngLat(e.lngLat); + } + } ,[]); + return (
= (args: any) => { mapStyle="mapbox://styles/mapbox/light-v9" mapboxAccessToken={process.env.STORYBOOK_MAPBOX_API_TOKEN} onViewStateChange={handleViewportChange} + onMouseMove={handleMouseMove} > {(map) => ( Date: Thu, 20 Oct 2022 01:03:10 +0200 Subject: [PATCH 4/9] Examples: tsaratanana & kigali --- .../playground/apng-layer/kigali.stories.tsx | 258 ++++++++++++++++++ .../apng-layer/tsaratanana.stories.tsx | 248 +++++++++++++++++ 2 files changed, 506 insertions(+) create mode 100644 applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx create mode 100644 applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx diff --git a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx new file mode 100644 index 0000000..7b5f52b --- /dev/null +++ b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx @@ -0,0 +1,258 @@ + +import React, { useCallback, useMemo, useState } from 'react'; +import { Story } from '@storybook/react/types-6-0'; +// Layer manager +import { LayerManager, Layer, LayerProps } from '@vizzuality/layer-manager-react'; +import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl'; +import CartoProvider from '@vizzuality/layer-manager-provider-carto'; + +import GL from '@luma.gl/constants'; +import { MapboxLayer } from '@deck.gl/mapbox'; +import { TileLayer } from '@deck.gl/geo-layers'; +import { BitmapLayer } from '@deck.gl/layers'; + +import parseAPNG from 'apng-js'; + +// Map +import Map from '../../../components/map'; +import useInterval from '../../layers/deck/utils'; + +const cartoProvider = new CartoProvider(); + +export default { + title: 'Playground/APNG-Layer', + argTypes: { + }, +}; + +const Template: Story = (args: LayerProps) => { + const [frame, setFrame] = useState(0); + const [delay, setDelay] = useState(null); + const minZoom = 0; + const maxZoom = 20; + const [viewport, setViewport] = useState({}); + const [bounds] = useState({ + bbox: [29.977519989043227, -2.079803228378188, 30.277151107802222, -1.779561400413334], + options: { + duration: 0, + } + }); + + useInterval(() => { + // 2017-2020 + const f = (frame === 3 - 1) ? 0 : frame + 1; + + setFrame(f); + }, delay); + + const DECK_LAYERS = useMemo(() => { + return [ + new MapboxLayer( + { + id: `prediction-animated`, + type: TileLayer, + frame, + getPolygonOffset: () => { + return [0, -50]; + }, + + + getTileData: (tile) => { + const { x, y, z, signal } = tile; + const url = `https://storage.googleapis.com/geo-ai/Redes/Tiles/Kigali/APNGs/Sentinel/${z}/${x}/${y}.png`; + const response = fetch(url, { signal }); + + if (signal.aborted) { + return null; + } + + return response + .then((res) => res.arrayBuffer()) + .then((buffer) => { + const apng = parseAPNG(buffer); + if (apng instanceof Error) { + throw apng; + } + + return apng.frames.map((frame) => { + return { + ...frame, + bitmapData: createImageBitmap(frame.imageData), + }; + }); + }); + }, + tileSize: 256, + visible: true, + opacity: 1, + refinementStrategy: 'no-overlap', + renderSubLayers: (sl) => { + if (!sl) return null; + + const { + id: subLayerId, + data, + tile, + visible, + opacity = 1, + frame: f + } = sl; + + if (!tile || !data) return null; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + const FRAME = data[f]; + + if (FRAME) { + return new BitmapLayer({ + id: subLayerId, + image: FRAME.bitmapData, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -50]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity, + }); + } + return null; + }, + minZoom: 10, + maxZoom: 14, + extent: bounds.bbox, + } + ), + ] + }, [frame]); + + const handleViewportChange = useCallback((vw) => { + setViewport(vw); + }, []); + + return ( +
+
+ + { + setDelay(null); + setFrame(+e.target.value - 2017); + }} + /> + + {2017 + frame} + +
+ + + + {(map) => ( + <> + + + + + + + )} + +
+ ); +}; + + + +export const Kigali = Template.bind({}); +Kigali.args = { + id: 'kigali-layer', + type: 'deck', + source: { + parse: false, + }, + render: { + parse: false + }, + deck: [] +}; diff --git a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx new file mode 100644 index 0000000..c314aad --- /dev/null +++ b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx @@ -0,0 +1,248 @@ + +import React, { useCallback, useMemo, useState } from 'react'; +import { Story } from '@storybook/react/types-6-0'; +// Layer manager +import { LayerManager, Layer, LayerProps } from '@vizzuality/layer-manager-react'; +import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl'; +import CartoProvider from '@vizzuality/layer-manager-provider-carto'; + +import GL from '@luma.gl/constants'; +import { MapboxLayer } from '@deck.gl/mapbox'; +import { TileLayer } from '@deck.gl/geo-layers'; +import { BitmapLayer } from '@deck.gl/layers'; + +import parseAPNG from 'apng-js'; + +// Map +import Map from '../../../components/map'; +import useInterval from '../../layers/deck/utils'; + +const cartoProvider = new CartoProvider(); + +export default { + title: 'Playground/APNG-Layer', + argTypes: { + }, +}; + +const Template: Story = (args: LayerProps) => { + const [frame, setFrame] = useState(0); + const [delay, setDelay] = useState(null); + const minZoom = 0; + const maxZoom = 20; + const [viewport, setViewport] = useState({}); + const [bounds] = useState({ + bbox: [48.7518310546875, -14.077973196671586, 49.04846191406249, -13.755392488822052], + options: { + duration: 0, + } + }); + + useInterval(() => { + // 2017-2020 + const f = (frame === 3 - 1) ? 0 : frame + 1; + + setFrame(f); + }, delay); + + const DECK_LAYERS = useMemo(() => { + return [ + new MapboxLayer( + { + id: `prediction-animated`, + type: TileLayer, + frame, + getPolygonOffset: () => { + return [0, -50]; + }, + + + getTileData: (tile) => { + const { x, y, z, signal } = tile; + const url = `https://storage.googleapis.com/geo-ai/Redes/Tiles/Tsaratanana2/APNGs/Sentinel/${z}/${x}/${y}.png`; + const response = fetch(url, { signal }); + + if (signal.aborted) { + return null; + } + + return response + .then((res) => res.arrayBuffer()) + .then((buffer) => { + const apng = parseAPNG(buffer); + if (apng instanceof Error) { + throw apng; + } + + return apng.frames.map((frame) => { + return { + ...frame, + bitmapData: createImageBitmap(frame.imageData), + }; + }); + }); + }, + tileSize: 256, + visible: true, + opacity: 1, + refinementStrategy: 'no-overlap', + renderSubLayers: (sl) => { + if (!sl) return null; + + const { + id: subLayerId, + data, + tile, + visible, + opacity = 1, + frame: f + } = sl; + + if (!tile || !data) return null; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + const FRAME = data[f]; + + if (FRAME) { + return new BitmapLayer({ + id: subLayerId, + image: FRAME.bitmapData, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -50]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity, + }); + } + return null; + }, + minZoom: 10, + maxZoom: 14, + extent: bounds.bbox, + } + ), + ] + }, [frame]); + + const handleViewportChange = useCallback((vw) => { + setViewport(vw); + }, []); + + return ( +
+
+ + { + setDelay(null); + setFrame(+e.target.value - 2017); + }} + /> + + {2017 + frame} + +
+ + + + {(map) => ( + <> + + + + + + )} + +
+ ); +}; + + + +export const Kigali = Template.bind({}); +Kigali.args = { + id: 'kigali-layer', + type: 'deck', + source: { + parse: false, + }, + render: { + parse: false + }, + deck: [] +}; From 11b2e31becebbbb76d7e7002a6537f2e896d3378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Barrenechea=20S=C3=A1nchez?= Date: Thu, 20 Oct 2022 01:04:43 +0200 Subject: [PATCH 5/9] Examples: tsaratanana & kigali --- .../src/stories/playground/apng-layer/tsaratanana.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx index c314aad..3d351fe 100644 --- a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx @@ -234,8 +234,8 @@ const Template: Story = (args: LayerProps) => { -export const Kigali = Template.bind({}); -Kigali.args = { +export const Tsaratanana = Template.bind({}); +Tsaratanana.args = { id: 'kigali-layer', type: 'deck', source: { From 44b412c8d690af0339304800ecddc0c5d90ab215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Barrenechea=20S=C3=A1nchez?= Date: Mon, 24 Oct 2022 15:49:09 +0200 Subject: [PATCH 6/9] Examples: tsaratanana & kigali --- .../playground/apng-layer/kigali.stories.tsx | 48 ++++++++++++++++++- .../apng-layer/tsaratanana.stories.tsx | 35 +++++++++++++- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx index 7b5f52b..667292b 100644 --- a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx @@ -26,6 +26,10 @@ export default { }; const Template: Story = (args: LayerProps) => { + const [biodiversityIntactnessOpacity, setBiodiversityIntactnessOpacity] = useState(1); + const [humanFootprintOpacity, setHumanFootprintOpacity] = useState(1); + + const [frame, setFrame] = useState(0); const [delay, setDelay] = useState(null); const minZoom = 0; @@ -40,7 +44,7 @@ const Template: Story = (args: LayerProps) => { useInterval(() => { // 2017-2020 - const f = (frame === 3 - 1) ? 0 : frame + 1; + const f = (frame === 4 - 1) ? 0 : frame + 1; setFrame(f); }, delay); @@ -150,6 +154,7 @@ const Template: Story = (args: LayerProps) => { height: '500px', }} > + {/* Timeline */}
= (args: LayerProps) => {
+ {/* Layers */} +
+
+ + { + setBiodiversityIntactnessOpacity(e.target.checked ? 1 : 0); + }} + /> +
+
+ + { + setHumanFootprintOpacity(e.target.checked ? 1 : 0); + }} + /> +
+
+ = (args: LayerProps) => { 'https://storage.googleapis.com/geo-ai/Redes/Tiles/Kigali/BII/{z}/{x}/{y}.png' ] }} + opacity={biodiversityIntactnessOpacity} /> = (args: LayerProps) => { 'https://storage.googleapis.com/geo-ai/Redes/Tiles/Kigali/HFC/{z}/{x}/{y}.png' ] }} + opacity={humanFootprintOpacity} /> = (args: LayerProps) => { + const [biodiversityIntactnessOpacity, setBiodiversityIntactnessOpacity] = useState(1); + const [frame, setFrame] = useState(0); const [delay, setDelay] = useState(null); const minZoom = 0; @@ -40,7 +42,7 @@ const Template: Story = (args: LayerProps) => { useInterval(() => { // 2017-2020 - const f = (frame === 3 - 1) ? 0 : frame + 1; + const f = (frame === 4 - 1) ? 0 : frame + 1; setFrame(f); }, delay); @@ -150,6 +152,7 @@ const Template: Story = (args: LayerProps) => { height: '500px', }} > + {/* Timeline */}
= (args: LayerProps) => {
+ {/* Layers */} +
+
+ + { + setBiodiversityIntactnessOpacity(e.target.checked ? 1 : 0); + }} + /> +
+
+ = (args: LayerProps) => { 'https://storage.googleapis.com/geo-ai/Redes/Tiles/Tsaratanana/BII/{z}/{x}/{y}.png' ] }} + opacity={biodiversityIntactnessOpacity} /> Date: Tue, 8 Nov 2022 10:50:44 +0100 Subject: [PATCH 7/9] Kigali: layers --- .../playground/apng-layer/kigali.stories.tsx | 125 +++++++++++++++--- 1 file changed, 106 insertions(+), 19 deletions(-) diff --git a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx index 667292b..75bb0f1 100644 --- a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx @@ -26,17 +26,17 @@ export default { }; const Template: Story = (args: LayerProps) => { - const [biodiversityIntactnessOpacity, setBiodiversityIntactnessOpacity] = useState(1); - const [humanFootprintOpacity, setHumanFootprintOpacity] = useState(1); + const [biiOpacity, setBiiOpacity] = useState(1); + const [biiChangeOpacity, setHumanFootprintOpacity] = useState(1); const [frame, setFrame] = useState(0); const [delay, setDelay] = useState(null); - const minZoom = 0; - const maxZoom = 20; + const minZoom = 10; + const maxZoom = 14; const [viewport, setViewport] = useState({}); const [bounds] = useState({ - bbox: [29.977519989043227, -2.079803228378188, 30.277151107802222, -1.779561400413334], + bbox: [29.882812499999986, -2.1088986592431382, 30.5859375, -1.75753681130829994], options: { duration: 0, } @@ -139,8 +139,96 @@ const Template: Story = (args: LayerProps) => { extent: bounds.bbox, } ), + new MapboxLayer( + { + id: `BII-animated`, + type: TileLayer, + frame, + getPolygonOffset: () => { + return [0, -50]; + }, + + + getTileData: (tile) => { + const { x, y, z, signal } = tile; + const url = `https://storage.googleapis.com/geo-ai/Redes/Tiles/Kigali/BII/APNGs/${z}/${x}/${y}.png`; + const response = fetch(url, { signal }); + + if (signal.aborted) { + return null; + } + + return response + .then((res) => res.arrayBuffer()) + .then((buffer) => { + const apng = parseAPNG(buffer); + if (apng instanceof Error) { + throw apng; + } + + return apng.frames.map((frame) => { + return { + ...frame, + bitmapData: createImageBitmap(frame.imageData), + }; + }); + }); + }, + tileSize: 256, + visible: true, + opacity: biiOpacity, + refinementStrategy: 'no-overlap', + renderSubLayers: (sl) => { + if (!sl) return null; + + const { + id: subLayerId, + data, + tile, + visible, + opacity = 1, + frame: f + } = sl; + + if (!tile || !data) return null; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + const FRAME = data[f]; + + if (FRAME) { + return new BitmapLayer({ + id: subLayerId, + image: FRAME.bitmapData, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -50]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity, + }); + } + return null; + }, + minZoom: 10, + maxZoom: 14, + extent: bounds.bbox, + } + ), ] - }, [frame]); + }, [frame, biiOpacity]); const handleViewportChange = useCallback((vw) => { setViewport(vw); @@ -209,20 +297,20 @@ const Template: Story = (args: LayerProps) => { }} >
- + { - setBiodiversityIntactnessOpacity(e.target.checked ? 1 : 0); + setBiiOpacity(e.target.checked ? 1 : 0); }} />
- + { setHumanFootprintOpacity(e.target.checked ? 1 : 0); }} @@ -232,14 +320,13 @@ const Template: Story = (args: LayerProps) => { @@ -252,7 +339,7 @@ const Template: Story = (args: LayerProps) => { [cartoProvider.name]: cartoProvider.handleData, }} > - = (args: LayerProps) => { 'https://storage.googleapis.com/geo-ai/Redes/Tiles/Kigali/BII/{z}/{x}/{y}.png' ] }} - opacity={biodiversityIntactnessOpacity} - /> + opacity={biiOpacity} + /> */} Date: Tue, 8 Nov 2022 11:01:40 +0100 Subject: [PATCH 8/9] Tsaratanana: layers --- .../apng-layer/tsaratanana.stories.tsx | 136 ++++++++++++++++-- 1 file changed, 123 insertions(+), 13 deletions(-) diff --git a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx index 95398e9..eaf71e0 100644 --- a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx @@ -26,15 +26,17 @@ export default { }; const Template: Story = (args: LayerProps) => { - const [biodiversityIntactnessOpacity, setBiodiversityIntactnessOpacity] = useState(1); + const [biiOpacity, setBiiOpacity] = useState(1); + const [biiChangeOpacity, setHumanFootprintOpacity] = useState(1); + const [frame, setFrame] = useState(0); const [delay, setDelay] = useState(null); - const minZoom = 0; - const maxZoom = 20; + const minZoom = 11; + const maxZoom = 14; const [viewport, setViewport] = useState({}); const [bounds] = useState({ - bbox: [48.7518310546875, -14.077973196671586, 49.04846191406249, -13.755392488822052], + bbox: [48.69140625000001,-14.093957177836236,49.04296875,-13.752724664396975], options: { duration: 0, } @@ -137,8 +139,96 @@ const Template: Story = (args: LayerProps) => { extent: bounds.bbox, } ), + new MapboxLayer( + { + id: `BII-animated`, + type: TileLayer, + frame, + getPolygonOffset: () => { + return [0, -50]; + }, + + + getTileData: (tile) => { + const { x, y, z, signal } = tile; + const url = `https://storage.googleapis.com/geo-ai/Redes/Tiles/Tsaratanana/BII/APNGs/${z}/${x}/${y}.png`; + const response = fetch(url, { signal }); + + if (signal.aborted) { + return null; + } + + return response + .then((res) => res.arrayBuffer()) + .then((buffer) => { + const apng = parseAPNG(buffer); + if (apng instanceof Error) { + throw apng; + } + + return apng.frames.map((frame) => { + return { + ...frame, + bitmapData: createImageBitmap(frame.imageData), + }; + }); + }); + }, + tileSize: 256, + visible: true, + opacity: biiOpacity, + refinementStrategy: 'no-overlap', + renderSubLayers: (sl) => { + if (!sl) return null; + + const { + id: subLayerId, + data, + tile, + visible, + opacity = 1, + frame: f + } = sl; + + if (!tile || !data) return null; + + const { + z, + bbox: { + west, south, east, north, + }, + } = tile; + + const FRAME = data[f]; + + if (FRAME) { + return new BitmapLayer({ + id: subLayerId, + image: FRAME.bitmapData, + bounds: [west, south, east, north], + getPolygonOffset: () => { + return [0, -50]; + }, + textureParameters: { + [GL.TEXTURE_MIN_FILTER]: GL.NEAREST, + [GL.TEXTURE_MAG_FILTER]: GL.NEAREST, + [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, + [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE, + }, + zoom: z, + visible, + opacity, + }); + } + return null; + }, + minZoom: 10, + maxZoom: 14, + extent: bounds.bbox, + } + ), ] - }, [frame]); + }, [frame, biiOpacity]); const handleViewportChange = useCallback((vw) => { setViewport(vw); @@ -191,7 +281,7 @@ const Template: Story = (args: LayerProps) => {
- {/* Layers */} + {/* Layers */}
= (args: LayerProps) => { }} >
- + { - setBiodiversityIntactnessOpacity(e.target.checked ? 1 : 0); + setBiiOpacity(e.target.checked ? 1 : 0); + }} + /> +
+
+ + { + setHumanFootprintOpacity(e.target.checked ? 1 : 0); }} />
@@ -220,14 +320,13 @@ const Template: Story = (args: LayerProps) => { @@ -240,7 +339,7 @@ const Template: Story = (args: LayerProps) => { [cartoProvider.name]: cartoProvider.handleData, }} > - = (args: LayerProps) => { 'https://storage.googleapis.com/geo-ai/Redes/Tiles/Tsaratanana/BII/{z}/{x}/{y}.png' ] }} - opacity={biodiversityIntactnessOpacity} + opacity={biiOpacity} + /> */} + Date: Thu, 10 Nov 2022 13:53:48 +0100 Subject: [PATCH 9/9] Kigali & Tsaratanana: layers --- .../playground/apng-layer/kigali.stories.tsx | 4 +- .../apng-layer/tsaratanana.stories.tsx | 48 +++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx index 75bb0f1..5e6b830 100644 --- a/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/kigali.stories.tsx @@ -27,7 +27,7 @@ export default { const Template: Story = (args: LayerProps) => { const [biiOpacity, setBiiOpacity] = useState(1); - const [biiChangeOpacity, setHumanFootprintOpacity] = useState(1); + const [biiChangeOpacity, setBiiChangeOpacity] = useState(0); const [frame, setFrame] = useState(0); @@ -312,7 +312,7 @@ const Template: Story = (args: LayerProps) => { type="checkbox" checked={!!biiChangeOpacity} onChange={(e) => { - setHumanFootprintOpacity(e.target.checked ? 1 : 0); + setBiiChangeOpacity(e.target.checked ? 1 : 0); }} />
diff --git a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx index eaf71e0..f7d7831 100644 --- a/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx +++ b/applications/docs/src/stories/playground/apng-layer/tsaratanana.stories.tsx @@ -27,7 +27,7 @@ export default { const Template: Story = (args: LayerProps) => { const [biiOpacity, setBiiOpacity] = useState(1); - const [biiChangeOpacity, setHumanFootprintOpacity] = useState(1); + const [biiChangeOpacity, setHumanFootprintOpacity] = useState(0); const [frame, setFrame] = useState(0); @@ -339,17 +339,45 @@ const Template: Story = (args: LayerProps) => { [cartoProvider.name]: cartoProvider.handleData, }} > - {/* */} + /> + = (args: LayerProps) => { export const Tsaratanana = Template.bind({}); Tsaratanana.args = { - id: 'kigali-layer', + id: 'tsaratanana-layer', type: 'deck', source: { parse: false,