Skip to content
89 changes: 68 additions & 21 deletions packages/base/src/dialogs/symbology/hooks/useGetProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,64 @@ interface IUseGetPropertiesResult {
error?: Error;
}

async function getGeoJsonProperties({
source,
model,
}: {
source: any;
model: IJupyterGISModel;
}): Promise<Record<string, Set<any>>> {
const result: Record<string, Set<any>> = {};

const data = await loadFile({
filepath: source.parameters?.path,
type: 'GeoJSONSource',
model,
});

if (!data) {
throw new Error('Failed to read GeoJSON data');
}

data.features.forEach((feature: GeoJSONFeature1) => {
if (feature.properties) {
for (const [key, value] of Object.entries(feature.properties)) {
if (!result[key]) {
result[key] = new Set();
}
result[key].add(value);
}
}
});

return result;
}

function getVectorTileProperties({
model,
sourceId,
}: {
model: IJupyterGISModel;
sourceId: string;
}): Record<string, Set<any>> {
const result: Record<string, Set<any>> = {};
const features = model.getFeaturesForCurrentTile({ sourceId });

features.forEach(feature => {
const props = feature.getProperties?.();
if (props) {
for (const [key, value] of Object.entries(props)) {
if (!result[key]) {
result[key] = new Set();
}
result[key].add(value);
}
}
});

return result;
}

export const useGetProperties = ({
layerId,
model,
Expand All @@ -39,33 +97,22 @@ export const useGetProperties = ({
throw new Error('Source not found');
}

const data = await loadFile({
filepath: source.parameters?.path,
type: 'GeoJSONSource',
model: model,
});
const sourceType = source?.type;
let result: Record<string, Set<any>> = {};

if (!data) {
throw new Error('Failed to read GeoJSON data');
if (sourceType === 'GeoJSONSource') {
result = await getGeoJsonProperties({ source, model });
} else if (sourceType === 'VectorTileSource') {
const sourceId = layer?.parameters?.source;
result = getVectorTileProperties({ model, sourceId });
} else {
throw new Error(`Unsupported source type: ${sourceType}`);
}

const result: Record<string, Set<any>> = {};

data.features.forEach((feature: GeoJSONFeature1) => {
if (feature.properties) {
Object.entries(feature.properties).forEach(([key, value]) => {
if (!(key in result)) {
result[key] = new Set();
}
result[key].add(value);
});
}
});

setFeatureProperties(result);
setIsLoading(false);
} catch (err) {
setError(err as Error);
} finally {
setIsLoading(false);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ const RENDER_TYPE_OPTIONS: RenderTypeOptions = {
Canonical: {
component: Canonical,
attributeChecker: getColorCodeFeatureAttributes,
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
supportedLayerTypes: ['VectorLayer', 'VectorTileLayer', 'HeatmapLayer'],
isTabbed: false,
},
Graduated: {
component: Graduated,
attributeChecker: getNumericFeatureAttributes,
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
supportedLayerTypes: ['VectorLayer', 'VectorTileLayer', 'HeatmapLayer'],
isTabbed: true,
},
Categorized: {
component: Categorized,
attributeChecker: getNumericFeatureAttributes,
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
supportedLayerTypes: ['VectorLayer', 'VectorTileLayer', 'HeatmapLayer'],
isTabbed: true,
},
Heatmap: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,15 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
});
newStyle['fill-color'] = colorExpr;
newStyle['circle-fill-color'] = colorExpr;
newStyle['stroke-color'] = colorExpr;
newStyle['circle-stroke-color'] = colorExpr;
} else {
newStyle['fill-color'] = undefined;
newStyle['circle-fill-color'] = undefined;
newStyle['stroke-color'] = colorManualStyleRef.current.strokeColor;
newStyle['circle-stroke-color'] = colorManualStyleRef.current.strokeColor;
}

newStyle['stroke-color'] = colorManualStyleRef.current.strokeColor;
newStyle['circle-stroke-color'] = colorManualStyleRef.current.strokeColor;
newStyle['stroke-width'] = colorManualStyleRef.current.strokeWidth;
newStyle['circle-stroke-width'] = colorManualStyleRef.current.strokeWidth;

Expand Down
21 changes: 19 additions & 2 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ import { IStateDB } from '@jupyterlab/statedb';
import { CommandRegistry } from '@lumino/commands';
import { JSONValue, UUID } from '@lumino/coreutils';
import { ContextMenu } from '@lumino/widgets';
import { Collection, MapBrowserEvent, Map as OlMap, View, getUid } from 'ol';
import {
Collection,
MapBrowserEvent,
Map as OlMap,
VectorTile,
View,
getUid,
} from 'ol';
import Feature, { FeatureLike } from 'ol/Feature';
import { FullScreen, ScaleLine } from 'ol/control';
import { Coordinate } from 'ol/coordinate';
Expand Down Expand Up @@ -72,9 +79,10 @@ import {
Vector as VectorSource,
VectorTile as VectorTileSource,
XYZ as XYZSource,
Tile as TileSource,
} from 'ol/source';
import Static from 'ol/source/ImageStatic';
import TileSource from 'ol/source/Tile';
import { TileSourceEvent } from 'ol/source/Tile';
import { Circle, Fill, Stroke, Style } from 'ol/style';
import { Rule } from 'ol/style/flat';
//@ts-expect-error no types for ol-pmtiles
Expand Down Expand Up @@ -633,6 +641,15 @@ export class MainView extends React.Component<IProps, IStates> {
});
}

newSource.on('tileloadend', (event: TileSourceEvent) => {
const tile = event.tile as VectorTile<FeatureLike>;
const features = tile.getFeatures();

if (features && features.length > 0) {
this._model.syncTileFeatures({ sourceId: id, features });
}
});

break;
}
case 'GeoJSONSource': {
Expand Down
14 changes: 14 additions & 0 deletions packages/schema/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Contents, User } from '@jupyterlab/services';
import { JSONObject } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';
import { SplitPanel } from '@lumino/widgets';
import { FeatureLike } from 'ol/Feature';

import {
IJGISContent,
Expand Down Expand Up @@ -186,6 +187,19 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {

pathChanged: ISignal<IJupyterGISModel, string>;

getFeaturesForCurrentTile: ({
sourceId,
}: {
sourceId: string;
}) => FeatureLike[];
syncTileFeatures: ({
sourceId,
features,
}: {
sourceId: string;
features: FeatureLike[];
}) => void;

getSettings(): IJupyterGISSettings;
getContent(): IJGISContent;
getLayers(): IJGISLayers;
Expand Down
22 changes: 22 additions & 0 deletions packages/schema/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { PartialJSONObject } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';
import Ajv from 'ajv';
import { FeatureLike } from 'ol/Feature';

import {
IJGISContent,
Expand Down Expand Up @@ -78,6 +79,26 @@ export class JupyterGISModel implements IJupyterGISModel {
return this._settings;
}

getFeaturesForCurrentTile({ sourceId }: { sourceId: string }): FeatureLike[] {
return Array.from(this._tileFeatureCache.get(sourceId) ?? []);
}

syncTileFeatures({
sourceId,
features,
}: {
sourceId: string;
features: FeatureLike[];
}): void {
let featureSet = this._tileFeatureCache.get(sourceId);

if (!featureSet) {
featureSet = new Set();
this._tileFeatureCache.set(sourceId, featureSet);
}
features.forEach(feature => featureSet.add(feature));
}

private _onSharedModelChanged = (sender: any, changes: any): void => {
if (changes && changes?.objectChange?.length) {
this._contentChanged.emit(void 0);
Expand Down Expand Up @@ -793,6 +814,7 @@ export class JupyterGISModel implements IJupyterGISModel {

private _geolocation: JgisCoordinates;
private _geolocationChanged = new Signal<this, JgisCoordinates>(this);
private _tileFeatureCache: Map<string, Set<FeatureLike>> = new Map();
}

export namespace JupyterGISModel {
Expand Down
Loading