From a4e58333e73f2a8ce94deaff1cc5fd56c3349631 Mon Sep 17 00:00:00 2001 From: zifanw9 <81448366+zifanw9@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:45:44 -0800 Subject: [PATCH 1/5] add a query parameter to change url of VectorTileLayer --- package-lock.json | 4 +- src/ts/react-leaflet/VectorTileLayer.ts | 53 ++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7676a4b..ba8f4d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dash-leaflet", - "version": "1.0.15", + "version": "1.0.17rc2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dash-leaflet", - "version": "1.0.15", + "version": "1.0.17rc2", "license": "MIT", "dependencies": { "@mapbox/vector-tile": "^2.0.3", diff --git a/src/ts/react-leaflet/VectorTileLayer.ts b/src/ts/react-leaflet/VectorTileLayer.ts index 76e808c..23ae9c6 100644 --- a/src/ts/react-leaflet/VectorTileLayer.ts +++ b/src/ts/react-leaflet/VectorTileLayer.ts @@ -72,6 +72,12 @@ export type VectorTileLayerOptions = { */ vectorTileLayerStyles?: object; // default undefined + /** + * Passing a Python dictionary, this dictionary will be turned into + * a URLSearchParams JavaScript object, which will be a part of url + * https://xyz/collections/public.building/tiles/WebMercatorQuad/{z}/{x}/{y}?{query} + */ + query?: object; } export type VectorTileLayerProps = Modify( + function createTileLayer({ url, ...options }, context) { + if (options.query != null) { + for (const [key, value] of Object.entries(options.query)) { + query_formatted.append(key,value); + } + } + const resolvedOptions = resolveProps(options, _funcOptions, context); - const layer = leafletVectorTileLayer(url, withPane(resolvedOptions, context)) - return createElementObject(layer, context) + const layer = leafletVectorTileLayer(url, Object.assign({},withPane(resolvedOptions, context), {query_formatted})); + return createElementObject(layer, context); }, function updateTileLayer(layer, props, prevProps) { - updateGridLayer(layer, props, prevProps) - const { url } = props + updateGridLayer(layer, props, prevProps); + const { query } = props // TODO: Double check property stuff here - if (url != null && url !== prevProps.url) { - layer.setUrl(url) + if (query != null && JSON.stringify(query) != JSON.stringify(prevProps.query)) { + console.log("Current query"); + console.log(query); + console.log("Previous query"); + console.log(prevProps.query); + + const new_query_keys = Object.keys(query); + + // loop through the old query + query_formatted.forEach((value, key) => { + if (new_query_keys.includes(key)) { + if (query[key] !== value) { + query_formatted.set(key, query[key]); + } + } + else { + query_formatted.delete(key); + } + }); + + // loop through new query + for (const [key, value] of Object.entries(query)) { + if (!query_formatted.has(key)) { + query_formatted.append(key,value); + } + } + + layer.redraw(); } }, ) From a9f575285fa03501b11dfd7f62a02dc39d45d62d Mon Sep 17 00:00:00 2001 From: zifanw9 <81448366+zifanw9@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:29:34 -0800 Subject: [PATCH 2/5] simplify VectorTileUrl parameter query_formatted to q and change package version --- package.json | 2 +- src/ts/react-leaflet/VectorTileLayer.ts | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 9c8e5f2..cfa1f62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dash-leaflet", - "version": "1.0.17rc2", + "version": "1.0.17rc2.5", "description": "Dash Leaflet is a light wrapper around React-Leaflet. The syntax is similar to other Dash components, with naming conventions following the React-Leaflet API.", "main": "index.ts", "repository": { diff --git a/src/ts/react-leaflet/VectorTileLayer.ts b/src/ts/react-leaflet/VectorTileLayer.ts index 23ae9c6..42559e5 100644 --- a/src/ts/react-leaflet/VectorTileLayer.ts +++ b/src/ts/react-leaflet/VectorTileLayer.ts @@ -74,8 +74,8 @@ export type VectorTileLayerOptions = { /** * Passing a Python dictionary, this dictionary will be turned into - * a URLSearchParams JavaScript object, which will be a part of url - * https://xyz/collections/public.building/tiles/WebMercatorQuad/{z}/{x}/{y}?{query} + * a URLSearchParams JavaScript object, which will correspond to {q} in the following url + * https://xyz/collections/schema.table/tiles/WebMercatorQuad/{z}/{x}/{y}?{q} */ query?: object; } @@ -89,7 +89,7 @@ export type VectorTileLayerProps = Modify { + q.forEach((value, key) => { if (new_query_keys.includes(key)) { if (query[key] !== value) { - query_formatted.set(key, query[key]); + q.set(key, query[key]); } } else { - query_formatted.delete(key); + q.delete(key); } }); // loop through new query for (const [key, value] of Object.entries(query)) { - if (!query_formatted.has(key)) { - query_formatted.append(key,value); + if (!q.has(key)) { + q.append(key,value); } } From d44e61d73c7d9f81fe4c47e5f247d3123fbea0a4 Mon Sep 17 00:00:00 2001 From: zifanw9 <81448366+zifanw9@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:47:56 -0800 Subject: [PATCH 3/5] fix malfunction when two vector tile layers are added to the map --- package-lock.json | 4 ++-- src/ts/react-leaflet/VectorTileLayer.ts | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba8f4d9..6b66f07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dash-leaflet", - "version": "1.0.17rc2", + "version": "1.0.17rc2.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dash-leaflet", - "version": "1.0.17rc2", + "version": "1.0.17rc2.5", "license": "MIT", "dependencies": { "@mapbox/vector-tile": "^2.0.3", diff --git a/src/ts/react-leaflet/VectorTileLayer.ts b/src/ts/react-leaflet/VectorTileLayer.ts index 42559e5..e9fe207 100644 --- a/src/ts/react-leaflet/VectorTileLayer.ts +++ b/src/ts/react-leaflet/VectorTileLayer.ts @@ -9,7 +9,7 @@ import { import { default as leafletVectorTileLayer } from 'leaflet-vector-tile-layer' import { DashFunction, Modify, resolveProps, TileLayerProps } from "../props"; import { omit, pick } from "../utils"; -import { GridLayer, TileLayer } from 'leaflet'; +import { GridLayer, TileLayer, TileLayerOptions } from 'leaflet'; export type VectorTileLayerOptions = { /** @@ -89,7 +89,9 @@ export type VectorTileLayerProps = Modify( function createTileLayer({ url, ...options }, context) { + const q = new URLSearchParams({}); + if (options.query != null) { for (const [key, value] of Object.entries(options.query)) { q.append(key,value); @@ -114,7 +118,8 @@ export const VectorTileLayer = createTileLayerComponent< if (query != null && JSON.stringify(query) != JSON.stringify(prevProps.query)) { const new_query_keys = Object.keys(query); - + const q = (layer.options as ExtendedTileLayerOptions).q; + const _oldKeyToRemove = []; // loop through the old query q.forEach((value, key) => { if (new_query_keys.includes(key)) { @@ -123,10 +128,16 @@ export const VectorTileLayer = createTileLayerComponent< } } else { - q.delete(key); + _oldKeyToRemove.push(key); } }); + for (let _key of _oldKeyToRemove) { + if (_key != null) { + q.delete(_key); + } + } + // loop through new query for (const [key, value] of Object.entries(query)) { if (!q.has(key)) { From b06048f7982a7a14483684c0fdadb5fbd9cc079a Mon Sep 17 00:00:00 2001 From: zifanw9 <81448366+zifanw9@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:46:12 -0800 Subject: [PATCH 4/5] VectorTileLayer add setStyle method so style becomes mutable and can be updated --- src/ts/react-leaflet/VectorTileLayer.ts | 47 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/ts/react-leaflet/VectorTileLayer.ts b/src/ts/react-leaflet/VectorTileLayer.ts index e9fe207..9bfae06 100644 --- a/src/ts/react-leaflet/VectorTileLayer.ts +++ b/src/ts/react-leaflet/VectorTileLayer.ts @@ -93,8 +93,42 @@ interface ExtendedTileLayerOptions extends TileLayerOptions { q?: URLSearchParams; } +interface VectorTileLayer extends TileLayer { + setStyle(style: any): void; +} + +function checkStrictEqualStyle(style1 : any, style2 : any) { + if (typeof style1 === 'function' && typeof style2 === 'function') { + if (style1.toString() === style2.toString()) { + // If two functions' string representation are the same, but they + // rely on an additional parameter/variable in it that can change; + // let's standardize this parameter name hideout + if (style1.hideout != null && style2.hideout != null) { + if (typeof style1.hideout === 'object' && typeof style2.hideout === 'object') { + if (JSON.stringify(style1.hideout) == JSON.stringify(style2.hideout)) { + return true; + } + } + else if (style1.hideout === style2.hideout) { + return true; + } + } + } + } + else if (typeof style1 === 'object' && typeof style2 === 'object') { + if (JSON.stringify(style1) == JSON.stringify(style2)) { + return true; + } + } + else if (style1 === style2) { + return true; + } + + return false; +} + export const VectorTileLayer = createTileLayerComponent< - TileLayer, // MAKE PROPER CLASS (might be equal though?) + VectorTileLayer, // MAKE PROPER CLASS (might be equal though?) VectorTileLayerProps >( @@ -113,7 +147,7 @@ export const VectorTileLayer = createTileLayerComponent< }, function updateTileLayer(layer, props, prevProps) { updateGridLayer(layer, props, prevProps); - const { query } = props + const { query, style } = props; // TODO: Double check property stuff here if (query != null && JSON.stringify(query) != JSON.stringify(prevProps.query)) { @@ -147,5 +181,14 @@ export const VectorTileLayer = createTileLayerComponent< layer.redraw(); } + + if (style != null && checkStrictEqualStyle(style, prevProps.style)==false) { + //console.log("Update style"); + //console.log(prevProps.style); + //console.log(style); + layer.setStyle(style); + // It seems that setStyle does not require calling layer redraw method + // as the layer is updated automatically + } }, ) From 97305649809d4cd1addca1ddfe7a56868771cf30 Mon Sep 17 00:00:00 2001 From: zifanw9 <81448366+zifanw9@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:19:24 -0800 Subject: [PATCH 5/5] add conditional redraw for VectorTileLayer --- src/ts/react-leaflet/VectorTileLayer.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/ts/react-leaflet/VectorTileLayer.ts b/src/ts/react-leaflet/VectorTileLayer.ts index 9bfae06..12ed288 100644 --- a/src/ts/react-leaflet/VectorTileLayer.ts +++ b/src/ts/react-leaflet/VectorTileLayer.ts @@ -91,6 +91,10 @@ const _funcOptions = ["featureToLayer", "filter", "layerOrder", "style"] interface ExtendedTileLayerOptions extends TileLayerOptions { q?: URLSearchParams; + + // Tracking zoom when last time use setStyle method; if the zoom this time apply setStyle is different from last time + // the layer will need to be redraw + zoomWhenStyleChanged?: number; } interface VectorTileLayer extends TileLayer { @@ -187,8 +191,21 @@ export const VectorTileLayer = createTileLayerComponent< //console.log(prevProps.style); //console.log(style); layer.setStyle(style); - // It seems that setStyle does not require calling layer redraw method - // as the layer is updated automatically + // In most cases setStyle does not require calling layer redraw method + // as the layer is updated automatically, but in some situations (such as + // applying a filter to get a subset of features shown -> zoom out or in + // -> remove the filter), the layer will not update automatically + + const zoomWhenStyleChanged = (layer.options as ExtendedTileLayerOptions).zoomWhenStyleChanged; + if (zoomWhenStyleChanged == null) { + (layer.options as ExtendedTileLayerOptions).zoomWhenStyleChanged = layer["_tileZoom"]; + //console.log(`First time set zoomWhenStyleChanged to ${layer["_tileZoom"]}`); + } + else if (zoomWhenStyleChanged !== layer["_tileZoom"]) { + layer.redraw(); + //console.log(`Redraw: ${zoomWhenStyleChanged} changed to ${layer["_tileZoom"]}`); + (layer.options as ExtendedTileLayerOptions).zoomWhenStyleChanged = layer["_tileZoom"]; + } } }, )