Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify tool #270

Merged
merged 30 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1056311
Identify tool start
gjmooney Dec 20, 2024
999f019
Collapsable attempt
gjmooney Dec 20, 2024
be87584
Keep UI basic
gjmooney Dec 23, 2024
c712f7e
Handle HTML
gjmooney Dec 23, 2024
bfc358f
Add toolbar icon
gjmooney Dec 23, 2024
ab94ac8
Add identify toggle
gjmooney Dec 23, 2024
d9df656
Move filters to left panel
gjmooney Dec 23, 2024
bf543dd
Set up identify panel
gjmooney Dec 23, 2024
1aaba4c
Move features list to panel
gjmooney Dec 23, 2024
d81e23c
Grid attempt
gjmooney Dec 23, 2024
665bc2f
Get panel looking good
gjmooney Dec 24, 2024
9c86ad2
Improve CSS
gjmooney Dec 24, 2024
53fe813
Clear panel on file change
gjmooney Dec 24, 2024
b0f0999
Get toolbar to work with multiple files
gjmooney Dec 24, 2024
c187536
Clean up
gjmooney Dec 24, 2024
95a1d02
Add identify for webgltile layers
gjmooney Jan 3, 2025
018681d
Handle no selected layer
gjmooney Jan 3, 2025
76bb9dd
Highlight selected vector features
gjmooney Jan 6, 2025
956ede7
Use getFeatures for interaction
gjmooney Jan 6, 2025
bd29c6e
Use feature ref in handler
gjmooney Jan 6, 2025
ecc93ba
Display collaborators features if following
gjmooney Jan 6, 2025
6e84c27
Update identify switch
gjmooney Jan 8, 2025
d4da2cc
Remove unused execute args
gjmooney Jan 9, 2025
d95d842
Make identify button toggleable and add esc keybinding
gjmooney Jan 9, 2025
6bb0d97
Tweak highlight color
gjmooney Jan 9, 2025
cff9141
Require layer to be selected
gjmooney Jan 9, 2025
7811323
Have first feature expanded by default
gjmooney Jan 9, 2025
b0a3274
Iterate on layer requirement
gjmooney Jan 9, 2025
b83bfbc
Put stroke back
gjmooney Jan 10, 2025
124cdf5
Update Playwright Snapshots
github-actions[bot] Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions packages/base/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CreationFormDialog } from './dialogs/formdialog';
import { LayerBrowserWidget } from './dialogs/layerBrowserDialog';
import { SymbologyWidget } from './dialogs/symbology/symbologyDialog';
import { JupyterGISWidget } from './widget';
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';

interface ICreateEntry {
tracker: WidgetTracker<JupyterGISWidget>;
Expand Down Expand Up @@ -91,7 +92,7 @@ export function addCommands(
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
execute: args => {
execute: () => {
const current = tracker.currentWidget;

if (current) {
Expand All @@ -108,7 +109,7 @@ export function addCommands(
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
execute: args => {
execute: () => {
const current = tracker.currentWidget;

if (current) {
Expand All @@ -118,6 +119,43 @@ export function addCommands(
...icons.get(CommandIDs.undo)
});

commands.addCommand(CommandIDs.identify, {
label: trans.__('Identify'),
isToggled: () => {
return tracker.currentWidget?.context.model.isIdentifying || false;
},
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
execute: args => {
gjmooney marked this conversation as resolved.
Show resolved Hide resolved
const current = tracker.currentWidget;
if (!current) {
return;
}

const luminoEvent = args['_luminoEvent'] as
| ReadonlyPartialJSONObject
| undefined;

if (luminoEvent) {
const keysPressed = luminoEvent.keys as string[] | undefined;
if (keysPressed?.includes('Escape')) {
current.context.model.isIdentifying = false;
current.node.classList.remove('jGIS-identify-tool');
commands.notifyCommandChanged(CommandIDs.identify);
return;
}
}

current.node.classList.toggle('jGIS-identify-tool');
current.context.model.toggleIdentify();
commands.notifyCommandChanged(CommandIDs.identify);
},
...icons.get(CommandIDs.identify)
});

/**
* SOURCES and LAYERS creation commands.
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/base/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export namespace CommandIDs {
export const redo = 'jupytergis:redo';
export const undo = 'jupytergis:undo';
export const symbology = 'jupytergis:symbology';
export const identify = 'jupytergis:identify';

// Layers and sources creation commands
export const openLayerBrowser = 'jupytergis:openLayerBrowser';
Expand Down Expand Up @@ -95,7 +96,8 @@ const iconObject = {
[CommandIDs.newVideoEntry]: { iconClass: 'fa fa-video' },
[CommandIDs.newShapefileLayer]: { iconClass: 'fa fa-file' },
[CommandIDs.newGeoTiffEntry]: { iconClass: 'fa fa-image' },
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' }
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
[CommandIDs.identify]: { iconClass: 'fa fa-info' }
};

/**
Expand Down
100 changes: 98 additions & 2 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import {
import { IObservableMap, ObservableMap } from '@jupyterlab/observables';
import { User } from '@jupyterlab/services';
import { JSONValue, UUID } from '@lumino/coreutils';
import { Collection, Map as OlMap, View } from 'ol';
import { Collection, MapBrowserEvent, Map as OlMap, View } from 'ol';
import { ScaleLine } from 'ol/control';
import { GeoJSON, MVT } from 'ol/format';
import DragAndDrop from 'ol/interaction/DragAndDrop';
import { DragAndDrop, Select } from 'ol/interaction';
import {
Image as ImageLayer,
Layer,
Expand Down Expand Up @@ -72,6 +72,8 @@ import AnnotationFloater from '../annotations/components/AnnotationFloater';
import { CommandIDs } from '../constants';
import { FollowIndicator } from './FollowIndicator';
import CollaboratorPointers, { ClientPointer } from './CollaboratorPointers';
import { Circle, Fill, Stroke, Style } from 'ol/style';
import { singleClick } from 'ol/events/condition';

interface IProps {
viewModel: MainViewModel;
Expand Down Expand Up @@ -179,6 +181,7 @@ export class MainView extends React.Component<IProps, IStates> {
controls: [new ScaleLine()]
});

// Add map interactions
const dragAndDropInteraction = new DragAndDrop({
formatConstructors: [GeoJSON]
});
Expand Down Expand Up @@ -216,6 +219,51 @@ export class MainView extends React.Component<IProps, IStates> {

this._Map.addInteraction(dragAndDropInteraction);

const selectInteraction = new Select({
gjmooney marked this conversation as resolved.
Show resolved Hide resolved
hitTolerance: 5,
gjmooney marked this conversation as resolved.
Show resolved Hide resolved
multi: true,
layers: layer => {
const localState = this._model?.sharedModel.awareness.getLocalState();
const selectedLayers = localState?.selected?.value;

if (!selectedLayers) {
return false;
}
const selectedLayerId = Object.keys(selectedLayers)[0];

return layer === this.getLayer(selectedLayerId);
},
condition: (event: MapBrowserEvent<any>) => {
return singleClick(event) && this._model.isIdentifying;
},
style: new Style({
image: new Circle({
radius: 5,
fill: new Fill({
color: '#C52707'
}),
stroke: new Stroke({
color: '#171717',
width: 2
})
})
})
});

selectInteraction.on('select', event => {
const identifiedFeatures: IDict<any> = [];
selectInteraction.getFeatures().forEach(feature => {
identifiedFeatures.push(feature.getProperties());
});

this._model.syncIdentifiedFeatures(
identifiedFeatures,
this._mainViewModel.id
);
});

this._Map.addInteraction(selectInteraction);

const view = this._Map.getView();

// TODO: Note for the future, will need to update listeners if view changes
Expand Down Expand Up @@ -276,6 +324,8 @@ export class MainView extends React.Component<IProps, IStates> {
});
});

this._Map.on('click', this._identifyFeature.bind(this));

this._Map
.getViewport()
.addEventListener('pointermove', this._onPointerMove.bind(this));
Expand Down Expand Up @@ -1349,6 +1399,52 @@ export class MainView extends React.Component<IProps, IStates> {
this._model.syncPointer(pointer);
});

private _identifyFeature(e: MapBrowserEvent<any>) {
if (!this._model.isIdentifying) {
return;
}

const localState = this._model?.sharedModel.awareness.getLocalState();
const selectedLayer = localState?.selected?.value;

if (!selectedLayer) {
console.warn('Layer must be selected to use identify tool');
return;
}

const layerId = Object.keys(selectedLayer)[0];
const jgisLayer = this._model.getLayer(layerId);

switch (jgisLayer?.type) {
case 'WebGlLayer': {
const layer = this.getLayer(layerId) as WebGlTileLayer;
const data = layer.getData(e.pixel);

// TODO: Handle dataviews?
if (!data || data instanceof DataView) {
return;
}

const bandValues: IDict<number> = {};

// Data is an array of band values
for (let i = 0; i < data.length - 1; i++) {
bandValues[`Band ${i + 1}`] = data[i];
}

// last element is alpha
bandValues['Alpha'] = data[data.length - 1];

this._model.syncIdentifiedFeatures(
[bandValues],
this._mainViewModel.id
);

break;
}
}
}

private _handleThemeChange = (): void => {
const lightTheme = isLightTheme();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
GeoJSONFeature1,
IDict,
IJGISFilterItem,
IJGISFormSchemaRegistry,
IJupyterGISModel,
IJupyterGISTracker
} from '@jupytergis/schema';
Expand Down Expand Up @@ -44,7 +43,6 @@ export namespace FilterPanel {
export interface IOptions {
model: IControlPanelModel;
tracker: IJupyterGISTracker;
formSchemaRegistry: IJGISFormSchemaRegistry;
}
}

Expand Down
Loading