@jupytergis/schema 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,28 @@
1
- import { ICollaborativeDrive } from '@jupyter/docprovider';
2
1
  import { Delta, DocumentChange, MapChange, StateChange, YDocument } from '@jupyter/ydoc';
3
2
  import { IWidgetTracker } from '@jupyterlab/apputils';
4
3
  import { IChangedArgs } from '@jupyterlab/coreutils';
5
4
  import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
6
- import { User } from '@jupyterlab/services';
5
+ import { Contents, User } from '@jupyterlab/services';
7
6
  import { ISignal, Signal } from '@lumino/signaling';
8
7
  import { SplitPanel } from '@lumino/widgets';
9
8
  import { GeoJSON } from './_interface/geojsonsource';
10
9
  import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerItem, IJGISLayers, IJGISLayerTree, IJGISOptions, IJGISSource, IJGISSources, SourceType } from './_interface/jgis';
11
10
  import { IRasterSource } from './_interface/rastersource';
12
11
  export { IGeoJSONSource } from './_interface/geojsonsource';
12
+ export type JgisCoordinates = {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ export interface IViewPortState {
17
+ coordinates: JgisCoordinates;
18
+ zoom: number;
19
+ }
20
+ export type Pointer = {
21
+ coordinates: {
22
+ x: number;
23
+ y: number;
24
+ };
25
+ };
13
26
  export interface IDict<T = any> {
14
27
  [key: string]: T;
15
28
  }
@@ -41,6 +54,18 @@ export interface IJupyterGISClientState {
41
54
  };
42
55
  emitter?: string | null;
43
56
  };
57
+ viewportState: {
58
+ value?: IViewPortState;
59
+ emitter?: string | null;
60
+ };
61
+ pointer: {
62
+ value?: Pointer;
63
+ emitter?: string | null;
64
+ };
65
+ identifiedFeatures: {
66
+ value?: any;
67
+ emitter?: string | null;
68
+ };
44
69
  user: User.IIdentity;
45
70
  remoteUser?: number;
46
71
  toolbarForm?: IDict;
@@ -50,6 +75,7 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
50
75
  layers: IJGISLayers;
51
76
  sources: IJGISSources;
52
77
  layerTree: IJGISLayerTree;
78
+ metadata: any;
53
79
  readonly editable: boolean;
54
80
  readonly toJGISEndpoint?: string;
55
81
  layerExists(id: string): boolean;
@@ -58,7 +84,7 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
58
84
  addLayer(id: string, value: IJGISLayer, groupName?: string, position?: number): void;
59
85
  updateLayer(id: string, value: IJGISLayer): void;
60
86
  sourceExists(id: string): boolean;
61
- getSource(id: string): IJGISSource | undefined;
87
+ getLayerSource(id: string): IJGISSource | undefined;
62
88
  removeSource(id: string): void;
63
89
  addSource(id: string, value: IJGISSource): void;
64
90
  updateSource(id: string, value: IJGISSource): void;
@@ -68,10 +94,14 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
68
94
  getObject(id: string): IJGISLayer | IJGISSource | undefined;
69
95
  getOption(key: keyof IJGISOptions): IDict | undefined;
70
96
  setOption(key: keyof IJGISOptions, value: IDict): void;
97
+ getMetadata(key: string): string | undefined;
98
+ setMetadata(key: string, value: string): void;
99
+ removeMetadata(key: string): void;
71
100
  optionsChanged: ISignal<IJupyterGISDoc, MapChange>;
72
101
  layersChanged: ISignal<IJupyterGISDoc, IJGISLayerDocChange>;
73
102
  sourcesChanged: ISignal<IJupyterGISDoc, IJGISSourceDocChange>;
74
103
  layerTreeChanged: ISignal<IJupyterGISDoc, IJGISLayerTreeDocChange>;
104
+ metadataChanged: ISignal<IJupyterGISDoc, MapChange>;
75
105
  }
76
106
  export interface IJupyterGISDocChange extends DocumentChange {
77
107
  contextChange?: MapChange;
@@ -89,13 +119,16 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
89
119
  isDisposed: boolean;
90
120
  sharedModel: IJupyterGISDoc;
91
121
  localState: IJupyterGISClientState | null;
122
+ annotationModel?: IAnnotationModel;
92
123
  themeChanged: Signal<IJupyterGISModel, IChangedArgs<string, string | null, string>>;
93
124
  clientStateChanged: ISignal<IJupyterGISModel, Map<number, IJupyterGISClientState>>;
94
125
  sharedOptionsChanged: ISignal<IJupyterGISDoc, MapChange>;
95
126
  sharedLayersChanged: ISignal<IJupyterGISDoc, IJGISLayerDocChange>;
96
127
  sharedLayerTreeChanged: ISignal<IJupyterGISDoc, IJGISLayerTreeDocChange>;
97
128
  sharedSourcesChanged: ISignal<IJupyterGISDoc, IJGISSourceDocChange>;
98
- setDrive(value: ICollaborativeDrive, filePath: string): void;
129
+ sharedMetadataChanged: ISignal<IJupyterGISModel, MapChange>;
130
+ zoomToAnnotationSignal: ISignal<IJupyterGISModel, string>;
131
+ setContentsManager(value: Contents.IManager | undefined, filePath: string): void;
99
132
  getContent(): IJGISContent;
100
133
  getLayers(): IJGISLayers;
101
134
  getLayer(id: string): IJGISLayer | undefined;
@@ -118,11 +151,19 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
118
151
  addNewLayerGroup(selected: {
119
152
  [key: string]: ISelection;
120
153
  }, group: IJGISLayerGroup): void;
154
+ syncViewport(viewport?: IViewPortState, emitter?: string): void;
121
155
  syncSelected(value: {
122
156
  [key: string]: ISelection;
123
157
  }, emitter?: string): void;
158
+ syncPointer(pointer?: Pointer, emitter?: string): void;
159
+ syncIdentifiedFeatures(features: IDict<any>, emitter?: string): void;
124
160
  setUserToFollow(userId?: number): void;
125
161
  getClientId(): number;
162
+ addMetadata(key: string, value: string): void;
163
+ removeMetadata(key: string): void;
164
+ centerOnAnnotation(id: string): void;
165
+ toggleIdentify(): void;
166
+ isIdentifying: boolean;
126
167
  disposed: ISignal<any, void>;
127
168
  }
128
169
  export interface IUserData {
@@ -183,3 +224,29 @@ export interface IJGISLayerBrowserRegistry {
183
224
  removeRegistryLayer(name: string): void;
184
225
  clearRegistry(): void;
185
226
  }
227
+ export interface IAnnotationModel {
228
+ updateSignal: ISignal<this, null>;
229
+ user: User.IIdentity | undefined;
230
+ context: DocumentRegistry.IContext<IJupyterGISModel> | undefined;
231
+ contextChanged: ISignal<this, void>;
232
+ update(): void;
233
+ getAnnotation(id: string): IAnnotation | undefined;
234
+ getAnnotationIds(): string[];
235
+ addAnnotation(key: string, value: IAnnotation): void;
236
+ removeAnnotation(key: string): void;
237
+ addContent(id: string, value: string): void;
238
+ }
239
+ export interface IAnnotationContent {
240
+ user?: User.IIdentity;
241
+ value: string;
242
+ }
243
+ export interface IAnnotation {
244
+ label: string;
245
+ position: {
246
+ x: number;
247
+ y: number;
248
+ };
249
+ zoom: number;
250
+ contents: IAnnotationContent[];
251
+ parent: string;
252
+ }
package/lib/model.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ICollaborativeDrive } from '@jupyter/docprovider';
2
1
  import { MapChange } from '@jupyter/ydoc';
3
2
  import { IChangedArgs } from '@jupyterlab/coreutils';
4
3
  import { DocumentRegistry } from '@jupyterlab/docregistry';
@@ -6,11 +5,12 @@ import { PartialJSONObject } from '@lumino/coreutils';
6
5
  import { ISignal, Signal } from '@lumino/signaling';
7
6
  import { GeoJSON } from './_interface/geojsonsource';
8
7
  import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerTree, IJGISLayers, IJGISOptions, IJGISSource, IJGISSources } from './_interface/jgis';
9
- import { IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, IJupyterGISClientState, IJupyterGISDoc, IJupyterGISModel, ISelection, IUserData } from './interfaces';
8
+ import { IViewPortState, Pointer, IAnnotationModel, IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, IJupyterGISClientState, IJupyterGISDoc, IJupyterGISModel, ISelection, IUserData, IDict } from './interfaces';
9
+ import { Contents } from '@jupyterlab/services';
10
10
  export declare class JupyterGISModel implements IJupyterGISModel {
11
- constructor(options: DocumentRegistry.IModelOptions<IJupyterGISDoc>);
11
+ constructor(options: JupyterGISModel.IOptions);
12
12
  private _onSharedModelChanged;
13
- readonly collaborative = true;
13
+ readonly collaborative: boolean;
14
14
  get sharedModel(): IJupyterGISDoc;
15
15
  get isDisposed(): boolean;
16
16
  get contentChanged(): ISignal<this, void>;
@@ -30,6 +30,14 @@ export declare class JupyterGISModel implements IJupyterGISModel {
30
30
  get sharedLayerTreeChanged(): ISignal<IJupyterGISDoc, IJGISLayerTreeDocChange>;
31
31
  get sharedSourcesChanged(): ISignal<IJupyterGISDoc, IJGISSourceDocChange>;
32
32
  get disposed(): ISignal<JupyterGISModel, void>;
33
+ get sharedMetadataChanged(): ISignal<this, MapChange>;
34
+ get zoomToAnnotationSignal(): ISignal<this, string>;
35
+ set isIdentifying(isIdentifying: boolean);
36
+ get isIdentifying(): boolean;
37
+ centerOnAnnotation(id: string): void;
38
+ private _metadataChangedHandler;
39
+ addMetadata(key: string, value: string): void;
40
+ removeMetadata(key: string): void;
33
41
  dispose(): void;
34
42
  toString(): string;
35
43
  fromString(data: string): void;
@@ -38,7 +46,7 @@ export declare class JupyterGISModel implements IJupyterGISModel {
38
46
  initialize(): void;
39
47
  getWorker(): Worker;
40
48
  getContent(): IJGISContent;
41
- setDrive(value: ICollaborativeDrive, filePath: string): void;
49
+ setContentsManager(value: Contents.IManager | undefined, filePath: string): void;
42
50
  getLayers(): IJGISLayers;
43
51
  getSources(): IJGISSources;
44
52
  getLayerTree(): IJGISLayerTree;
@@ -89,9 +97,12 @@ export declare class JupyterGISModel implements IJupyterGISModel {
89
97
  removeLayer(layer_id: string): void;
90
98
  setOptions(value: IJGISOptions): void;
91
99
  getOptions(): IJGISOptions;
100
+ syncViewport(viewport?: IViewPortState, emitter?: string): void;
101
+ syncPointer(pointer?: Pointer, emitter?: string): void;
92
102
  syncSelected(value: {
93
103
  [key: string]: ISelection;
94
104
  }, emitter?: string): void;
105
+ syncIdentifiedFeatures(features: IDict<any>, emitter?: string): void;
95
106
  setUserToFollow(userId?: number): void;
96
107
  getClientId(): number;
97
108
  /**
@@ -111,15 +122,18 @@ export declare class JupyterGISModel implements IJupyterGISModel {
111
122
  }, group: IJGISLayerGroup): void;
112
123
  private _removeLayerTreeLayer;
113
124
  private _removeLayerTreeGroup;
125
+ private _removeLayerTreeItem;
114
126
  renameLayerGroup(groupName: string, newName: string): void;
115
127
  removeLayerGroup(groupName: string): void;
128
+ toggleIdentify(): void;
116
129
  private _getLayerTreeInfo;
117
130
  private _onClientStateChanged;
118
131
  readonly defaultKernelName: string;
119
132
  readonly defaultKernelLanguage: string;
133
+ readonly annotationModel?: IAnnotationModel;
120
134
  private _sharedModel;
121
135
  private _filePath;
122
- private _drive?;
136
+ private _contentsManager?;
123
137
  private _dirty;
124
138
  private _readOnly;
125
139
  private _isDisposed;
@@ -130,6 +144,9 @@ export declare class JupyterGISModel implements IJupyterGISModel {
130
144
  private _stateChanged;
131
145
  private _themeChanged;
132
146
  private _clientStateChanged;
147
+ private _sharedMetadataChanged;
148
+ private _zoomToAnnotationSignal;
149
+ private _isIdentifying;
133
150
  static worker: Worker;
134
151
  }
135
152
  export declare namespace JupyterGISModel {
@@ -137,4 +154,7 @@ export declare namespace JupyterGISModel {
137
154
  * Function to get the ordered list of layers according to the tree.
138
155
  */
139
156
  function getOrderedLayerIds(model: IJupyterGISModel): string[];
157
+ interface IOptions extends DocumentRegistry.IModelOptions<IJupyterGISDoc> {
158
+ annotationModel?: IAnnotationModel;
159
+ }
140
160
  }
package/lib/model.js CHANGED
@@ -12,15 +12,13 @@ export class JupyterGISModel {
12
12
  this.dirty = true;
13
13
  }
14
14
  };
15
- this.collaborative = true;
15
+ this.collaborative = document.querySelectorAll('[data-jupyter-lite-root]')[0] === undefined;
16
16
  this._onClientStateChanged = (changed) => {
17
17
  const clients = this.sharedModel.awareness.getStates();
18
18
  this._clientStateChanged.emit(clients);
19
- this._sharedModel.awareness.on('change', (update) => {
20
- if (update.added.length || update.removed.length) {
21
- this._userChanged.emit(this.users);
22
- }
23
- });
19
+ if (changed.added.length || changed.removed.length) {
20
+ this._userChanged.emit(this.users);
21
+ }
24
22
  };
25
23
  this.defaultKernelName = '';
26
24
  this.defaultKernelLanguage = '';
@@ -33,7 +31,10 @@ export class JupyterGISModel {
33
31
  this._stateChanged = new Signal(this);
34
32
  this._themeChanged = new Signal(this);
35
33
  this._clientStateChanged = new Signal(this);
36
- const { sharedModel } = options;
34
+ this._sharedMetadataChanged = new Signal(this);
35
+ this._zoomToAnnotationSignal = new Signal(this);
36
+ this._isIdentifying = false;
37
+ const { annotationModel, sharedModel } = options;
37
38
  if (sharedModel) {
38
39
  this._sharedModel = sharedModel;
39
40
  }
@@ -42,6 +43,8 @@ export class JupyterGISModel {
42
43
  this._sharedModel.changed.connect(this._onSharedModelChanged);
43
44
  }
44
45
  this.sharedModel.awareness.on('change', this._onClientStateChanged);
46
+ this._sharedModel.metadataChanged.connect(this._metadataChangedHandler, this);
47
+ this.annotationModel = annotationModel;
45
48
  }
46
49
  get sharedModel() {
47
50
  return this._sharedModel;
@@ -109,6 +112,30 @@ export class JupyterGISModel {
109
112
  get disposed() {
110
113
  return this._disposed;
111
114
  }
115
+ get sharedMetadataChanged() {
116
+ return this._sharedMetadataChanged;
117
+ }
118
+ get zoomToAnnotationSignal() {
119
+ return this._zoomToAnnotationSignal;
120
+ }
121
+ set isIdentifying(isIdentifying) {
122
+ this._isIdentifying = isIdentifying;
123
+ }
124
+ get isIdentifying() {
125
+ return this._isIdentifying;
126
+ }
127
+ centerOnAnnotation(id) {
128
+ this._zoomToAnnotationSignal.emit(id);
129
+ }
130
+ _metadataChangedHandler(_, args) {
131
+ this._sharedMetadataChanged.emit(args);
132
+ }
133
+ addMetadata(key, value) {
134
+ this.sharedModel.setMetadata(key, value);
135
+ }
136
+ removeMetadata(key) {
137
+ this.sharedModel.removeMetadata(key);
138
+ }
112
139
  dispose() {
113
140
  if (this._isDisposed) {
114
141
  return;
@@ -127,14 +154,14 @@ export class JupyterGISModel {
127
154
  const validate = ajv.compile(jgisSchema);
128
155
  const valid = validate(jsonData);
129
156
  if (!valid) {
130
- let errorMsg = 'File format errors:\n';
157
+ let errorMsg = 'JupyterGIS format errors:\n';
131
158
  for (const error of validate.errors || []) {
132
159
  errorMsg = `${errorMsg}- ${error.instancePath} ${error.message}\n`;
133
160
  }
134
- throw Error(errorMsg);
161
+ console.warn(errorMsg);
135
162
  }
136
163
  this.sharedModel.transact(() => {
137
- var _a, _b, _c, _d;
164
+ var _a, _b, _c, _d, _e;
138
165
  this.sharedModel.sources = (_a = jsonData.sources) !== null && _a !== void 0 ? _a : {};
139
166
  this.sharedModel.layers = (_b = jsonData.layers) !== null && _b !== void 0 ? _b : {};
140
167
  this.sharedModel.layerTree = (_c = jsonData.layerTree) !== null && _c !== void 0 ? _c : [];
@@ -146,6 +173,7 @@ export class JupyterGISModel {
146
173
  pitch: 0,
147
174
  projection: 'EPSG:3857'
148
175
  };
176
+ this.sharedModel.metadata = (_e = jsonData.metadata) !== null && _e !== void 0 ? _e : {};
149
177
  });
150
178
  this.dirty = true;
151
179
  }
@@ -166,11 +194,12 @@ export class JupyterGISModel {
166
194
  sources: this.sharedModel.sources,
167
195
  layers: this.sharedModel.layers,
168
196
  layerTree: this.sharedModel.layerTree,
169
- options: this.sharedModel.options
197
+ options: this.sharedModel.options,
198
+ metadata: this.sharedModel.metadata
170
199
  };
171
200
  }
172
- setDrive(value, filePath) {
173
- this._drive = value;
201
+ setContentsManager(value, filePath) {
202
+ this._contentsManager = value;
174
203
  this._filePath = filePath;
175
204
  }
176
205
  getLayers() {
@@ -186,7 +215,7 @@ export class JupyterGISModel {
186
215
  return this.sharedModel.getLayer(id);
187
216
  }
188
217
  getSource(id) {
189
- return this.sharedModel.getSource(id);
218
+ return this.sharedModel.getLayerSource(id);
190
219
  }
191
220
  /**
192
221
  * Get a {[key: id]: name} dictionary of sources for a given source type
@@ -225,22 +254,17 @@ export class JupyterGISModel {
225
254
  * @returns a promise to the GeoJSON data.
226
255
  */
227
256
  async readGeoJSON(filepath) {
228
- if (!this._drive) {
257
+ if (!this._contentsManager) {
229
258
  return;
230
259
  }
231
- let dir = PathExt.dirname(this._filePath);
232
- if (dir.includes(':')) {
233
- dir = dir.split(':')[1];
234
- }
235
- const absolutePath = PathExt.join(dir, filepath);
236
- return this._drive
237
- .get(absolutePath)
238
- .then(contentModel => {
239
- return JSON.parse(contentModel.content);
240
- })
241
- .catch(e => {
242
- throw e;
260
+ const absolutePath = PathExt.resolve(PathExt.dirname(this._filePath), filepath);
261
+ const file = await this._contentsManager.get(absolutePath, {
262
+ content: true
243
263
  });
264
+ if (typeof file.content === 'string') {
265
+ return JSON.parse(file.content);
266
+ }
267
+ return file.content;
244
268
  }
245
269
  /**
246
270
  * Add a layer group in the layer tree.
@@ -289,10 +313,28 @@ export class JupyterGISModel {
289
313
  getOptions() {
290
314
  return this._sharedModel.options;
291
315
  }
316
+ syncViewport(viewport, emitter) {
317
+ this.sharedModel.awareness.setLocalStateField('viewportState', {
318
+ value: viewport,
319
+ emitter
320
+ });
321
+ }
322
+ syncPointer(pointer, emitter) {
323
+ this.sharedModel.awareness.setLocalStateField('pointer', {
324
+ value: pointer,
325
+ emitter
326
+ });
327
+ }
292
328
  syncSelected(value, emitter) {
293
329
  this.sharedModel.awareness.setLocalStateField('selected', {
294
330
  value,
295
- emitter: emitter
331
+ emitter
332
+ });
333
+ }
334
+ syncIdentifiedFeatures(features, emitter) {
335
+ this.sharedModel.awareness.setLocalStateField('identifiedFeatures', {
336
+ value: features,
337
+ emitter
296
338
  });
297
339
  }
298
340
  setUserToFollow(userId) {
@@ -385,29 +427,22 @@ export class JupyterGISModel {
385
427
  this._addLayerTreeItem(group);
386
428
  }
387
429
  _removeLayerTreeLayer(layerTree, layerIdToRemove) {
388
- // Iterate over each item in the layerTree
389
- for (let i = 0; i < layerTree.length; i++) {
390
- const currentItem = layerTree[i];
391
- // Check if the current item is a string and matches the target
392
- if (typeof currentItem === 'string' && currentItem === layerIdToRemove) {
393
- // Remove the item from the array
394
- layerTree.splice(i, 1);
395
- // Decrement i to ensure the next iteration processes the remaining items correctly
396
- i--;
397
- }
398
- else if (typeof currentItem !== 'string' && 'layers' in currentItem) {
399
- // If the current item is a group, recursively call the function on its layers
400
- this._removeLayerTreeLayer(currentItem.layers, layerIdToRemove);
401
- }
402
- }
430
+ this._removeLayerTreeItem(layerTree, layerIdToRemove, true);
403
431
  this.sharedModel.layerTree = layerTree;
404
432
  }
405
433
  _removeLayerTreeGroup(layerTree, groupName) {
434
+ this._removeLayerTreeItem(layerTree, groupName, false);
435
+ this.sharedModel.layerTree = layerTree;
436
+ }
437
+ _removeLayerTreeItem(layerTree, target, isLayer) {
406
438
  // Iterate over each item in the layerTree
407
439
  for (let i = 0; i < layerTree.length; i++) {
408
440
  const currentItem = layerTree[i];
441
+ const matches = isLayer
442
+ ? typeof currentItem === 'string' && currentItem === target
443
+ : typeof currentItem !== 'string' && currentItem.name === target;
409
444
  // Check if the current item is a string and matches the target
410
- if (typeof currentItem !== 'string' && currentItem.name === groupName) {
445
+ if (matches) {
411
446
  // Remove the item from the array
412
447
  layerTree.splice(i, 1);
413
448
  // Decrement i to ensure the next iteration processes the remaining items correctly
@@ -415,10 +450,9 @@ export class JupyterGISModel {
415
450
  }
416
451
  else if (typeof currentItem !== 'string' && 'layers' in currentItem) {
417
452
  // If the current item is a group, recursively call the function on its layers
418
- this._removeLayerTreeGroup(currentItem.layers, groupName);
453
+ this._removeLayerTreeItem(currentItem.layers, target, isLayer);
419
454
  }
420
455
  }
421
- this.sharedModel.layerTree = layerTree;
422
456
  }
423
457
  renameLayerGroup(groupName, newName) {
424
458
  const layerTreeInfo = this._getLayerTreeInfo(groupName);
@@ -451,6 +485,9 @@ export class JupyterGISModel {
451
485
  this._sharedModel.updateLayerTreeItem(layerTreeInfo.mainGroupIndex, updatedLayerTree[layerTreeInfo.mainGroupIndex]);
452
486
  }
453
487
  }
488
+ toggleIdentify() {
489
+ this._isIdentifying = !this._isIdentifying;
490
+ }
454
491
  _getLayerTreeInfo(groupName) {
455
492
  const layerTree = this.getLayerTree();
456
493
  const indexesPath = Private.findItemPath(layerTree, groupName);