From abc81e3826118f8138eb8aea407edc0bd1554a29 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:13:27 +0100 Subject: [PATCH 01/12] Trying with subclassing --- src/App.tsx | 127 +++++++++++++++++++++++++++++------------------ src/delaunay.tsx | 86 +++++++++++++++++++++++++++++++- 2 files changed, 163 insertions(+), 50 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 5aa8611..2f5c8f5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,10 +7,10 @@ import { Protocol } from "pmtiles"; import { ScatterplotLayer, GeoJsonLayer } from "@deck.gl/layers"; import { interpolateYlOrRd } from "d3-scale-chromatic"; import { color } from "d3-color"; -import DelaunayLayer from "./delaunay.tsx"; +import { DelaunayLayer, MyCompositeLayer } from "./delaunay.tsx"; import { useMenuStore } from "./Store"; - -import { DataFilterExtension } from "@deck.gl/extensions"; +import { MVTLayer } from "@deck.gl/geo-layers"; +import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; const INITIAL_VIEW_STATE = { latitude: 62.5, @@ -31,36 +31,36 @@ export function App() { } }); - const step = 1; - const loopLength = 2500; - const [time, setTime] = useState(0); - const [z, setZ] = useState(0); - const [animation] = useState({}); - const animate = () => { - setTime((t) => (t + step) % loopLength); - if (z > 13) { - setZ(0); - } - setZ((t) => t + 1); - setTimeout(() => { - animation.id = window.requestAnimationFrame(animate); // draw next frame - }, 100); - }; - useEffect(() => { - if (!true) { - window.cancelAnimationFrame(animation.id); - return; - } + // const step = 1; + // const loopLength = 2500; + // const [time, setTime] = useState(0); + // const [z, setZ] = useState(0); + // const [animation] = useState({}); + // const animate = () => { + // setTime((t) => (t + step) % loopLength); + // if (z > 13) { + // setZ(0); + // } + // setZ((t) => t + 1); + // setTimeout(() => { + // animation.id = window.requestAnimationFrame(animate); // draw next frame + // }, 100); + // }; + // useEffect(() => { + // if (!true) { + // window.cancelAnimationFrame(animation.id); + // return; + // } - animation.id = window.requestAnimationFrame(animate); // start animation - return () => window.cancelAnimationFrame(animation.id); - }, [true]); + // animation.id = window.requestAnimationFrame(animate); // start animation + // return () => window.cancelAnimationFrame(animation.id); + // }, [true]); - if (z > 12) { - setZ(0); - } + // if (z > 12) { + // setZ(0); + // } - console.log(z); + // console.log(z); useEffect(() => { let protocol: any = new Protocol(); @@ -200,25 +200,56 @@ export function App() { } const lays = [ - // @ts-ignore - new DelaunayLayer({ - data: "file_2.json", - id: "c", - getPosition: (d) => d.c, - getValue: (d) => { - return d.t[z]; - }, - colorScale: (x) => { - return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - }, - updateTriggers: { - getValue: { z }, - }, - transitions: { - getValue: "interpolation", - }, + // new PMTLayer({ + // data: "file_2.pmtiles", + // id: "c", + // renderSubLayers: (props) => { + // return ( + // // @ts-ignore + // new DelaunayLayer({ + // ...props, + // id: "c", + // getPosition: (d) => d.c, + // getValue: (d) => { + // return d.t[z]; + // }, + // colorScale: (x) => { + // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + // } + // }) + // ) + // } + // }), + + new MyCompositeLayer({ + id: "f", + data: "file_2.pmtiles", }), + // // @ts-ignore + // new DelaunayLayer({ + // id: "c", + // data: "file_2.pmtiles", + // getPosition: (d) => d.c, + // getValue: (d) => { + // return d.d; + // }, + // colorScale: (x) => { + // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + // } + // }), + + // new MVTLayer({ + // data: `file_2/{z}/{x}/{y}.pbf`, + + // minZoom: 0, + // maxZoom: 23, + // getFillColor: f => { + // console.log(f) + // return [100, 100, 100] + // }, + // }) + // new ScatterplotLayer({ // data: "file_2.json", // id: "c", @@ -258,7 +289,7 @@ export function App() { { setZoom(viewState.zoom); diff --git a/src/delaunay.tsx b/src/delaunay.tsx index f2b2aa4..8515b33 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -1,7 +1,11 @@ import { Model } from "@luma.gl/core"; import { Layer, project32, picking } from "@deck.gl/core"; +import { MVTLayer } from "@deck.gl/geo-layers"; import { Delaunay } from "d3-delaunay"; import GL from "@luma.gl/constants"; +import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; + +import { AttributeManager } from "@deck.gl/core"; const defaultProps = { getPosition: { type: "accessor", value: (d) => d.position }, @@ -51,7 +55,7 @@ void main(void) { } `; -export default class DelaunayLayer extends Layer { +export class DelaunayLayer extends PMTLayer { getShaders() { return super.getShaders({ vs, @@ -60,8 +64,22 @@ export default class DelaunayLayer extends Layer { }); } + protected _getAttributeManager(): AttributeManager | null { + const context = this.context; + return new AttributeManager(context.device, { + id: this.props.id, + stats: context.stats, + timeline: context.timeline, + }); + } + + getAttributeManager(): AttributeManager | null { + return this._getAttributeManager(); + } + initializeState() { const attributeManager = this.getAttributeManager(); + super.initializeState(); attributeManager.remove(["instancePickingColors"]); @@ -107,7 +125,7 @@ export default class DelaunayLayer extends Layer { updateState(params) { super.updateState(params); - const { changeFlags } = params; + const { props, oldProps, context, changeFlags } = params; if (changeFlags.extensionsChanged) { if (this.state.model) { this.state.model.delete(); @@ -121,8 +139,16 @@ export default class DelaunayLayer extends Layer { (changeFlags.updateTriggersChanged && changeFlags.updateTriggersChanged.getValue) ) { + this._updateTileset(); this.setState({ valueRange: this.getValueRange() }); } + + if (this.state?.data) { + super.updateState({ props, oldProps, context, changeFlags }); + this._setWGS84PropertyForTiles(); + } + + console.log(this); } draw({ uniforms }) { @@ -172,6 +198,7 @@ export default class DelaunayLayer extends Layer { } calculateIndices(attribute) { + console.log("1"); const { data, getPosition } = this.props; const points = data.map(getPosition); @@ -189,3 +216,58 @@ export default class DelaunayLayer extends Layer { DelaunayLayer.layerName = "DelaunayLayer"; DelaunayLayer.defaultProps = defaultProps; + +import { DataFilterExtension } from "@deck.gl/extensions"; + +import { PMTilesSource } from "@loaders.gl/pmtiles"; +import { load } from "@loaders.gl/core"; + +const url = "http://localhost:5173/file_2.pmtiles"; +const source = new PMTilesSource({ url }); +const tile = await source.getTile({ layers: "file_2", zoom: 0, x: 0, y: 0 }); +const vtile = await source.getVectorTile({ + layers: "file_2", + zoom: 0, + x: 0, + y: 0, +}); +const h = await source.getMetadata(); +console.log(source); +console.log(tile); +console.log(vtile); +console.log(h); + +import { CompositeLayer } from "deck.gl"; + +export class MyCompositeLayer extends CompositeLayer { + initializeState() {} + + updateState({ changeFlags }) { + const { data } = this.props; + const udata = data; + //console.log(data) + this.setState({ udata }); + } + + renderLayers() { + return [ + new PMTLayer(this.props, this.getSubLayerProps({ id: "geojson" }), { + data: this.props.data, + }), + // @ts-ignore + new DelaunayLayer(this.getSubLayerProps({ id: "text" }), { + data: this.state.labelData, + getPosition: (d) => d.c, + getValue: (d) => { + return d.d; + }, + colorScale: (x) => { + return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + }, + }), + ]; + } +} + +MyCompositeLayer.layerName = "MyCompositeLayer"; +MyCompositeLayer.defaultProps = defaultProps; From 45855d460e61449e949e0e274f433c49c74fa4c9 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:17:16 +0100 Subject: [PATCH 02/12] Working tile render, bounding box points missing --- src/App.tsx | 137 ++++++++++++++++++++++++++++---------- src/delaunay.tsx | 169 +++++++++++++++++++++++++++++------------------ 2 files changed, 207 insertions(+), 99 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2f5c8f5..b762a58 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,11 +7,18 @@ import { Protocol } from "pmtiles"; import { ScatterplotLayer, GeoJsonLayer } from "@deck.gl/layers"; import { interpolateYlOrRd } from "d3-scale-chromatic"; import { color } from "d3-color"; -import { DelaunayLayer, MyCompositeLayer } from "./delaunay.tsx"; +import { MyCompositeLayer } from "./delaunay.tsx"; +import DelaunayLayer from "./delaunay.tsx"; import { useMenuStore } from "./Store"; import { MVTLayer } from "@deck.gl/geo-layers"; import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; +import { PMTilesSource } from "@loaders.gl/pmtiles"; +import { MVTLoader } from "@loaders.gl/mvt"; +import { PMTLoader } from "@mgcth/deck.gl-pmtiles"; + +import { TileLayer } from "@deck.gl/geo-layers"; + const INITIAL_VIEW_STATE = { latitude: 62.5, longitude: 16, @@ -200,45 +207,107 @@ export function App() { } const lays = [ - // new PMTLayer({ - // data: "file_2.pmtiles", - // id: "c", - // renderSubLayers: (props) => { - // return ( - // // @ts-ignore - // new DelaunayLayer({ - // ...props, - // id: "c", - // getPosition: (d) => d.c, - // getValue: (d) => { - // return d.t[z]; - // }, - // colorScale: (x) => { - // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - // } - // }) - // ) - // } - // }), + new TileLayer({ + data: "http://localhost:5173/file_2/{z}/{x}/{y}.pbf", + id: "composite", + maxZoom: 8, + loaders: [MVTLoader], + loadOptions: { worker: false }, + binary: false, + renderSubLayers: (props) => { + // console.log(props.data) + // const udata = [] + // if (props.data) { + // for (let i = 0; i < props.data.points.properties.length; i++) { + // const c = props.data.points.properties[i].c + // .replace("[", "") + // .replace("]", "") + // .split(",").map((x => Number(x))) - new MyCompositeLayer({ - id: "f", - data: "file_2.pmtiles", + // const t = props.data.points.properties[i].t + // .replace("[", "") + // .replace("]", "") + // .split(",").map((x => Number(x))) + + // const time = props.data.points.properties[i].time + // .replace("[", "") + // .replace("]", "") + // .split(",").map((x => Number(x))) + + // udata.push({c: c, t: t, time: time}) + // } + // } + + // console.log(udata) + + const { id } = props; + //console.log(props) + + return ( + // @ts-ignore + new DelaunayLayer({ + ...props, + id: id + "c", + getPosition: (d) => { + let val = d.properties.c + .replace("[", "") + .replace("]", "") + .split(",") + .map((e) => Number(e)); + //console.log(val) + return val; + }, + getValue: (d) => { + let val = d.properties.t + .replace("[", "") + .replace("]", "") + .split(",") + .map((e) => Number(e))[0]; + return val; + }, + colorScale: (x) => { + let val = [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + return val; + }, + }) + // new GeoJsonLayer({ + // ...props, + // id: id + 'geojson-layer', + // pointType: 'circle', + // getFillColor: (e) => { + // //console.log(e) + // return [160, 160, 180, 200] + // }, + // getPointRadius: 100, + // pointRadiusMinPixels: 1, + // pointRadiusMaxPixels: 10, + // }) + ); + }, }), - // // @ts-ignore - // new DelaunayLayer({ - // id: "c", + // new MyCompositeLayer({ + // id: "f", // data: "file_2.pmtiles", - // getPosition: (d) => d.c, - // getValue: (d) => { - // return d.d; - // }, - // colorScale: (x) => { - // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - // } + // loaders: [PMTLoader], + // loadOptions: {worker: false} // }), + // @ts-ignore + new DelaunayLayer({ + id: "c", + data: "file_2.pmtiles", + getPosition: (d) => d.c, + getValue: (d) => { + return d.d; + }, + colorScale: (x) => { + return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + }, + loaders: [PMTLoader], + loadOptions: { worker: false }, + }), + // new MVTLayer({ // data: `file_2/{z}/{x}/{y}.pbf`, diff --git a/src/delaunay.tsx b/src/delaunay.tsx index 8515b33..546dc89 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -5,6 +5,8 @@ import { Delaunay } from "d3-delaunay"; import GL from "@luma.gl/constants"; import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; +import { PMTLoader } from "@mgcth/deck.gl-pmtiles"; + import { AttributeManager } from "@deck.gl/core"; const defaultProps = { @@ -55,7 +57,7 @@ void main(void) { } `; -export class DelaunayLayer extends PMTLayer { +export default class DelaunayLayer extends Layer { getShaders() { return super.getShaders({ vs, @@ -64,22 +66,8 @@ export class DelaunayLayer extends PMTLayer { }); } - protected _getAttributeManager(): AttributeManager | null { - const context = this.context; - return new AttributeManager(context.device, { - id: this.props.id, - stats: context.stats, - timeline: context.timeline, - }); - } - - getAttributeManager(): AttributeManager | null { - return this._getAttributeManager(); - } - initializeState() { const attributeManager = this.getAttributeManager(); - super.initializeState(); attributeManager.remove(["instancePickingColors"]); @@ -125,7 +113,7 @@ export class DelaunayLayer extends PMTLayer { updateState(params) { super.updateState(params); - const { props, oldProps, context, changeFlags } = params; + const { changeFlags } = params; if (changeFlags.extensionsChanged) { if (this.state.model) { this.state.model.delete(); @@ -139,16 +127,8 @@ export class DelaunayLayer extends PMTLayer { (changeFlags.updateTriggersChanged && changeFlags.updateTriggersChanged.getValue) ) { - this._updateTileset(); this.setState({ valueRange: this.getValueRange() }); } - - if (this.state?.data) { - super.updateState({ props, oldProps, context, changeFlags }); - this._setWGS84PropertyForTiles(); - } - - console.log(this); } draw({ uniforms }) { @@ -198,17 +178,12 @@ export class DelaunayLayer extends PMTLayer { } calculateIndices(attribute) { - console.log("1"); const { data, getPosition } = this.props; const points = data.map(getPosition); const delaunay = Delaunay.from(points); const indices = delaunay.triangles; - //console.log(points) - //console.log(delaunay) - //console.log(indices) - this.state.vertexCount = indices.length; attribute.value = new Uint32Array(indices); } @@ -217,57 +192,121 @@ export class DelaunayLayer extends PMTLayer { DelaunayLayer.layerName = "DelaunayLayer"; DelaunayLayer.defaultProps = defaultProps; -import { DataFilterExtension } from "@deck.gl/extensions"; - -import { PMTilesSource } from "@loaders.gl/pmtiles"; -import { load } from "@loaders.gl/core"; - -const url = "http://localhost:5173/file_2.pmtiles"; -const source = new PMTilesSource({ url }); -const tile = await source.getTile({ layers: "file_2", zoom: 0, x: 0, y: 0 }); -const vtile = await source.getVectorTile({ - layers: "file_2", - zoom: 0, - x: 0, - y: 0, -}); -const h = await source.getMetadata(); -console.log(source); -console.log(tile); -console.log(vtile); -console.log(h); +// import { DataFilterExtension } from "@deck.gl/extensions"; + +// import { PMTilesSource } from "@loaders.gl/pmtiles"; +// import { load } from "@loaders.gl/core"; + +// const url = "http://localhost:5173/file_2.pmtiles"; +// const source = new PMTilesSource({ url }); +// const tile = await source.getTile({ layers: "file_2", zoom: 0, x: 0, y: 0 }); +// const vtile = await source.getVectorTile({ +// layers: "file_2", +// zoom: 0, +// x: 0, +// y: 0, +// }); +// const h = await source.getMetadata(); +// console.log(source); +// console.log(tile); +// console.log(vtile); +// console.log(h); import { CompositeLayer } from "deck.gl"; export class MyCompositeLayer extends CompositeLayer { - initializeState() {} - updateState({ changeFlags }) { + console.log(this); const { data } = this.props; - const udata = data; - //console.log(data) - this.setState({ udata }); + console.log(data); + if (changeFlags.dataChanged && data) { + //console.log(data) + const udata = data; + + //this.setState({udata}); + } } renderLayers() { + //console.log(this.props) return [ new PMTLayer(this.props, this.getSubLayerProps({ id: "geojson" }), { data: this.props.data, }), - // @ts-ignore - new DelaunayLayer(this.getSubLayerProps({ id: "text" }), { - data: this.state.labelData, - getPosition: (d) => d.c, - getValue: (d) => { - return d.d; - }, - colorScale: (x) => { - return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - }, - }), + // // @ts-ignore + // new DelaunayLayer(this.getSubLayerProps({ id: "text" }), { + // data: this.state.labelData, + // getPosition: (d) => d.c, + // getValue: (d) => { + // return d.d; + // }, + // colorScale: (x) => { + // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + // }, + // }), ]; } } +import { type TileLayerProps } from "@deck.gl/geo-layers/typed"; +import { GeoJsonLayer, type GeoJsonLayerProps } from "@deck.gl/layers/typed"; +import { type DefaultProps } from "@deck.gl/core/typed"; +import type { Loader } from "@loaders.gl/loader-utils"; + +export type TileJson = { + tilejson: string; + tiles: string[]; + // eslint-disable-next-line camelcase + vector_layers: any[]; + attribution?: string; + scheme?: string; + maxzoom?: number; + minzoom?: number; + version?: string; +}; +export type _MVTLayerProps = { + /** Called if `data` is a TileJSON URL when it is successfully fetched. */ + onDataLoad?: ((tilejson: TileJson | null) => void) | null; + + /** Needed for highlighting a feature split across two or more tiles. */ + uniqueIdProperty?: string; + + /** A feature with ID corresponding to the supplied value will be highlighted. */ + highlightedFeatureId?: string | null; + + /** + * Use tile data in binary format. + * + * @default true + */ + binary?: boolean; + + /** + * Loaders used to transform tiles into `data` property passed to `renderSubLayers`. + * + * @default [MVTWorkerLoader] from `@loaders.gl/mvt` + */ + loaders?: Loader[]; +}; + +export type ParsedPmTile = Feature[] | BinaryFeatures; +export type ExtraProps = { + raster?: boolean; +}; + +export type _PMTLayerProps = _MVTLayerProps & ExtraProps; +export type PmtLayerProps = _PMTLayerProps & TileLayerProps; + +const defaultPropsMy: DefaultProps = { + ...GeoJsonLayer.defaultProps, + onDataLoad: { type: "function", value: null, optional: true, compare: false }, + uniqueIdProperty: "", + highlightedFeatureId: null, + binary: true, + raster: false, + loaders: [PMTLoader], + loadOptions: { worker: false }, +}; + MyCompositeLayer.layerName = "MyCompositeLayer"; -MyCompositeLayer.defaultProps = defaultProps; +MyCompositeLayer.defaultProps = defaultPropsMy; From 7639651705dfc2090b0c025bc0358811147a92cf Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sat, 17 Feb 2024 10:02:26 +0100 Subject: [PATCH 03/12] Tile rendering working by fetching all tiles before rendering --- src/App.tsx | 320 ++++++++++++++++++++++++----------------------- src/Header.tsx | 8 +- src/config.tsx | 8 ++ src/delaunay.tsx | 125 ------------------ 4 files changed, 179 insertions(+), 282 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b762a58..ffa41a4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { Protocol } from "pmtiles"; import { ScatterplotLayer, GeoJsonLayer } from "@deck.gl/layers"; import { interpolateYlOrRd } from "d3-scale-chromatic"; import { color } from "d3-color"; +import { scaleQuantize } from "d3-scale"; import { MyCompositeLayer } from "./delaunay.tsx"; import DelaunayLayer from "./delaunay.tsx"; import { useMenuStore } from "./Store"; @@ -14,10 +15,35 @@ import { MVTLayer } from "@deck.gl/geo-layers"; import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; import { PMTilesSource } from "@loaders.gl/pmtiles"; -import { MVTLoader } from "@loaders.gl/mvt"; -import { PMTLoader } from "@mgcth/deck.gl-pmtiles"; -import { TileLayer } from "@deck.gl/geo-layers"; +function lerp(a: number, b: number, alpha: number) { + return a + alpha * (b - a); +} + +function cartesianToWGS84(lngLat, boundingBox) { + const [minX, maxY] = boundingBox[0]; + const [maxX, minY] = boundingBox[1]; + + const [x, y] = lngLat; + const x0 = lerp(minX, maxX, x); + const y0 = lerp(minY, maxY, y); + return [x0, y0]; +} + +function lon2tile(lon, zoom) { + return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom)); +} +function lat2tile(lat, zoom) { + return Math.floor( + ((1 - + Math.log( + Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180), + ) / + Math.PI) / + 2) * + Math.pow(2, zoom), + ); +} const INITIAL_VIEW_STATE = { latitude: 62.5, @@ -28,6 +54,7 @@ const INITIAL_VIEW_STATE = { }; export function App() { + const [tmp, setTmp] = useState([]); const zoom = useMenuStore((state: any) => state.zoom); const setZoom = useMenuStore((state: any) => state.setZoom); const searchView = useMenuStore((state: any) => { @@ -37,6 +64,7 @@ export function App() { return INITIAL_VIEW_STATE; } }); + const [viewState, setViewState] = useState(searchView); // const step = 1; // const loopLength = 2500; @@ -77,6 +105,43 @@ export function App() { }; }, []); + useEffect(() => { + async function getMetadata(source) { + return await source.getMetadata(); + } + async function tmp() { + const source = new PMTilesSource({ + url: "http://localhost:5173/file_3.pmtiles", + }); + const metadata = await getMetadata(source); + console.log(metadata); + + const { boundingBox } = metadata; + + const minY = lon2tile(boundingBox[0][0], zoom); + const maxY = lon2tile(boundingBox[0][1], zoom); + const minX = lat2tile(boundingBox[1][0], zoom); + const maxX = lon2tile(boundingBox[1][1], zoom); + + console.log("z", zoom); + console.log("minY", minY); + console.log("maxY", maxY); + console.log("minX", minX); + console.log("maxX", maxX); + + for (let i = minY; i <= maxY; i++) { + for (let j = minX; j <= maxX; j++) { + let tile = await source.getTile({ x: j, y: i, zoom: zoom }); + console.log(i, j, tile); + } + } + + return 1; + } + + tmp(); + }, []); + const layers = useMenuStore((state: any) => { let layers: any = []; @@ -201,167 +266,110 @@ export function App() { return layers; }); - function hexToRGB(hex) { - const c = color(hex); - return [c.r, c.g, c.b]; - } - - const lays = [ - new TileLayer({ - data: "http://localhost:5173/file_2/{z}/{x}/{y}.pbf", - id: "composite", - maxZoom: 8, - loaders: [MVTLoader], - loadOptions: { worker: false }, - binary: false, - renderSubLayers: (props) => { - // console.log(props.data) - // const udata = [] - // if (props.data) { - // for (let i = 0; i < props.data.points.properties.length; i++) { - // const c = props.data.points.properties[i].c - // .replace("[", "") - // .replace("]", "") - // .split(",").map((x => Number(x))) - - // const t = props.data.points.properties[i].t - // .replace("[", "") - // .replace("]", "") - // .split(",").map((x => Number(x))) - - // const time = props.data.points.properties[i].time - // .replace("[", "") - // .replace("]", "") - // .split(",").map((x => Number(x))) - - // udata.push({c: c, t: t, time: time}) - // } - // } - - // console.log(udata) - - const { id } = props; - //console.log(props) - - return ( - // @ts-ignore - new DelaunayLayer({ - ...props, - id: id + "c", - getPosition: (d) => { - let val = d.properties.c - .replace("[", "") - .replace("]", "") - .split(",") - .map((e) => Number(e)); - //console.log(val) - return val; - }, - getValue: (d) => { - let val = d.properties.t - .replace("[", "") - .replace("]", "") - .split(",") - .map((e) => Number(e))[0]; - return val; - }, - colorScale: (x) => { - let val = [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - return val; - }, - }) - // new GeoJsonLayer({ - // ...props, - // id: id + 'geojson-layer', - // pointType: 'circle', - // getFillColor: (e) => { - // //console.log(e) - // return [160, 160, 180, 200] - // }, - // getPointRadius: 100, - // pointRadiusMinPixels: 1, - // pointRadiusMaxPixels: 10, - // }) - ); - }, - }), - - // new MyCompositeLayer({ - // id: "f", - // data: "file_2.pmtiles", - // loaders: [PMTLoader], - // loadOptions: {worker: false} - // }), - - // @ts-ignore - new DelaunayLayer({ - id: "c", - data: "file_2.pmtiles", - getPosition: (d) => d.c, - getValue: (d) => { - return d.d; - }, - colorScale: (x) => { - return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - }, - loaders: [PMTLoader], - loadOptions: { worker: false }, - }), - - // new MVTLayer({ - // data: `file_2/{z}/{x}/{y}.pbf`, - - // minZoom: 0, - // maxZoom: 23, - // getFillColor: f => { - // console.log(f) - // return [100, 100, 100] - // }, - // }) - - // new ScatterplotLayer({ - // data: "file_2.json", - // id: "c", - // radiusMinPixels: 100, - // radiusMaxPixels: 100, - // getFillColor: d => { - // console.log(d.t[0]) - // return [d.t[0]+100, d.t[0]+100, d.t[0]+100] - // }, - // // // props added by DataFilterExtension - // // getFilterValue: f => { - // // return f.time - // // }, // in seconds - // // filterRange: [0, 1], // 12:00 - 13:00 - - // // // Define extensions - // // extensions: [new DataFilterExtension({filterSize: 1})] - // }), - - // new ScatterplotLayer({ - // id: 'scatterplot-layer', - // data: 'file.json', - // pickable: true, - // opacity: 0.6, - // filled: true, - // radiusScale: 6, - // radiusMinPixels: 1, - // radiusMaxPixels: 100, - // lineWidthMinPixels: 1, - // getPosition: d => d.c, - // getRadius: d => Math.sqrt(d.exits), - // getFillColor: d => [d.d*100, d.d*100, d.d*100], - // }) - ]; + const layers2 = useMenuStore((state: any) => { + const layers = []; + layers.push( + new MVTLayer({ + id: "geojson-layer", + data: "http://localhost:5173/file_3/{z}/{x}/{y}.pbf", + onViewportLoad: (e) => { + //console.log("onviewload", e) + + const aa = []; + for (let i = 0; i < e.length; i++) { + const x = e[i]; + const boundingBox = x.boundingBox; + + function lerp(a: number, b: number, alpha: number) { + return a + alpha * (b - a); + } + + function cartesianToWGS84(lngLat) { + const [minX, maxY] = boundingBox[0]; + const [maxX, minY] = boundingBox[1]; + + const [x, y] = lngLat; + const x0 = lerp(minX, maxX, x); + const y0 = lerp(minY, maxY, y); + return [x0, y0]; + } + + if (x.content != null) { + let t = x.content.points.properties.map( + (y) => JSON.parse(y.t)[8], + ); + const newArr = []; + const arr = Array.from(x.content.points.positions.value); + while (arr.length) newArr.push(arr.splice(0, 2)); + let c = newArr.map((y) => cartesianToWGS84(y)); + + aa.push({ t: t, c: c }); + } + } + + const bb = []; + for (let i = 0; i < aa.length; i++) { + for (let j = 0; j < aa[i].t.length; j++) { + bb.push({ t: aa[i].t[j], c: aa[i].c[j] }); + } + } + + setTmp(bb); + }, + maxZoom: 8, + }), + ); + + function hexToRGB(hex) { + const c = color(hex); + return [c.r, c.g, c.b]; + } + + layers.push( + // @ts-ignore + new DelaunayLayer({ + id: "c", + data: tmp, + getPosition: (d) => d.c, + getValue: (d) => { + return d.t; + }, + colorScale: (x) => { + let col = interpolateYlOrRd((x + 30) / 50); + let colorScale = scaleQuantize() + .domain(col) + .range(["red", "blue", "green"]); + console.log(colorScale); + + return [ + ...hexToRGB(col), + state.layer["Temperatur"].checked === true ? 200 : 0, + ]; + }, + updateTriggers: { + colorScale: [state.layer["Temperatur"].checked], + }, + }), + ); + + return layers; + }); return ( { setZoom(viewState.zoom); + + //console.log("viewstate", viewState) + const viewport = viewState; + //console.log("viewport", viewport) + // const visibleData = this.getVisibleData(viewport); // Implement getVisibleData function + // this.setState({ visibleData }); + return { ...viewState, }; diff --git a/src/Header.tsx b/src/Header.tsx index 5a8d941..0a3929a 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -65,6 +65,12 @@ const communicationList = Object.entries(mapElements) return ; }); +const temperature = Object.entries(mapElements) + .filter(([_, value]) => value["type"] == "data") + .map(([key, _]) => { + return ; + }); + // @ts-ignore function LayerLabel({ _, label }: any): any { const layer = useMenuStore((state: any) => state.layer); @@ -232,7 +238,7 @@ export function Header() { - {} + {temperature} diff --git a/src/config.tsx b/src/config.tsx index f9971f2..bd3e90c 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -424,4 +424,12 @@ export const mapElements: Dictionary = { checked: true, stroke: 0, }, + Temperatur: { + name: "01_temperatur", + color: colors["other-building"], + dark_color: colors["dark-other-building"], + type: "data", + checked: true, + stroke: 0, + }, }; diff --git a/src/delaunay.tsx b/src/delaunay.tsx index 546dc89..25a9a52 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -1,13 +1,7 @@ import { Model } from "@luma.gl/core"; import { Layer, project32, picking } from "@deck.gl/core"; -import { MVTLayer } from "@deck.gl/geo-layers"; import { Delaunay } from "d3-delaunay"; import GL from "@luma.gl/constants"; -import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; - -import { PMTLoader } from "@mgcth/deck.gl-pmtiles"; - -import { AttributeManager } from "@deck.gl/core"; const defaultProps = { getPosition: { type: "accessor", value: (d) => d.position }, @@ -191,122 +185,3 @@ export default class DelaunayLayer extends Layer { DelaunayLayer.layerName = "DelaunayLayer"; DelaunayLayer.defaultProps = defaultProps; - -// import { DataFilterExtension } from "@deck.gl/extensions"; - -// import { PMTilesSource } from "@loaders.gl/pmtiles"; -// import { load } from "@loaders.gl/core"; - -// const url = "http://localhost:5173/file_2.pmtiles"; -// const source = new PMTilesSource({ url }); -// const tile = await source.getTile({ layers: "file_2", zoom: 0, x: 0, y: 0 }); -// const vtile = await source.getVectorTile({ -// layers: "file_2", -// zoom: 0, -// x: 0, -// y: 0, -// }); -// const h = await source.getMetadata(); -// console.log(source); -// console.log(tile); -// console.log(vtile); -// console.log(h); - -import { CompositeLayer } from "deck.gl"; - -export class MyCompositeLayer extends CompositeLayer { - updateState({ changeFlags }) { - console.log(this); - const { data } = this.props; - console.log(data); - if (changeFlags.dataChanged && data) { - //console.log(data) - const udata = data; - - //this.setState({udata}); - } - } - - renderLayers() { - //console.log(this.props) - return [ - new PMTLayer(this.props, this.getSubLayerProps({ id: "geojson" }), { - data: this.props.data, - }), - // // @ts-ignore - // new DelaunayLayer(this.getSubLayerProps({ id: "text" }), { - // data: this.state.labelData, - // getPosition: (d) => d.c, - // getValue: (d) => { - // return d.d; - // }, - // colorScale: (x) => { - // return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; - // }, - // }), - ]; - } -} - -import { type TileLayerProps } from "@deck.gl/geo-layers/typed"; -import { GeoJsonLayer, type GeoJsonLayerProps } from "@deck.gl/layers/typed"; -import { type DefaultProps } from "@deck.gl/core/typed"; -import type { Loader } from "@loaders.gl/loader-utils"; - -export type TileJson = { - tilejson: string; - tiles: string[]; - // eslint-disable-next-line camelcase - vector_layers: any[]; - attribution?: string; - scheme?: string; - maxzoom?: number; - minzoom?: number; - version?: string; -}; -export type _MVTLayerProps = { - /** Called if `data` is a TileJSON URL when it is successfully fetched. */ - onDataLoad?: ((tilejson: TileJson | null) => void) | null; - - /** Needed for highlighting a feature split across two or more tiles. */ - uniqueIdProperty?: string; - - /** A feature with ID corresponding to the supplied value will be highlighted. */ - highlightedFeatureId?: string | null; - - /** - * Use tile data in binary format. - * - * @default true - */ - binary?: boolean; - - /** - * Loaders used to transform tiles into `data` property passed to `renderSubLayers`. - * - * @default [MVTWorkerLoader] from `@loaders.gl/mvt` - */ - loaders?: Loader[]; -}; - -export type ParsedPmTile = Feature[] | BinaryFeatures; -export type ExtraProps = { - raster?: boolean; -}; - -export type _PMTLayerProps = _MVTLayerProps & ExtraProps; -export type PmtLayerProps = _PMTLayerProps & TileLayerProps; - -const defaultPropsMy: DefaultProps = { - ...GeoJsonLayer.defaultProps, - onDataLoad: { type: "function", value: null, optional: true, compare: false }, - uniqueIdProperty: "", - highlightedFeatureId: null, - binary: true, - raster: false, - loaders: [PMTLoader], - loadOptions: { worker: false }, -}; - -MyCompositeLayer.layerName = "MyCompositeLayer"; -MyCompositeLayer.defaultProps = defaultPropsMy; From d22f51eaeb32109968ecf68af71a5b57ddf45a3b Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:06:23 +0100 Subject: [PATCH 04/12] Initial continous scalar data layer rendering from tiles --- .gitignore | 1 + src/App.tsx | 103 +++++++++++++---------- src/delaunay.tsx | 213 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 49103d9..89e6598 100644 --- a/.gitignore +++ b/.gitignore @@ -189,3 +189,4 @@ dist-ssr *.pmtiles *.geojson *.gpkg +*.pbf diff --git a/src/App.tsx b/src/App.tsx index ffa41a4..9d59a97 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,11 +8,12 @@ import { ScatterplotLayer, GeoJsonLayer } from "@deck.gl/layers"; import { interpolateYlOrRd } from "d3-scale-chromatic"; import { color } from "d3-color"; import { scaleQuantize } from "d3-scale"; -import { MyCompositeLayer } from "./delaunay.tsx"; +import { MyLayer } from "./delaunay.tsx"; import DelaunayLayer from "./delaunay.tsx"; import { useMenuStore } from "./Store"; import { MVTLayer } from "@deck.gl/geo-layers"; import { PMTLayer } from "@mgcth/deck.gl-pmtiles"; +import { MVTLoader } from "@loaders.gl/mvt"; import { PMTilesSource } from "@loaders.gl/pmtiles"; @@ -109,38 +110,42 @@ export function App() { async function getMetadata(source) { return await source.getMetadata(); } + async function tmp() { const source = new PMTilesSource({ url: "http://localhost:5173/file_3.pmtiles", }); const metadata = await getMetadata(source); - console.log(metadata); + //console.log(metadata); const { boundingBox } = metadata; - const minY = lon2tile(boundingBox[0][0], zoom); - const maxY = lon2tile(boundingBox[0][1], zoom); - const minX = lat2tile(boundingBox[1][0], zoom); - const maxX = lon2tile(boundingBox[1][1], zoom); - - console.log("z", zoom); - console.log("minY", minY); - console.log("maxY", maxY); - console.log("minX", minX); - console.log("maxX", maxX); - - for (let i = minY; i <= maxY; i++) { - for (let j = minX; j <= maxX; j++) { - let tile = await source.getTile({ x: j, y: i, zoom: zoom }); - console.log(i, j, tile); - } - } + const minX = lon2tile(boundingBox[0][0], zoom); + const maxX = lon2tile(boundingBox[1][0], zoom); + const maxY = lat2tile(boundingBox[0][1], zoom); + const minY = lat2tile(boundingBox[1][1], zoom); + + // console.log("boundingBox", boundingBox) + // console.log("z", zoom); + // console.log("minY", minY); + // console.log("maxY", maxY); + // console.log("minX", minX); + // console.log("maxX", maxX); + + // console.time(String(zoom)) + // for (let i = minY; i <= maxY; i++) { + // for (let j = minX; j <= maxX; j++) { + // let tile = await source.getVectorTile({ x: j, y: i, zoom: zoom }); + // //console.log(zoom, i, j, tile); + // } + // } + // console.timeEnd(String(zoom)) return 1; } tmp(); - }, []); + }, [zoom]); const layers = useMenuStore((state: any) => { let layers: any = []; @@ -325,30 +330,39 @@ export function App() { return [c.r, c.g, c.b]; } + // layers.push( + // // @ts-ignore + // new DelaunayLayer({ + // id: "c", + // data: tmp, + // getPosition: (d) => d.c, + // getValue: (d) => { + // return d.t; + // }, + // colorScale: (x) => { + // let col = interpolateYlOrRd((x + 30) / 50); + // let colorScale = scaleQuantize() + // .domain(col) + // .range(["red", "blue", "green"]); + // //console.log(colorScale); + + // return [ + // ...hexToRGB(col), + // state.layer["Temperatur"].checked === true ? 200 : 0, + // ]; + // }, + // updateTriggers: { + // colorScale: [state.layer["Temperatur"].checked], + // }, + // }), + // ); + layers.push( // @ts-ignore - new DelaunayLayer({ - id: "c", - data: tmp, - getPosition: (d) => d.c, - getValue: (d) => { - return d.t; - }, - colorScale: (x) => { - let col = interpolateYlOrRd((x + 30) / 50); - let colorScale = scaleQuantize() - .domain(col) - .range(["red", "blue", "green"]); - console.log(colorScale); - - return [ - ...hexToRGB(col), - state.layer["Temperatur"].checked === true ? 200 : 0, - ]; - }, - updateTriggers: { - colorScale: [state.layer["Temperatur"].checked], - }, + new MyLayer({ + id: "test", + data: "http://localhost:5173/file_3/{z}/{x}/{y}.pbf", + loaders: [MVTLoader], }), ); @@ -363,6 +377,11 @@ export function App() { ContextProvider={MapProvider} onViewStateChange={({ viewState }) => { setZoom(viewState.zoom); + //console.log(viewState) + + // const {width, height } = viewState; + + // view.makeViewport({width, height, viewState}) //console.log("viewstate", viewState) const viewport = viewState; diff --git a/src/delaunay.tsx b/src/delaunay.tsx index 25a9a52..eb624c6 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -105,6 +105,7 @@ export default class DelaunayLayer extends Layer { } updateState(params) { + //console.log("viewport", this.context.viewport) super.updateState(params); const { changeFlags } = params; @@ -185,3 +186,215 @@ export default class DelaunayLayer extends Layer { DelaunayLayer.layerName = "DelaunayLayer"; DelaunayLayer.defaultProps = defaultProps; + +import { CompositeLayer } from "deck.gl"; + +import { MVTLayer, TileLayer } from "@deck.gl/geo-layers"; +import { MVTLoader } from "@loaders.gl/mvt"; + +import { ScatterplotLayer } from "@deck.gl/layers"; + +import { PMTilesSource } from "@loaders.gl/pmtiles"; + +import { interpolateYlOrRd } from "d3-scale-chromatic"; +import { color } from "d3-color"; +import { scaleQuantize } from "d3-scale"; + +function hexToRGB(hex) { + const c = color(hex); + return [c.r, c.g, c.b]; +} + +function lon2tile(lon, zoom) { + return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom)); +} +function lat2tile(lat, zoom) { + return Math.floor( + ((1 - + Math.log( + Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180), + ) / + Math.PI) / + 2) * + Math.pow(2, zoom), + ); +} + +function getTilesInViewport(viewport, zoom) { + const { width, height, latitude, longitude, zoom: currentZoom } = viewport; + const tiles = []; + + const tileSize = 256; // Standard tile size for most map tile services + const worldSize = Math.pow(2, zoom) * tileSize; + + const topLeft = viewport.unproject([0, 0]); + const bottomRight = viewport.unproject([width, height]); + + const tileX1 = + Math.floor((topLeft[0] + worldSize) / tileSize) % Math.pow(2, zoom); + const tileY1 = + Math.floor((topLeft[1] + worldSize) / tileSize) % Math.pow(2, zoom); + const tileX2 = + Math.floor((bottomRight[0] + worldSize) / tileSize) % Math.pow(2, zoom); + const tileY2 = + Math.floor((bottomRight[1] + worldSize) / tileSize) % Math.pow(2, zoom); + + console.log("tileX1", tileX1); + console.log("tileY1", tileY1); + console.log("tileX2", tileX2); + console.log("tileY2", tileY2); + + for (let x = tileX1; x <= tileX2; x++) { + for (let y = tileY1; y <= tileY2; y++) { + tiles.push({ x, y, zoom }); + } + } + + return tiles; +} + +async function getMetadata(source) { + return await source.getMetadata(); +} + +function getTileCoordinates(boundingBox, zoom) { + const minX = lon2tile(boundingBox[0], zoom); + const maxX = lon2tile(boundingBox[2], zoom); + const maxY = lat2tile(boundingBox[1], zoom); + const minY = lat2tile(boundingBox[3], zoom); + + return { minX, maxX, minY, maxY, zoom }; +} + +async function getAllTiles(source, viewTiles) { + let data = []; + const tiles = {}; + for (let i = viewTiles.minY - 1; i <= viewTiles.maxY + 1; i++) { + for (let j = viewTiles.minX - 1; j <= viewTiles.maxX + 1; j++) { + let tile = await source.getVectorTile({ + x: j, + y: i, + zoom: Math.floor(viewTiles.zoom), + }); + + if (tile != null) { + tile = tile.features.map((x) => { + return { c: x.geometry.coordinates, t: x.properties.t }; + }); + + data = data.concat(tile); + } + } + } + + //console.log("data", zoom, data); + + return data; +} + +function reloadData(viewTiles, oldViewTiles) { + const { + minX: minXOld, + minY: minYOld, + maxX: maxXOld, + maxY: maxYOld, + zoom: zoomOld, + } = oldViewTiles; + const { minX, minY, maxX, maxY, zoom } = viewTiles; + + const reload = { + tiles: false, + zoom: false, + }; + + if ( + minX != minXOld || + minY != minYOld || + maxX != maxXOld || + maxY != maxYOld + ) { + reload.tiles = true; + } + + if (Math.abs(zoom - (zoomOld - (zoomOld % Math.floor(zoomOld)))) >= 1) { + reload.zoom = true; + } + + return reload; +} + +export class MyLayer extends CompositeLayer { + initializeState() { + const source = new PMTilesSource({ + url: "http://localhost:5173/file_3.pmtiles", + }); + + const viewTiles = getTileCoordinates( + this.context.viewport.getBounds(), + this.context.viewport.zoom, + ); + viewTiles.zoom = viewTiles.zoom > 10 ? 10 : viewTiles.zoom; + + const ldata = getAllTiles(source, viewTiles); + this.setState({ + source: source, + storage: {}, + viewTiles, + ldata, + }); + } + + updateState() { + const viewTiles = getTileCoordinates( + this.context.viewport.getBounds(), + Math.floor(this.context.viewport.zoom), + ); + + const reload = reloadData(viewTiles, this.state.viewTiles); + if (reload.tiles == true || reload.zoom == true) { + viewTiles.zoom = viewTiles.zoom > 10 ? 10 : viewTiles.zoom; + const ldata = getAllTiles(this.state.source, viewTiles); + this.setState({ + storage: {}, + viewTiles, + ldata, + }); + } else { + this.setState({ + storage: {}, + }); + } + } + + renderLayers() { + //console.log("this", this) + return [ + // @ts-ignore + new DelaunayLayer({ + id: "c", + data: this.state.ldata, + getPosition: (d) => { + //console.log(d.c) + return d.c; + }, + getValue: (d) => { + const t = d.t + .replace("[", "") + .replace("]", "") + .split(",") + .map((x) => Number(x)); + return t[0]; + }, + colorScale: (x) => { + let col = interpolateYlOrRd((x + 30) / 50); + return [...hexToRGB(col), 200]; + }, + // updateTriggers: { + // colorScale: [state.layer["Temperatur"].checked], + // }, + }), + ]; + } +} + +MyLayer.layerName = "MyLayer"; From 698da2602af18773392e71adc4252732dfef04e8 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:17:30 +0100 Subject: [PATCH 05/12] Quantize colours and clean files --- src/App.tsx | 124 ----------------------------------------------- src/delaunay.tsx | 14 ++++-- 2 files changed, 11 insertions(+), 127 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9d59a97..2bc8eb5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -106,47 +106,6 @@ export function App() { }; }, []); - useEffect(() => { - async function getMetadata(source) { - return await source.getMetadata(); - } - - async function tmp() { - const source = new PMTilesSource({ - url: "http://localhost:5173/file_3.pmtiles", - }); - const metadata = await getMetadata(source); - //console.log(metadata); - - const { boundingBox } = metadata; - - const minX = lon2tile(boundingBox[0][0], zoom); - const maxX = lon2tile(boundingBox[1][0], zoom); - const maxY = lat2tile(boundingBox[0][1], zoom); - const minY = lat2tile(boundingBox[1][1], zoom); - - // console.log("boundingBox", boundingBox) - // console.log("z", zoom); - // console.log("minY", minY); - // console.log("maxY", maxY); - // console.log("minX", minX); - // console.log("maxX", maxX); - - // console.time(String(zoom)) - // for (let i = minY; i <= maxY; i++) { - // for (let j = minX; j <= maxX; j++) { - // let tile = await source.getVectorTile({ x: j, y: i, zoom: zoom }); - // //console.log(zoom, i, j, tile); - // } - // } - // console.timeEnd(String(zoom)) - - return 1; - } - - tmp(); - }, [zoom]); - const layers = useMenuStore((state: any) => { let layers: any = []; @@ -273,89 +232,6 @@ export function App() { const layers2 = useMenuStore((state: any) => { const layers = []; - layers.push( - new MVTLayer({ - id: "geojson-layer", - data: "http://localhost:5173/file_3/{z}/{x}/{y}.pbf", - onViewportLoad: (e) => { - //console.log("onviewload", e) - - const aa = []; - for (let i = 0; i < e.length; i++) { - const x = e[i]; - const boundingBox = x.boundingBox; - - function lerp(a: number, b: number, alpha: number) { - return a + alpha * (b - a); - } - - function cartesianToWGS84(lngLat) { - const [minX, maxY] = boundingBox[0]; - const [maxX, minY] = boundingBox[1]; - - const [x, y] = lngLat; - const x0 = lerp(minX, maxX, x); - const y0 = lerp(minY, maxY, y); - return [x0, y0]; - } - - if (x.content != null) { - let t = x.content.points.properties.map( - (y) => JSON.parse(y.t)[8], - ); - const newArr = []; - const arr = Array.from(x.content.points.positions.value); - while (arr.length) newArr.push(arr.splice(0, 2)); - let c = newArr.map((y) => cartesianToWGS84(y)); - - aa.push({ t: t, c: c }); - } - } - - const bb = []; - for (let i = 0; i < aa.length; i++) { - for (let j = 0; j < aa[i].t.length; j++) { - bb.push({ t: aa[i].t[j], c: aa[i].c[j] }); - } - } - - setTmp(bb); - }, - maxZoom: 8, - }), - ); - - function hexToRGB(hex) { - const c = color(hex); - return [c.r, c.g, c.b]; - } - - // layers.push( - // // @ts-ignore - // new DelaunayLayer({ - // id: "c", - // data: tmp, - // getPosition: (d) => d.c, - // getValue: (d) => { - // return d.t; - // }, - // colorScale: (x) => { - // let col = interpolateYlOrRd((x + 30) / 50); - // let colorScale = scaleQuantize() - // .domain(col) - // .range(["red", "blue", "green"]); - // //console.log(colorScale); - - // return [ - // ...hexToRGB(col), - // state.layer["Temperatur"].checked === true ? 200 : 0, - // ]; - // }, - // updateTriggers: { - // colorScale: [state.layer["Temperatur"].checked], - // }, - // }), - // ); layers.push( // @ts-ignore diff --git a/src/delaunay.tsx b/src/delaunay.tsx index eb624c6..5b2d313 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -326,7 +326,7 @@ function reloadData(viewTiles, oldViewTiles) { export class MyLayer extends CompositeLayer { initializeState() { const source = new PMTilesSource({ - url: "http://localhost:5173/file_3.pmtiles", + url: "http://localhost:5173/mesan.pmtiles", }); const viewTiles = getTileCoordinates( @@ -386,8 +386,16 @@ export class MyLayer extends CompositeLayer { return t[0]; }, colorScale: (x) => { - let col = interpolateYlOrRd((x + 30) / 50); - return [...hexToRGB(col), 200]; + const q = scaleQuantize( + [-50, 50], + [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], + ); + //console.log(q(x)) + //let col = interpolateYlOrRd((x + 30) / 50); + //console.log("x", x) + //console.log("q(x)", q(x)) + //console.log("rgb", hexToRGB(interpolateYlOrRd(q(x)))) + return [...hexToRGB(interpolateYlOrRd(q(x))), 200]; }, // updateTriggers: { // colorScale: [state.layer["Temperatur"].checked], From e7fe1f721596c96b7584df5eafe450b6328545a5 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:35:59 +0100 Subject: [PATCH 06/12] Add update triggers (alpha only for now) --- src/App.tsx | 4 ++++ src/delaunay.tsx | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2bc8eb5..4b6fb24 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -239,6 +239,10 @@ export function App() { id: "test", data: "http://localhost:5173/file_3/{z}/{x}/{y}.pbf", loaders: [MVTLoader], + alpha: state.layer["Temperatur"].checked == true ? 200 : 0, + // updateTriggers: { + // alpha: state.layer["Temperatur"].checked + // } }), ); diff --git a/src/delaunay.tsx b/src/delaunay.tsx index 5b2d313..8321085 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -387,7 +387,7 @@ export class MyLayer extends CompositeLayer { }, colorScale: (x) => { const q = scaleQuantize( - [-50, 50], + [-10, 20], [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], ); //console.log(q(x)) @@ -395,11 +395,12 @@ export class MyLayer extends CompositeLayer { //console.log("x", x) //console.log("q(x)", q(x)) //console.log("rgb", hexToRGB(interpolateYlOrRd(q(x)))) - return [...hexToRGB(interpolateYlOrRd(q(x))), 200]; + return [...hexToRGB(interpolateYlOrRd(q(x))), this.props.alpha]; + //return [...hexToRGB(interpolateYlOrRd((x + 30) / 50)), 200]; + }, + updateTriggers: { + colorScale: [this.props.alpha], }, - // updateTriggers: { - // colorScale: [state.layer["Temperatur"].checked], - // }, }), ]; } From 853ecfd7ef4b6faae22424e322443047d24e5dac Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:03:44 +0100 Subject: [PATCH 07/12] Add very rudimentary cache of tiles --- src/delaunay.tsx | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/delaunay.tsx b/src/delaunay.tsx index 8321085..b3cd657 100644 --- a/src/delaunay.tsx +++ b/src/delaunay.tsx @@ -266,16 +266,21 @@ function getTileCoordinates(boundingBox, zoom) { return { minX, maxX, minY, maxY, zoom }; } -async function getAllTiles(source, viewTiles) { +async function getAllTiles(source, tiles, viewTiles) { + const zoom = viewTiles.zoom; + let data = []; - const tiles = {}; for (let i = viewTiles.minY - 1; i <= viewTiles.maxY + 1; i++) { for (let j = viewTiles.minX - 1; j <= viewTiles.maxX + 1; j++) { - let tile = await source.getVectorTile({ - x: j, - y: i, - zoom: Math.floor(viewTiles.zoom), - }); + if (!tiles.hasOwnProperty(`${j}${i}${zoom}`)) { + tiles[`${j}${i}${zoom}`] = await source.getVectorTile({ + x: j, + y: i, + zoom: Math.floor(zoom), + }); + } + + let tile = tiles[`${j}${i}${zoom}`]; if (tile != null) { tile = tile.features.map((x) => { @@ -292,6 +297,23 @@ async function getAllTiles(source, viewTiles) { return data; } +function clearTiles(tiles, viewTiles) { + const keepKeys = []; + for (let i = viewTiles.minY - 2; i <= viewTiles.maxY + 2; i++) { + for (let j = viewTiles.minX - 2; j <= viewTiles.maxX + 2; j++) { + keepKeys.push(`${j}${i}${viewTiles.zoom}`); + } + } + + for (var key in tiles) { + if (tiles.hasOwnProperty(key)) { + if (!keepKeys.includes(key)) { + delete tiles[key]; + } + } + } +} + function reloadData(viewTiles, oldViewTiles) { const { minX: minXOld, @@ -334,13 +356,16 @@ export class MyLayer extends CompositeLayer { this.context.viewport.zoom, ); viewTiles.zoom = viewTiles.zoom > 10 ? 10 : viewTiles.zoom; + const tiles = {}; + + const ldata = getAllTiles(source, tiles, viewTiles); - const ldata = getAllTiles(source, viewTiles); this.setState({ source: source, storage: {}, viewTiles, ldata, + tiles, }); } @@ -353,11 +378,15 @@ export class MyLayer extends CompositeLayer { const reload = reloadData(viewTiles, this.state.viewTiles); if (reload.tiles == true || reload.zoom == true) { viewTiles.zoom = viewTiles.zoom > 10 ? 10 : viewTiles.zoom; - const ldata = getAllTiles(this.state.source, viewTiles); + + const ldata = getAllTiles(this.state.source, this.state.tiles, viewTiles); + clearTiles(this.state.tiles, viewTiles); + this.setState({ storage: {}, viewTiles, ldata, + tiles: this.state.tiles, }); } else { this.setState({ From af3dac8e38c5b222f8ba0ef486016e0a8b27100a Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:36:11 +0100 Subject: [PATCH 08/12] Disable smooth scroll --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 4b6fb24..3a431a0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -253,7 +253,7 @@ export function App() { { setZoom(viewState.zoom); From 8c7e722645b7e58a8ff42fa58616d9f7fcac2b82 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:40:51 +0100 Subject: [PATCH 09/12] Change default data visibility to false --- src/config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.tsx b/src/config.tsx index bd3e90c..730a163 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -429,7 +429,7 @@ export const mapElements: Dictionary = { color: colors["other-building"], dark_color: colors["dark-other-building"], type: "data", - checked: true, + checked: false, stroke: 0, }, }; From 888506860b4a169dde96fd670c38977bee09ea8d Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:26:00 +0100 Subject: [PATCH 10/12] Save mesan data to file --- src/data_layer/smhi.py | 61 +++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/data_layer/smhi.py b/src/data_layer/smhi.py index e8c937a..4116846 100644 --- a/src/data_layer/smhi.py +++ b/src/data_layer/smhi.py @@ -1,37 +1,46 @@ """SMHI data layer.""" import json -import pandas as pd +import logging from smhi.mesan import Mesan +logger = logging.getLogger() -def save_mesan_multipoint(file: str) -> pd.DataFrame: + +def save_mesan_multipoint(file: str) -> None: + """Save Mesan multipoint data. + + Args: + file: file path for saved file + """ client = Mesan() valid_times = client.valid_time - coordinates = client.get_geo_multipoint(20) - # temperature = client.get_multipoint(valid_times[10], "t", "hl", 2, 10) + coordinates = client.get_geo_multipoint(1) + + all_temperatures = [] + for i, time in enumerate(valid_times): + try: + temperature = client.get_multipoint(time, "t", "hl", 2, 1) + except KeyError: + logger.warning(f"No temperature data found for {time}.") + all_temperatures.append(temperature["values"].to_list()) - print(valid_times) - tmp = [] - for i, time in enumerate(valid_times[1:-9]): - temperature = client.get_multipoint(time, "t", "hl", 2, 20) - tmp.append(temperature["values"].to_list()) + if len(all_temperatures) == 0: + logger.warning("No data saved.") + return None features = [] - for i, c in enumerate(coordinates): - temp = [j[i] for j in tmp] - features.append({"t": temp, "c": c, "time": len(tmp)}) - - # { - # # "type": "Feature", - # # "properties": { - # "t": tmp, - # "c": coordinates[i], - # "time": tm, - # # }, - # # "geometry": {"type": "Point", "coordinates": coordinates[index]}, - # } - # ) + for i, coord in enumerate(coordinates): + temperature = [j[i] for j in all_temperatures] + features.append( + { + "type": "Feature", + "properties": { + "t": temperature, + }, + "geometry": {"type": "Point", "coordinates": coord}, + } + ) geosjson = { "type": "FeatureCollection", @@ -43,8 +52,4 @@ def save_mesan_multipoint(file: str) -> pd.DataFrame: } with open(file, "w") as f: - json.dump(features, f) - - print(valid_times[0]) - - # return temperature_and_coordinates + json.dump(geosjson, f) From cea058eb760ac12a6fa251a8fc19275ed3f4fbb2 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:03:00 +0100 Subject: [PATCH 11/12] Add metfcts data download --- src/data_layer/smhi.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/data_layer/smhi.py b/src/data_layer/smhi.py index 4116846..8677114 100644 --- a/src/data_layer/smhi.py +++ b/src/data_layer/smhi.py @@ -2,18 +2,19 @@ import json import logging +from typing import Union, Optional from smhi.mesan import Mesan +from smhi.metfcts import Metfcts logger = logging.getLogger() -def save_mesan_multipoint(file: str) -> None: - """Save Mesan multipoint data. +def get_data(client: Union[Mesan, Metfcts]) -> Optional[dict]: + """Get data from client. Args: - file: file path for saved file + client: Mesan or Metfcts clients """ - client = Mesan() valid_times = client.valid_time coordinates = client.get_geo_multipoint(1) @@ -21,9 +22,9 @@ def save_mesan_multipoint(file: str) -> None: for i, time in enumerate(valid_times): try: temperature = client.get_multipoint(time, "t", "hl", 2, 1) + all_temperatures.append(temperature["values"].to_list()) except KeyError: logger.warning(f"No temperature data found for {time}.") - all_temperatures.append(temperature["values"].to_list()) if len(all_temperatures) == 0: logger.warning("No data saved.") @@ -42,7 +43,7 @@ def save_mesan_multipoint(file: str) -> None: } ) - geosjson = { + return { "type": "FeatureCollection", "crs": { "type": "name", @@ -51,5 +52,28 @@ def save_mesan_multipoint(file: str) -> None: "features": features, } + +def save_metfcts_multipoint(file: str) -> None: + """Save Metfcts multipoint data. + + Args: + file: file path for saved file + """ + client = Metfcts() + geojson = get_data(client) + + with open(file, "w") as f: + json.dump(geojson, f) + + +def save_mesan_multipoint(file: str) -> None: + """Save Mesan multipoint data. + + Args: + file: file path for saved file + """ + client = Mesan() + geojson = get_data(client) + with open(file, "w") as f: - json.dump(geosjson, f) + json.dump(geojson, f) From ce4902d5923d30d46fc7a859fdba8b7ebd35f065 Mon Sep 17 00:00:00 2001 From: Mladen Gibanica <11275336+mgcth@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:57:39 +0100 Subject: [PATCH 12/12] Ignore smhi types --- pyproject.toml | 5 +++-- src/data_layer/smhi.py | 4 ++-- src/text_search_server/search.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e39706..fe288f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,5 +36,6 @@ dev = [ where = ["src"] exclude = ["material"] -[tool.mypy] -ignore_missing_imports = true +[[tool.mypy.overrides]] +module = ["smhi.*"] +ignore_missing_imports = true \ No newline at end of file diff --git a/src/data_layer/smhi.py b/src/data_layer/smhi.py index 8677114..82801f2 100644 --- a/src/data_layer/smhi.py +++ b/src/data_layer/smhi.py @@ -3,8 +3,8 @@ import json import logging from typing import Union, Optional -from smhi.mesan import Mesan -from smhi.metfcts import Metfcts +from smhi.mesan import Mesan # type: ignore +from smhi.metfcts import Metfcts # type: ignore logger = logging.getLogger() diff --git a/src/text_search_server/search.py b/src/text_search_server/search.py index 0db474f..997a469 100644 --- a/src/text_search_server/search.py +++ b/src/text_search_server/search.py @@ -2,7 +2,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI -from text_search_server import trie +from text_search_server import trie # type: ignore import geopandas as gpd # type: ignore from fastapi.middleware.cors import CORSMiddleware