Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions packages/base/src/commands/BaseCommandIDs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
*
* See the documentation for more details.
*/
// Toolbar
export const createNew = 'jupytergis:create-new-jGIS-file';
export const redo = 'jupytergis:redo';
export const undo = 'jupytergis:undo';
export const symbology = 'jupytergis:symbology';
export const identify = 'jupytergis:identify';
export const temporalController = 'jupytergis:temporalController';
export const addMarker = 'jupytergis:addMarker';

// geolocation
export const getGeolocation = 'jupytergis:getGeolocation';
Expand Down
38 changes: 34 additions & 4 deletions packages/base/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools';
import { JupyterGISTracker } from '../types';
import { JupyterGISDocumentWidget } from '../widget';

const POINT_SELECTION_TOOL_CLASS = 'jGIS-point-selection-tool';

interface ICreateEntry {
tracker: JupyterGISTracker;
formSchemaRegistry: IJGISFormSchemaRegistry;
Expand Down Expand Up @@ -163,7 +165,7 @@ export function addCommands(

if (current.model.currentMode === 'identifying' && !canIdentify) {
current.model.currentMode = 'panning';
current.node.classList.remove('jGIS-identify-tool');
current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
return false;
}

Expand Down Expand Up @@ -198,14 +200,14 @@ export function addCommands(
const keysPressed = luminoEvent.keys as string[] | undefined;
if (keysPressed?.includes('Escape')) {
current.model.currentMode = 'panning';
current.node.classList.remove('jGIS-identify-tool');
current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
commands.notifyCommandChanged(CommandIDs.identify);
return;
}
}

current.node.classList.toggle('jGIS-identify-tool');
current.model.toggleIdentify();
current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
current.model.toggleMode('identifying');

commands.notifyCommandChanged(CommandIDs.identify);
},
Expand Down Expand Up @@ -1039,6 +1041,34 @@ export function addCommands(
},
});

commands.addCommand(CommandIDs.addMarker, {
label: trans.__('Add Marker'),
isToggled: () => {
const current = tracker.currentWidget;
if (!current) {
return false;
}

return current.model.currentMode === 'marking';
},
isEnabled: () => {
// TODO should check if at least one layer exists?
return true;
},
execute: args => {
const current = tracker.currentWidget;
if (!current) {
return;
}

current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
current.model.toggleMode('marking');

commands.notifyCommandChanged(CommandIDs.addMarker);
},
...icons.get(CommandIDs.addMarker),
});

loadKeybindings(commands, keybindings);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/base/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
moundIcon,
rasterIcon,
vectorSquareIcon,
markerIcon,
} from './icons';

/**
Expand Down Expand Up @@ -56,6 +57,7 @@ const iconObject = {
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
[CommandIDs.identify]: { icon: infoIcon },
[CommandIDs.temporalController]: { icon: clockIcon },
[CommandIDs.addMarker]: { icon: markerIcon },
};

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/base/src/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import logoSvgStr from '../style/icons/logo.svg';
import logoMiniSvgStr from '../style/icons/logo_mini.svg';
import logoMiniAlternativeSvgStr from '../style/icons/logo_mini_alternative.svg';
import logoMiniQGZ from '../style/icons/logo_mini_qgz.svg';
import markerSvgStr from '../style/icons/marker.svg';
import moundSvgStr from '../style/icons/mound.svg';
import nonVisibilitySvgStr from '../style/icons/nonvisibility.svg';
import rasterSvgStr from '../style/icons/raster.svg';
Expand Down Expand Up @@ -109,3 +110,8 @@ export const targetWithCenterIcon = new LabIcon({
name: 'jupytergis::targetWithoutCenter',
svgstr: targetWithoutCenterSvgStr,
});

export const markerIcon = new LabIcon({
name: 'jupytergis::marker',
svgstr: markerSvgStr,
});
55 changes: 55 additions & 0 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
IWebGlLayer,
JgisCoordinates,
JupyterGISModel,
IMarkerSource,
} from '@jupytergis/schema';
import { showErrorMessage } from '@jupyterlab/apputils';
import { IObservableMap, ObservableMap } from '@jupyterlab/observables';
Expand Down Expand Up @@ -411,6 +412,7 @@ export class MainView extends React.Component<IProps, IStates> {
});

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

this._Map
.getViewport()
Expand Down Expand Up @@ -825,6 +827,20 @@ export class MainView extends React.Component<IProps, IStates> {
});
break;
}

case 'MarkerSource': {
const parameters = source.parameters as IMarkerSource;

const point = new Point(parameters.feature.coords);
const marker = new Feature({
type: 'icon',
geometry: point,
});

newSource = new VectorSource({
features: [marker],
});
}
}

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

private _addMarker(e: MapBrowserEvent<any>) {
if (this._model.currentMode !== 'marking') {
return;
}

const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
const sourceId = UUID.uuid4();
const layerId = UUID.uuid4();

const sourceParameters: IMarkerSource = {
feature: { coords: [coordinate[0], coordinate[1]] },
};

const layerParams: IVectorLayer = {
opacity: 1.0,
source: sourceId,
symbologyState: { renderType: 'Single Symbol' },
};

const sourceModel: IJGISSource = {
type: 'MarkerSource',
name: 'Marker',
parameters: sourceParameters,
};

const layerModel: IJGISLayer = {
type: 'VectorLayer',
visible: true,
name: 'Marker',
parameters: layerParams,
};

this.addSource(sourceId, sourceModel);
this._model.sharedModel.addSource(sourceId, sourceModel);

this.addLayer(layerId, layerModel, this.getLayerIDs().length);
this._model.addLayer(layerId, layerModel);
}

private _identifyFeature(e: MapBrowserEvent<any>) {
if (this._model.currentMode !== 'identifying') {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ const FilterComponent: React.FC<IFilterComponentProps> = ({ model }) => {
<Button
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
onClick={addFilterRow}
data-testid="add-filter-button"
>
Add
</Button>
Expand Down
8 changes: 8 additions & 0 deletions packages/base/src/toolbar/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ export class ToolbarWidget extends ReactiveToolbar {
temporalControllerButton.node.dataset.testid =
'temporal-controller-button';

const addMarkerButton = new CommandToolbarButton({
id: CommandIDs.addMarker,
label: '',
commands: options.commands,
});
this.addItem('addMarker', addMarkerButton);
addMarkerButton.node.dataset.testid = 'add-marker-controller-button';

this.addItem('spacer', ReactiveToolbar.createSpacerItem());

// Users
Expand Down
4 changes: 4 additions & 0 deletions packages/base/style/icons/marker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 3 additions & 5 deletions packages/schema/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SourceType,
} from './_interface/project/jgis';
import { IRasterSource } from './_interface/project/sources/rasterSource';
import { Modes } from './types';
export { IGeoJSONSource } from './_interface/project/sources/geoJsonSource';

export type JgisCoordinates = { x: number; y: number };
Expand Down Expand Up @@ -161,10 +162,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
geolocation: JgisCoordinates;
localState: IJupyterGISClientState | null;
annotationModel?: IAnnotationModel;

// TODO Add more modes: "annotating"
currentMode: 'panning' | 'identifying';

currentMode: Modes;
themeChanged: Signal<
IJupyterGISModel,
IChangedArgs<string, string | null, string>
Expand Down Expand Up @@ -246,7 +244,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
removeMetadata(key: string): void;
centerOnPosition(id: string): void;

toggleIdentify(): void;
toggleMode(mode: Modes): void;

isTemporalControllerActive: boolean;
toggleTemporalController(): void;
Expand Down
20 changes: 11 additions & 9 deletions packages/schema/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
IJupyterGISSettings,
} from './interfaces';
import jgisSchema from './schema/project/jgis.json';
import { Modes } from './types';

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

Expand Down Expand Up @@ -758,19 +759,20 @@ export class JupyterGISModel implements IJupyterGISModel {
}
}

toggleIdentify() {
if (this._currentMode === 'identifying') {
this._currentMode = 'panning';
} else {
this._currentMode = 'identifying';
}
/**
* Toggle a map interaction mode on or off.
* Toggleing off sets the mode to 'panning'.
* @param mode The mode to be toggled
*/
toggleMode(mode: Modes) {
this._currentMode = this._currentMode === mode ? 'panning' : mode;
}

get currentMode(): 'panning' | 'identifying' {
get currentMode(): Modes {
return this._currentMode;
}

set currentMode(value: 'panning' | 'identifying') {
set currentMode(value: Modes) {
this._currentMode = value;
}

Expand Down Expand Up @@ -869,7 +871,7 @@ export class JupyterGISModel implements IJupyterGISModel {
private _settingsChanged: Signal<JupyterGISModel, string>;
private _jgisSettings: IJupyterGISSettings;

private _currentMode: 'panning' | 'identifying';
private _currentMode: Modes;

private _sharedModel: IJupyterGISDoc;
private _filePath: string;
Expand Down
3 changes: 2 additions & 1 deletion packages/schema/src/schema/project/jgis.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"ImageSource",
"ShapefileSource",
"GeoTiffSource",
"GeoParquetSource"
"GeoParquetSource",
"MarkerSource"
]
},
"jGISLayer": {
Expand Down
24 changes: 24 additions & 0 deletions packages/schema/src/schema/project/sources/markerSource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"type": "object",
"description": "MarkerSource",
"title": "IMarkerSource",
"required": ["feature"],
"additionalProperties": false,
"properties": {
"feature": {
"type": "object",
"description": "Info for the marker",
"required": ["coords"],
"properties": {
"coords": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number"
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions packages/schema/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './_interface/project/sources/shapefileSource';
export * from './_interface/project/sources/vectorTileSource';
export * from './_interface/project/sources/videoSource';
export * from './_interface/project/sources/geoParquetSource';
export * from './_interface/project/sources/markerSource';

// Layers
export * from './_interface/project/layers/heatmapLayer';
Expand All @@ -34,3 +35,5 @@ export * from './index';
export * from './interfaces';
export * from './model';
export * from './token';

export type Modes = 'panning' | 'identifying' | 'marking';
1 change: 1 addition & 0 deletions python/jupytergis_core/jupytergis_core/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .interfaces.project.layers.heatmapLayer import IHeatmapLayer # noqa

from .interfaces.project.sources.vectorTileSource import IVectorTileSource # noqa
from .interfaces.project.sources.markerSource import IMarkerSource # noqa
from .interfaces.project.sources.rasterSource import IRasterSource # noqa
from .interfaces.project.sources.geoJsonSource import IGeoJSONSource # noqa
from .interfaces.project.sources.videoSource import IVideoSource # noqa
Expand Down
3 changes: 3 additions & 0 deletions python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
IVectorLayer,
IVectorTileLayer,
IVectorTileSource,
IMarkerSource,
IVideoSource,
IWebGlLayer,
LayerType,
Expand Down Expand Up @@ -880,6 +881,7 @@ class Config:
parameters: (
IRasterSource
| IVectorTileSource
| IMarkerSource
| IGeoJSONSource
| IImageSource
| IVideoSource
Expand Down Expand Up @@ -967,6 +969,7 @@ def create_source(
OBJECT_FACTORY.register_factory(LayerType.HeatmapLayer, IHeatmapLayer)

OBJECT_FACTORY.register_factory(SourceType.VectorTileSource, IVectorTileSource)
OBJECT_FACTORY.register_factory(SourceType.MarkerSource, IMarkerSource)
OBJECT_FACTORY.register_factory(SourceType.RasterSource, IRasterSource)
OBJECT_FACTORY.register_factory(SourceType.GeoJSONSource, IGeoJSONSource)
OBJECT_FACTORY.register_factory(SourceType.ImageSource, IImageSource)
Expand Down
2 changes: 1 addition & 1 deletion python/jupytergis_lab/style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ div.jGIS-toolbar-widget > div.jp-Toolbar-item:last-child {
fill: var(--jp-inverse-layout-color3);
}

.jGIS-identify-tool {
.jGIS-point-selection-tool {
cursor: crosshair;
}

Expand Down
Loading
Loading