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

Add geoJSON source and layer #30

Merged
merged 22 commits into from
Jul 11, 2024
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
5,962 changes: 5,962 additions & 0 deletions examples/france_regions.json

Large diffs are not rendered by default.

62 changes: 32 additions & 30 deletions examples/test.jGIS
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
{
"layers": {
"a0044fd7-f167-445f-b3d1-620a8f94b498": {
"name": "Open Topo Map",
"type": "RasterLayer",
"57ef55ef-facb-48a2-ae1d-c9c824be3e8a": {
"parameters": {
"source": "5fd42e3b-4681-4607-b15d-65c3a3e89b32"
"source": "7d082e75-69d5-447a-82d8-b05cca5945ba",
"type": "line",
"color": "#e66100",
"opacity": 0.6
},
"visible": true
"name": "Regions France",
"visible": true,
"type": "VectorLayer"
},
"a5ac7671-74bb-4c99-a494-916348397d01": {
"name": "Open Street Map 2",
"parameters": {
"source": "f22850a8-bfd5-4dfb-ba1e-c3f2c3ccf93b"
},
"a0044fd7-f167-445f-b3d1-620a8f94b498": {
"type": "RasterLayer",
"visible": true
"visible": true,
"name": "Open Topo Map",
"parameters": {
"source": "5fd42e3b-4681-4607-b15d-65c3a3e89b32"
}
},
"2467576f-b527-4cb7-998d-fa1d056fb8a1": {
"parameters": {
"source": "699facc9-e7c4-4f38-acf1-1fd7f02d9f36"
},
"visible": true,
"type": "RasterLayer",
"name": "Open Street Map",
"type": "RasterLayer"
"visible": true
}
},
"sources": {
"f22850a8-bfd5-4dfb-ba1e-c3f2c3ccf93b": {
"5fd42e3b-4681-4607-b15d-65c3a3e89b32": {
"name": "Open Topo Map",
"parameters": {
"maxZoom": 24.0,
"minZoom": 0.0,
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
"url": "https://tile.opentopomap.org/{z}/{x}/{y}.png ",
"maxZoom": 24.0
},
"type": "RasterSource",
"name": "Open Street Map 2"
"type": "RasterSource"
},
"5fd42e3b-4681-4607-b15d-65c3a3e89b32": {
"7d082e75-69d5-447a-82d8-b05cca5945ba": {
"name": "france_regions",
"parameters": {
"url": "https://tile.opentopomap.org/{z}/{x}/{y}.png ",
"maxZoom": 24.0,
"minZoom": 0.0
"path": "examples/france_regions.json",
"valid": true
},
"name": "Open Topo Map",
"type": "RasterSource"
"type": "GeoJSONSource"
},
"699facc9-e7c4-4f38-acf1-1fd7f02d9f36": {
"type": "RasterSource",
Expand All @@ -60,18 +62,18 @@
"zoom": 0
},
"layerTree": [
"2467576f-b527-4cb7-998d-fa1d056fb8a1",
"a0044fd7-f167-445f-b3d1-620a8f94b498",
{
"name": "level 1 group",
"layers": [
"a0044fd7-f167-445f-b3d1-620a8f94b498",
"2467576f-b527-4cb7-998d-fa1d056fb8a1",
{
"name": "level 2 group",
"layers": [
"a5ac7671-74bb-4c99-a494-916348397d01"
"57ef55ef-facb-48a2-ae1d-c9c824be3e8a"
]
}
],
"name": "level 1 group"
]
}
]
}
}
2 changes: 2 additions & 0 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"@rjsf/core": "^4.2.0",
"@types/d3-color": "^3.1.0",
"@types/three": "^0.134.0",
"ajv": "^8.14.0",
"d3-color": "^3.1.0",
"geojson-schema": "^1.0.5",
"maplibre-gl": "^4.4.1",
"react": "^18.0.1",
"styled-components": "^5.3.6",
Expand Down
237 changes: 225 additions & 12 deletions packages/base/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import { WidgetTracker } from '@jupyterlab/apputils';
import { Dialog, WidgetTracker, showErrorMessage } from '@jupyterlab/apputils';
import { PathExt } from '@jupyterlab/coreutils';
import { ITranslator } from '@jupyterlab/translation';
import { redoIcon, undoIcon } from '@jupyterlab/ui-components';

import {
IDict,
IGeoJSONSource,
IJGISFormSchemaRegistry,
IJGISLayerBrowserRegistry
IJGISLayer,
IJGISLayerBrowserRegistry,
IJGISSource,
IJupyterGISModel
} from '@jupytergis/schema';
import { UUID } from '@lumino/coreutils';
import { Ajv } from 'ajv';
import * as geojson from 'geojson-schema/GeoJSON.json';

import { LayerBrowserWidget } from './layerBrowser/layerBrowserDialog';
import {
DataErrorDialog,
DialogAddDataSourceBody,
FormDialog
} from './formdialog';
import { geoJSONIcon } from './icons';
import { LayerBrowserWidget } from './dialogs/layerBrowserDialog';
import { GeoJSONLayerDialog } from './dialogs/geoJsonLayerDialog';
import { JupyterGISWidget } from './widget';

/**
* The command IDs.
*/
export namespace CommandIDs {
export const redo = 'jupytergis:redo';
export const undo = 'jupytergis:undo';

export const openLayerBrowser = 'jupytergis:openLayerBrowser';

export const newGeoJSONLayer = 'jupytergis:newGeoJSONLayer';
export const newGeoJSONSource = 'jupytergis:newGeoJSONSource';
export const newVectorLayer = 'jupytergis:newVectorLayer';
}

/**
* Add the commands to the application's command registry.
*/
Expand Down Expand Up @@ -73,16 +102,39 @@ export function addCommands(
formSchemaRegistry
)
});
}

/**
* The command IDs.
*/
export namespace CommandIDs {
export const redo = 'jupytergis:redo';
export const undo = 'jupytergis:undo';
commands.addCommand(CommandIDs.newGeoJSONLayer, {
label: trans.__('New vector layer'),
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
iconClass: 'fa fa-vector-square',
execute: Private.createGeoJSONLayer(tracker, formSchemaRegistry)
});

export const openLayerBrowser = 'jupytergis:openLayerBrowser';
commands.addCommand(CommandIDs.newVectorLayer, {
label: trans.__('New vector layer'),
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
iconClass: 'fa fa-vector-square',
execute: Private.createVectorLayer(tracker)
});

commands.addCommand(CommandIDs.newGeoJSONSource, {
label: trans.__('Add GeoJSON data from file'),
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.context.model.sharedModel.editable
: false;
},
icon: geoJSONIcon,
execute: Private.createGeoJSONSource(tracker)
});
}

namespace Private {
Expand Down Expand Up @@ -125,4 +177,165 @@ namespace Private {
await dialog.launch();
};
}

/**
* Command to create a GeoJSON source and vector layer.
*/
export function createGeoJSONLayer(
tracker: WidgetTracker<JupyterGISWidget>,
formSchemaRegistry: IJGISFormSchemaRegistry
) {
return async () => {
const current = tracker.currentWidget;

if (!current) {
return;
}

const dialog = new GeoJSONLayerDialog({
model: current.context.model,
registry: formSchemaRegistry
});
await dialog.launch();
};
}

/**
* Command to create a GeoJSON source.
*
* This is currently not used.
*/
export function createGeoJSONSource(
tracker: WidgetTracker<JupyterGISWidget>
) {
const ajv = new Ajv();
const validate = ajv.compile(geojson);

return async (args: any) => {
const current = tracker.currentWidget;

if (!current) {
return;
}

let filepath: string | null = (args.path as string) ?? null;
let saveDataInShared: boolean = args.path ?? false;

if (filepath === null) {
const dialog = new Dialog({
title: 'Path of the GeoJSON file',
body: new DialogAddDataSourceBody()
});
const value = (await dialog.launch()).value;
if (value) {
filepath = value.path;
saveDataInShared = value.saveDataInShared;
}
}

if (!filepath) {
return;
}

current.context.model
.readGeoJSON(filepath)
.then(async geoJSONData => {
const name = PathExt.basename(filepath, '.json');
const valid = validate(geoJSONData);
if (!valid) {
const dialog = new DataErrorDialog({
title: 'GeoJSON data invalid',
errors: validate.errors,
saveDataInShared
});
const toContinue = await dialog.launch();
if (!toContinue.button.accept || saveDataInShared) {
return;
}
}

const parameters: IGeoJSONSource = {};
if (saveDataInShared) {
parameters.data = geoJSONData;
} else {
(parameters.path = filepath), (parameters.valid = valid);
}

const sourceModel: IJGISSource = {
type: 'GeoJSONSource',
name,
parameters
};

current.context.model.sharedModel.addSource(
UUID.uuid4(),
sourceModel
);
})
.catch(e => {
showErrorMessage('Error opening GeoJSON file', e);
return;
});
};
}

/**
* Command to create a Vector layer.
*
* This is currently not used.
*/
export function createVectorLayer(tracker: WidgetTracker<JupyterGISWidget>) {
return async (arg: any) => {
const current = tracker.currentWidget;

if (!current) {
return;
}

const sources = current.context.model.getSourcesByType('GeoJSONSource');

const form = {
title: 'Vector Layer parameters',
default: (model: IJupyterGISModel) => {
return {
name: 'VectorSource',
source: Object.keys(sources)[0] ?? null
};
}
};

FORM_SCHEMA['VectorLayer'].properties.source.enumNames =
Object.values(sources);
FORM_SCHEMA['VectorLayer'].properties.source.enum = Object.keys(sources);
const dialog = new FormDialog({
context: current.context,
title: form.title,
sourceData: form.default(current.context.model),
schema: FORM_SCHEMA['VectorLayer'],
syncData: (props: IDict) => {
const sharedModel = current.context.model.sharedModel;
if (!sharedModel) {
return;
}

const { name, ...parameters } = props;

const layerModel: IJGISLayer = {
type: 'VectorLayer',
parameters: {
source: parameters.source,
type: parameters.type,
color: parameters.color,
opacity: parameters.opacity
},
visible: true,
name: name + ' Layer'
};

current.context.model.addLayer(UUID.uuid4(), layerModel);
}
});
await dialog.launch();
};
}
}
Loading
Loading