Skip to content

Commit 13ecfc0

Browse files
Allow adding markers (#965)
* Add add landmark option * Use a type for modes * Use const for identify cursor class * Rename tool * Implement add landmark option * Rename landmark to marker * Add Marker Source schema and new layer/source handling * Temp icon * Dont set style when creating marker * Tidy up * Create generic mode toggle method * Use a marker icon * Add marker source to sourceType * Update Playwright Snapshots * Add data to filter button for tests --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent be605c1 commit 13ecfc0

File tree

20 files changed

+162
-22
lines changed

20 files changed

+162
-22
lines changed

packages/base/src/commands/BaseCommandIDs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
*
33
* See the documentation for more details.
44
*/
5+
// Toolbar
56
export const createNew = 'jupytergis:create-new-jGIS-file';
67
export const redo = 'jupytergis:redo';
78
export const undo = 'jupytergis:undo';
89
export const symbology = 'jupytergis:symbology';
910
export const identify = 'jupytergis:identify';
1011
export const temporalController = 'jupytergis:temporalController';
12+
export const addMarker = 'jupytergis:addMarker';
1113

1214
// geolocation
1315
export const getGeolocation = 'jupytergis:getGeolocation';

packages/base/src/commands/index.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools';
3333
import { JupyterGISTracker } from '../types';
3434
import { JupyterGISDocumentWidget } from '../widget';
3535

36+
const POINT_SELECTION_TOOL_CLASS = 'jGIS-point-selection-tool';
37+
3638
interface ICreateEntry {
3739
tracker: JupyterGISTracker;
3840
formSchemaRegistry: IJGISFormSchemaRegistry;
@@ -163,7 +165,7 @@ export function addCommands(
163165

164166
if (current.model.currentMode === 'identifying' && !canIdentify) {
165167
current.model.currentMode = 'panning';
166-
current.node.classList.remove('jGIS-identify-tool');
168+
current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
167169
return false;
168170
}
169171

@@ -198,14 +200,14 @@ export function addCommands(
198200
const keysPressed = luminoEvent.keys as string[] | undefined;
199201
if (keysPressed?.includes('Escape')) {
200202
current.model.currentMode = 'panning';
201-
current.node.classList.remove('jGIS-identify-tool');
203+
current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
202204
commands.notifyCommandChanged(CommandIDs.identify);
203205
return;
204206
}
205207
}
206208

207-
current.node.classList.toggle('jGIS-identify-tool');
208-
current.model.toggleIdentify();
209+
current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
210+
current.model.toggleMode('identifying');
209211

210212
commands.notifyCommandChanged(CommandIDs.identify);
211213
},
@@ -1039,6 +1041,34 @@ export function addCommands(
10391041
},
10401042
});
10411043

1044+
commands.addCommand(CommandIDs.addMarker, {
1045+
label: trans.__('Add Marker'),
1046+
isToggled: () => {
1047+
const current = tracker.currentWidget;
1048+
if (!current) {
1049+
return false;
1050+
}
1051+
1052+
return current.model.currentMode === 'marking';
1053+
},
1054+
isEnabled: () => {
1055+
// TODO should check if at least one layer exists?
1056+
return true;
1057+
},
1058+
execute: args => {
1059+
const current = tracker.currentWidget;
1060+
if (!current) {
1061+
return;
1062+
}
1063+
1064+
current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
1065+
current.model.toggleMode('marking');
1066+
1067+
commands.notifyCommandChanged(CommandIDs.addMarker);
1068+
},
1069+
...icons.get(CommandIDs.addMarker),
1070+
});
1071+
10421072
loadKeybindings(commands, keybindings);
10431073
}
10441074

packages/base/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
moundIcon,
1111
rasterIcon,
1212
vectorSquareIcon,
13+
markerIcon,
1314
} from './icons';
1415

1516
/**
@@ -56,6 +57,7 @@ const iconObject = {
5657
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
5758
[CommandIDs.identify]: { icon: infoIcon },
5859
[CommandIDs.temporalController]: { icon: clockIcon },
60+
[CommandIDs.addMarker]: { icon: markerIcon },
5961
};
6062

6163
/**

packages/base/src/icons.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import logoSvgStr from '../style/icons/logo.svg';
1616
import logoMiniSvgStr from '../style/icons/logo_mini.svg';
1717
import logoMiniAlternativeSvgStr from '../style/icons/logo_mini_alternative.svg';
1818
import logoMiniQGZ from '../style/icons/logo_mini_qgz.svg';
19+
import markerSvgStr from '../style/icons/marker.svg';
1920
import moundSvgStr from '../style/icons/mound.svg';
2021
import nonVisibilitySvgStr from '../style/icons/nonvisibility.svg';
2122
import rasterSvgStr from '../style/icons/raster.svg';
@@ -109,3 +110,8 @@ export const targetWithCenterIcon = new LabIcon({
109110
name: 'jupytergis::targetWithoutCenter',
110111
svgstr: targetWithoutCenterSvgStr,
111112
});
113+
114+
export const markerIcon = new LabIcon({
115+
name: 'jupytergis::marker',
116+
svgstr: markerSvgStr,
117+
});

packages/base/src/mainview/mainView.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
IWebGlLayer,
3333
JgisCoordinates,
3434
JupyterGISModel,
35+
IMarkerSource,
3536
} from '@jupytergis/schema';
3637
import { showErrorMessage } from '@jupyterlab/apputils';
3738
import { IObservableMap, ObservableMap } from '@jupyterlab/observables';
@@ -411,6 +412,7 @@ export class MainView extends React.Component<IProps, IStates> {
411412
});
412413

413414
this._Map.on('click', this._identifyFeature.bind(this));
415+
this._Map.on('click', this._addMarker.bind(this));
414416

415417
this._Map
416418
.getViewport()
@@ -825,6 +827,20 @@ export class MainView extends React.Component<IProps, IStates> {
825827
});
826828
break;
827829
}
830+
831+
case 'MarkerSource': {
832+
const parameters = source.parameters as IMarkerSource;
833+
834+
const point = new Point(parameters.feature.coords);
835+
const marker = new Feature({
836+
type: 'icon',
837+
geometry: point,
838+
});
839+
840+
newSource = new VectorSource({
841+
features: [marker],
842+
});
843+
}
828844
}
829845

830846
newSource.set('id', id);
@@ -2084,6 +2100,45 @@ export class MainView extends React.Component<IProps, IStates> {
20842100
this._model.syncPointer(pointer);
20852101
});
20862102

2103+
private _addMarker(e: MapBrowserEvent<any>) {
2104+
if (this._model.currentMode !== 'marking') {
2105+
return;
2106+
}
2107+
2108+
const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
2109+
const sourceId = UUID.uuid4();
2110+
const layerId = UUID.uuid4();
2111+
2112+
const sourceParameters: IMarkerSource = {
2113+
feature: { coords: [coordinate[0], coordinate[1]] },
2114+
};
2115+
2116+
const layerParams: IVectorLayer = {
2117+
opacity: 1.0,
2118+
source: sourceId,
2119+
symbologyState: { renderType: 'Single Symbol' },
2120+
};
2121+
2122+
const sourceModel: IJGISSource = {
2123+
type: 'MarkerSource',
2124+
name: 'Marker',
2125+
parameters: sourceParameters,
2126+
};
2127+
2128+
const layerModel: IJGISLayer = {
2129+
type: 'VectorLayer',
2130+
visible: true,
2131+
name: 'Marker',
2132+
parameters: layerParams,
2133+
};
2134+
2135+
this.addSource(sourceId, sourceModel);
2136+
this._model.sharedModel.addSource(sourceId, sourceModel);
2137+
2138+
this.addLayer(layerId, layerModel, this.getLayerIDs().length);
2139+
this._model.addLayer(layerId, layerModel);
2140+
}
2141+
20872142
private _identifyFeature(e: MapBrowserEvent<any>) {
20882143
if (this._model.currentMode !== 'identifying') {
20892144
return;

packages/base/src/panelview/components/filter-panel/Filter.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ const FilterComponent: React.FC<IFilterComponentProps> = ({ model }) => {
237237
<Button
238238
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
239239
onClick={addFilterRow}
240+
data-testid="add-filter-button"
240241
>
241242
Add
242243
</Button>

packages/base/src/toolbar/widget.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ export class ToolbarWidget extends ReactiveToolbar {
170170
temporalControllerButton.node.dataset.testid =
171171
'temporal-controller-button';
172172

173+
const addMarkerButton = new CommandToolbarButton({
174+
id: CommandIDs.addMarker,
175+
label: '',
176+
commands: options.commands,
177+
});
178+
this.addItem('addMarker', addMarkerButton);
179+
addMarkerButton.node.dataset.testid = 'add-marker-controller-button';
180+
173181
this.addItem('spacer', ReactiveToolbar.createSpacerItem());
174182

175183
// Users
Lines changed: 4 additions & 0 deletions
Loading

packages/schema/src/interfaces.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
SourceType,
3030
} from './_interface/project/jgis';
3131
import { IRasterSource } from './_interface/project/sources/rasterSource';
32+
import { Modes } from './types';
3233
export { IGeoJSONSource } from './_interface/project/sources/geoJsonSource';
3334

3435
export type JgisCoordinates = { x: number; y: number };
@@ -161,10 +162,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
161162
geolocation: JgisCoordinates;
162163
localState: IJupyterGISClientState | null;
163164
annotationModel?: IAnnotationModel;
164-
165-
// TODO Add more modes: "annotating"
166-
currentMode: 'panning' | 'identifying';
167-
165+
currentMode: Modes;
168166
themeChanged: Signal<
169167
IJupyterGISModel,
170168
IChangedArgs<string, string | null, string>
@@ -246,7 +244,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
246244
removeMetadata(key: string): void;
247245
centerOnPosition(id: string): void;
248246

249-
toggleIdentify(): void;
247+
toggleMode(mode: Modes): void;
250248

251249
isTemporalControllerActive: boolean;
252250
toggleTemporalController(): void;

packages/schema/src/model.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
IJupyterGISSettings,
3838
} from './interfaces';
3939
import jgisSchema from './schema/project/jgis.json';
40+
import { Modes } from './types';
4041

4142
const SETTINGS_ID = '@jupytergis/jupytergis-core:jupytergis-settings';
4243

@@ -758,19 +759,20 @@ export class JupyterGISModel implements IJupyterGISModel {
758759
}
759760
}
760761

761-
toggleIdentify() {
762-
if (this._currentMode === 'identifying') {
763-
this._currentMode = 'panning';
764-
} else {
765-
this._currentMode = 'identifying';
766-
}
762+
/**
763+
* Toggle a map interaction mode on or off.
764+
* Toggleing off sets the mode to 'panning'.
765+
* @param mode The mode to be toggled
766+
*/
767+
toggleMode(mode: Modes) {
768+
this._currentMode = this._currentMode === mode ? 'panning' : mode;
767769
}
768770

769-
get currentMode(): 'panning' | 'identifying' {
771+
get currentMode(): Modes {
770772
return this._currentMode;
771773
}
772774

773-
set currentMode(value: 'panning' | 'identifying') {
775+
set currentMode(value: Modes) {
774776
this._currentMode = value;
775777
}
776778

@@ -869,7 +871,7 @@ export class JupyterGISModel implements IJupyterGISModel {
869871
private _settingsChanged: Signal<JupyterGISModel, string>;
870872
private _jgisSettings: IJupyterGISSettings;
871873

872-
private _currentMode: 'panning' | 'identifying';
874+
private _currentMode: Modes;
873875

874876
private _sharedModel: IJupyterGISDoc;
875877
private _filePath: string;

0 commit comments

Comments
 (0)