From c6015fde5cd8be4b4f653045c61fdb24fbf1f117 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 8 Aug 2025 02:45:25 +0530 Subject: [PATCH 1/5] Make VectorTiles downlodable as GeoJSON --- packages/base/src/commands/index.ts | 4 +-- packages/base/src/mainview/mainView.tsx | 35 ++++++++++++++++++++----- packages/base/src/processing/index.ts | 2 +- packages/base/src/tools.ts | 21 ++++++++++++++- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index d702984cf..305aaa539 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -758,7 +758,7 @@ export function addCommands( isEnabled: () => { const selectedLayer = getSingleSelectedLayer(tracker); return selectedLayer - ? ['VectorLayer', 'ShapefileLayer'].includes(selectedLayer.type) + ? ['VectorLayer', 'ShapefileLayer', 'VectorTileLayer'].includes(selectedLayer.type) : false; }, execute: async () => { @@ -800,7 +800,7 @@ export function addCommands( const sourceId = selectedLayer.parameters.source; const source = sources[sourceId]; - const geojsonString = await getGeoJSONDataFromLayerSource(source, model); + const geojsonString = await getGeoJSONDataFromLayerSource(selectedLayer, source, model); if (!geojsonString) { return; } diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index cfa42a40c..a73f83b35 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -102,6 +102,7 @@ import { FollowIndicator } from './FollowIndicator'; import TemporalSlider from './TemporalSlider'; import { MainViewModel } from './mainviewmodel'; import { LeftPanel, RightPanel } from '../panelview'; +import { toGeometry } from 'ol/render/Feature'; type OlLayerTypes = | TileLayer @@ -641,14 +642,16 @@ export class MainView extends React.Component { }); } - newSource.on('tileloadend', (event: TileSourceEvent) => { - const tile = event.tile as VectorTile; - const features = tile.getFeatures(); + newSource.on('tileloadend', (event: TileSourceEvent) => { + const tile = event.tile as VectorTile; + const rawFeatures = tile.getFeatures() as RenderFeature[]; + + if (rawFeatures && rawFeatures.length > 0) { + const realFeatures = this.convertRenderFeaturesToFeatures(rawFeatures); + this._model.syncTileFeatures({ sourceId: id, features: realFeatures }); + } + }); - if (features && features.length > 0) { - this._model.syncTileFeatures({ sourceId: id, features }); - } - }); break; } @@ -828,6 +831,24 @@ export class MainView extends React.Component { this._sources[id] = newSource; } +private convertRenderFeaturesToFeatures(renderFeatures: RenderFeature[]): Feature[] { + const features: Feature[] = []; + + for (const rf of renderFeatures) { + const properties = rf.getProperties(); + const geometry = toGeometry(rf); + + if (!geometry) continue; + + const feature = new Feature({ ...properties }); + feature.setGeometry(geometry); + features.push(feature); + } + + return features; +} + + private computeSourceUrl(source: IJGISSource): string { const parameters = source.parameters as IRasterSource; const urlParameters = parameters.urlParameters || {}; diff --git a/packages/base/src/processing/index.ts b/packages/base/src/processing/index.ts index 20b1942b9..3b10e0d56 100644 --- a/packages/base/src/processing/index.ts +++ b/packages/base/src/processing/index.ts @@ -79,7 +79,7 @@ export async function getLayerGeoJSON( return null; } - return await getGeoJSONDataFromLayerSource(source, model); + return await getGeoJSONDataFromLayerSource(layer,source, model); } export type GdalFunctions = diff --git a/packages/base/src/tools.ts b/packages/base/src/tools.ts index 855d98374..4be53b776 100644 --- a/packages/base/src/tools.ts +++ b/packages/base/src/tools.ts @@ -3,6 +3,7 @@ import { IJGISLayerBrowserRegistry, IJGISOptions, IJGISSource, + IJGISLayer, IJupyterGISModel, IRasterLayerGalleryEntry, SourceType, @@ -13,6 +14,8 @@ import { Contents, ServerConnection } from '@jupyterlab/services'; import { VectorTile } from '@mapbox/vector-tile'; import * as d3Color from 'd3-color'; import { compressors } from 'hyparquet-compressors'; +import Feature from 'ol/Feature'; +import GeoJSON from 'ol/format/GeoJSON'; import Protobuf from 'pbf'; import shp from 'shpjs'; @@ -949,10 +952,15 @@ export function downloadFile( } export async function getGeoJSONDataFromLayerSource( + selectedLayer: IJGISLayer, source: IJGISSource, model: IJupyterGISModel, ): Promise { - const vectorSourceTypes: SourceType[] = ['GeoJSONSource', 'ShapefileSource']; + const vectorSourceTypes: SourceType[] = [ + 'GeoJSONSource', + 'ShapefileSource', + 'VectorTileSource' + ]; if (!vectorSourceTypes.includes(source.type as SourceType)) { console.error( @@ -961,6 +969,17 @@ export async function getGeoJSONDataFromLayerSource( return null; } + const sourceId = selectedLayer.parameters?.source; + if (source.type === 'VectorTileSource' && sourceId) { + const features = model.getFeaturesForCurrentTile({ sourceId }); + + + const format = new GeoJSON(); + const geojson = format.writeFeaturesObject(features as Feature[]); + + return JSON.stringify(geojson); + } + if (!source.parameters) { console.error('Source parameters are missing.'); return null; From 27a5482dc508a970efe4b4bbdf05ce48ed9ee4d3 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 8 Aug 2025 02:45:53 +0530 Subject: [PATCH 2/5] lint --- packages/base/src/commands/index.ts | 10 ++++- packages/base/src/mainview/mainView.tsx | 52 +++++++++++++------------ packages/base/src/processing/index.ts | 2 +- packages/base/src/tools.ts | 3 +- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 305aaa539..826f3220e 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -758,7 +758,9 @@ export function addCommands( isEnabled: () => { const selectedLayer = getSingleSelectedLayer(tracker); return selectedLayer - ? ['VectorLayer', 'ShapefileLayer', 'VectorTileLayer'].includes(selectedLayer.type) + ? ['VectorLayer', 'ShapefileLayer', 'VectorTileLayer'].includes( + selectedLayer.type, + ) : false; }, execute: async () => { @@ -800,7 +802,11 @@ export function addCommands( const sourceId = selectedLayer.parameters.source; const source = sources[sourceId]; - const geojsonString = await getGeoJSONDataFromLayerSource(selectedLayer, source, model); + const geojsonString = await getGeoJSONDataFromLayerSource( + selectedLayer, + source, + model, + ); if (!geojsonString) { return; } diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index a73f83b35..dd4fecbfe 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -73,6 +73,7 @@ import { } from 'ol/proj'; import { register } from 'ol/proj/proj4.js'; import RenderFeature from 'ol/render/Feature'; +import { toGeometry } from 'ol/render/Feature'; import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, @@ -102,7 +103,6 @@ import { FollowIndicator } from './FollowIndicator'; import TemporalSlider from './TemporalSlider'; import { MainViewModel } from './mainviewmodel'; import { LeftPanel, RightPanel } from '../panelview'; -import { toGeometry } from 'ol/render/Feature'; type OlLayerTypes = | TileLayer @@ -642,16 +642,19 @@ export class MainView extends React.Component { }); } - newSource.on('tileloadend', (event: TileSourceEvent) => { - const tile = event.tile as VectorTile; - const rawFeatures = tile.getFeatures() as RenderFeature[]; - - if (rawFeatures && rawFeatures.length > 0) { - const realFeatures = this.convertRenderFeaturesToFeatures(rawFeatures); - this._model.syncTileFeatures({ sourceId: id, features: realFeatures }); - } - }); - + newSource.on('tileloadend', (event: TileSourceEvent) => { + const tile = event.tile as VectorTile; + const rawFeatures = tile.getFeatures() as RenderFeature[]; + + if (rawFeatures && rawFeatures.length > 0) { + const realFeatures = + this.convertRenderFeaturesToFeatures(rawFeatures); + this._model.syncTileFeatures({ + sourceId: id, + features: realFeatures, + }); + } + }); break; } @@ -831,23 +834,24 @@ export class MainView extends React.Component { this._sources[id] = newSource; } -private convertRenderFeaturesToFeatures(renderFeatures: RenderFeature[]): Feature[] { - const features: Feature[] = []; - - for (const rf of renderFeatures) { - const properties = rf.getProperties(); - const geometry = toGeometry(rf); + private convertRenderFeaturesToFeatures( + renderFeatures: RenderFeature[], + ): Feature[] { + const features: Feature[] = []; - if (!geometry) continue; + for (const rf of renderFeatures) { + const properties = rf.getProperties(); + const geometry = toGeometry(rf); - const feature = new Feature({ ...properties }); - feature.setGeometry(geometry); - features.push(feature); - } + if (!geometry) {continue;} - return features; -} + const feature = new Feature({ ...properties }); + feature.setGeometry(geometry); + features.push(feature); + } + return features; + } private computeSourceUrl(source: IJGISSource): string { const parameters = source.parameters as IRasterSource; diff --git a/packages/base/src/processing/index.ts b/packages/base/src/processing/index.ts index 3b10e0d56..159e200ab 100644 --- a/packages/base/src/processing/index.ts +++ b/packages/base/src/processing/index.ts @@ -79,7 +79,7 @@ export async function getLayerGeoJSON( return null; } - return await getGeoJSONDataFromLayerSource(layer,source, model); + return await getGeoJSONDataFromLayerSource(layer, source, model); } export type GdalFunctions = diff --git a/packages/base/src/tools.ts b/packages/base/src/tools.ts index 4be53b776..1479ab04b 100644 --- a/packages/base/src/tools.ts +++ b/packages/base/src/tools.ts @@ -959,7 +959,7 @@ export async function getGeoJSONDataFromLayerSource( const vectorSourceTypes: SourceType[] = [ 'GeoJSONSource', 'ShapefileSource', - 'VectorTileSource' + 'VectorTileSource', ]; if (!vectorSourceTypes.includes(source.type as SourceType)) { @@ -973,7 +973,6 @@ export async function getGeoJSONDataFromLayerSource( if (source.type === 'VectorTileSource' && sourceId) { const features = model.getFeaturesForCurrentTile({ sourceId }); - const format = new GeoJSON(); const geojson = format.writeFeaturesObject(features as Feature[]); From 5988dfb64473ae6ce06a918f47f416f71468d038 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 11 Aug 2025 11:04:25 +0530 Subject: [PATCH 3/5] lint --- packages/base/src/commands/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 826f3220e..69384fea9 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -811,6 +811,7 @@ export function addCommands( return; } + downloadFile( geojsonString, `${exportFileName}.geojson`, From 8354fc1d4b90bf6bfca117da8c0e3855facf97d1 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 11 Aug 2025 14:58:47 +0530 Subject: [PATCH 4/5] lint-2 --- packages/base/src/commands/index.ts | 1 - packages/base/src/mainview/mainView.tsx | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 69384fea9..826f3220e 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -811,7 +811,6 @@ export function addCommands( return; } - downloadFile( geojsonString, `${exportFileName}.geojson`, diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index dd4fecbfe..ac1ef0ee4 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -843,7 +843,9 @@ export class MainView extends React.Component { const properties = rf.getProperties(); const geometry = toGeometry(rf); - if (!geometry) {continue;} + if (!geometry) { + continue; + } const feature = new Feature({ ...properties }); feature.setGeometry(geometry); From bf5107a852658b6c93cb1921eeb0f41d7b70ecf9 Mon Sep 17 00:00:00 2001 From: Arjun Verma Date: Wed, 13 Aug 2025 20:53:30 +0530 Subject: [PATCH 5/5] Update packages/base/src/mainview/mainView.tsx Co-authored-by: Greg Mooney --- packages/base/src/mainview/mainView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index ac1ef0ee4..b5763b547 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -72,8 +72,7 @@ import { transformExtent, } from 'ol/proj'; import { register } from 'ol/proj/proj4.js'; -import RenderFeature from 'ol/render/Feature'; -import { toGeometry } from 'ol/render/Feature'; +import RenderFeature, { toGeometry } from 'ol/render/Feature'; import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource,