Skip to content
Draft
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
39 changes: 39 additions & 0 deletions python/jupytergis_core/jupytergis_core/geojson_ydoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import json
from typing import Any, Callable
from functools import partial

from pycrdt import Text
from jupyter_ydoc.ybasedoc import YBaseDoc

from .schema import SCHEMA_VERSION


class YGEOJSON(YBaseDoc):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ydoc["source"] = self._ysource = Text()

def version(self) -> str:
return SCHEMA_VERSION

def get(self) -> str:
"""
Returns the content of the document.
:return: Document's content.
:rtype: Any
"""
return json.dumps(self._ysource.to_py())

def set(self, value: str) -> None:
"""
Sets the content of the document.
:param value: The content of the document.
:type value: Any
"""
self._ysource[:] = value

def observe(self, callback: Callable[[str, Any], None]):
self.unobserve()
self._subscriptions[self._ysource] = self._ysource.observe(
partial(callback, "source")
)
89 changes: 89 additions & 0 deletions python/jupytergis_core/src/geojsonplugin/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
Copy link
Member

@arjxn-py arjxn-py May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks like you followed how we initialize model for stl/step files on JupyterCAD, but in the case of JupyterGIS, the model properties are fairly different, most importantly layers & sources instead of objects, for context https://github.com/geojupyter/jupytergis/blob/main/packages/schema/src/doc.ts

SCHEMA_VERSION,
IJupyterGISDoc,
JupyterGISDoc
} from '@jupytergis/schema';
import { JSONExt } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';
import * as Y from 'yjs';

export class JupyterGISGeoJSONDoc extends JupyterGISDoc {
constructor() {
super();

this._source = this.ydoc.getText('source');
this._source.observeDeep(this._sourceObserver);
}

set source(value: string) {
this._source.insert(0, value);
}

get version(): string {
return SCHEMA_VERSION;
}

get objectsChanged(): ISignal<IJupyterGISDoc, any> {
return this._objectChanged;
}

get objects(): Array<any> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be get layers() and it should respect vectorlayer schema - https://github.com/geojupyter/jupytergis/blob/main/packages/schema/src/schema/project/layers/vectorlayer.json

Similarly there should be getters for sources & layerTree too and the sources getter shall respect https://github.com/geojupyter/jupytergis/blob/main/packages/schema/src/schema/geojsonsource.json - note that you can either give it the parameter path or directly write the content of geojson in data

const source = this._source.toJSON();
console.log("calling source");
console.log(source);

if (!source) {
return [];
}

return [
{
name: 'GeoJSON File',
visible: true,
type: 'VectorTileLayer',
parameters: {
content: this._source.toJSON(),
style: {
color: '#3388ff',
weight: 2,
opacity: 0.8,
fillOpacity: 0.2
}
}
}
];
}

setSource(value: string): void {
this._source.insert(0, value);
}

static create(): JupyterGISGeoJSONDoc {
return new JupyterGISGeoJSONDoc();
}

editable = false;
toJgisEndpoint = 'jupytergis/export';

private _sourceObserver = (events: Y.YEvent<any>[]): void => {
const changes: Array<{
name: string;
key: string;
newValue: any;
}> = [];
events.forEach(event => {
event.keys.forEach((change, key) => {
changes.push({
name: 'GeoJSON File',
key: key as string,
newValue: JSONExt.deepCopy(event.target.toJSON())
});
});
});
this._objectChanged.emit({ objectChange: changes });
this._changed.emit({ layerChange: changes });
};

private _source: Y.Text;
private _objectChanged = new Signal<IJupyterGISDoc, any>(this);
}
123 changes: 123 additions & 0 deletions python/jupytergis_core/src/geojsonplugin/modelfactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { IJupyterGISDoc, JupyterGISModel } from '@jupytergis/schema';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { Contents } from '@jupyterlab/services';
import { JupyterGISGeoJSONDoc } from './model';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { IAnnotationModel } from '@jupytergis/schema';

class JupyterGISGeoJSONModel extends JupyterGISModel {
constructor(options: {
sharedModel?: IJupyterGISDoc;
languagePreference?: string;
settingRegistry?: ISettingRegistry;
annotationModel?: IAnnotationModel;
}) {
super({
sharedModel: options.sharedModel,
languagePreference: options.languagePreference,
settingRegistry: options.settingRegistry,
annotationModel: options.annotationModel
});
}

fromString(data: string): void {
(this.sharedModel as JupyterGISGeoJSONDoc).source = data;
this.dirty = true;
}

protected createSharedModel(): IJupyterGISDoc {
return JupyterGISGeoJSONDoc.create();
}
}

/**
* A Model factory to create new instances of JupyterGISGeoJSONModel.
*/
export class JupyterGISGeoJSONModelFactory
implements DocumentRegistry.IModelFactory<JupyterGISGeoJSONModel>
{
constructor(options: {
settingRegistry?: ISettingRegistry;
annotationModel?: IAnnotationModel;
}) {
this._settingRegistry = options.settingRegistry;
this._annotationModel = options.annotationModel;
}

readonly collaborative = true;

/**
* The name of the model.
*
* @returns The name
*/
get name(): string {
return 'jupytergis-geojsonmodel';
}

/**
* The content type of the file.
*
* @returns The content type
*/
get contentType(): Contents.ContentType {
return 'geojson';
}

/**
* The format of the file.
*
* @returns the file format
*/
get fileFormat(): Contents.FileFormat {
return 'text';
}

/**
* Get whether the model factory has been disposed.
*
* @returns disposed status
*/
get isDisposed(): boolean {
return this._disposed;
}

/**
* Dispose the model factory.
*/
dispose(): void {
this._disposed = true;
}

/**
* Get the preferred language given the path on the file.
*
* @param path path of the file represented by this document model
* @returns The preferred language
*/
preferredLanguage(path: string): string {
return '';
}

/**
* Create a new instance of JupyterGISGeoJSONModel.
*
* @returns The model
*/
createNew(
options: DocumentRegistry.IModelOptions<IJupyterGISDoc>
): JupyterGISGeoJSONModel {
const model = new JupyterGISGeoJSONModel({
sharedModel: options.sharedModel,
languagePreference: options.languagePreference,
settingRegistry: this._settingRegistry,
annotationModel: this._annotationModel
});
model.initSettings();
return model;
}

private _disposed = false;
private _settingRegistry: ISettingRegistry | undefined;
private _annotationModel: IAnnotationModel | undefined;
}
139 changes: 139 additions & 0 deletions python/jupytergis_core/src/geojsonplugin/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
ICollaborativeDrive,
SharedDocumentFactory
} from '@jupyter/collaborative-drive';
import {
IJupyterGISDocTracker,
IJupyterGISWidget,
IJGISExternalCommandRegistry,
IJGISExternalCommandRegistryToken,
IAnnotationModel,
IAnnotationToken
} from '@jupytergis/schema';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
import { IEditorServices } from '@jupyterlab/codeeditor';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ICommandPalette } from '@jupyterlab/apputils';

import { JupyterGISGeoJSONModelFactory } from './modelfactory';
import { JupyterGISDocumentWidgetFactory } from '../factory';
import { JupyterGISGeoJSONDoc } from './model';
import { logoMiniIcon, JupyterGISDocumentWidget } from '@jupytergis/base';

const FACTORY = 'JupyterGIS GeoJSON Viewer';
const SETTINGS_ID = '@jupytergis/jupytergis-core:jupytergis-settings';

const activate = async (
app: JupyterFrontEnd,
tracker: WidgetTracker<IJupyterGISWidget>,
themeManager: IThemeManager,
drive: ICollaborativeDrive,
externalCommandRegistry: IJGISExternalCommandRegistry,
contentFactory: ConsolePanel.IContentFactory,
editorServices: IEditorServices,
rendermime: IRenderMimeRegistry,
consoleTracker: IConsoleTracker,
annotationModel: IAnnotationModel,
settingRegistry: ISettingRegistry,
commandPalette: ICommandPalette | null
): Promise<void> => {
let settings: ISettingRegistry.ISettings | null = null;

if (settingRegistry) {
try {
settings = await settingRegistry.load(SETTINGS_ID);
console.log(`Loaded settings for ${SETTINGS_ID}`, settings);
} catch (error) {
console.warn(`Failed to load settings for ${SETTINGS_ID}`, error);
}
} else {
console.warn('No settingRegistry available; using default settings.');
}

const widgetFactory = new JupyterGISDocumentWidgetFactory({
name: FACTORY,
modelName: 'jupytergis-geojsonmodel',
fileTypes: ['geojson'],
defaultFor: ['geojson'],
tracker,
commands: app.commands,
externalCommandRegistry,
manager: app.serviceManager,
contentFactory,
rendermime,
mimeTypeService: editorServices.mimeTypeService,
consoleTracker
});

console.log("geojson widget factory created", widgetFactory);

app.docRegistry.addWidgetFactory(widgetFactory);

const modelFactory = new JupyterGISGeoJSONModelFactory({
annotationModel,
settingRegistry
});
app.docRegistry.addModelFactory(modelFactory);

app.docRegistry.addFileType({
name: 'geojson',
displayName: 'GeoJSON',
mimeTypes: ['application/json'],
extensions: ['.geojson', '.GEOJSON'],
fileFormat: 'text',
contentType: 'geojson',
icon: logoMiniIcon
});

const geojsonSharedModelFactory: SharedDocumentFactory = () => {
return new JupyterGISGeoJSONDoc();
};
drive.sharedModelFactory.registerDocumentFactory(
'geojson',
geojsonSharedModelFactory
);

const widgetCreatedCallback = (
sender: any,
widget: JupyterGISDocumentWidget
) => {
console.log("calling geojson widget callback");
widget.title.icon = logoMiniIcon;
widget.context.pathChanged.connect(() => {
tracker.save(widget);
});
themeManager.themeChanged.connect((_, changes) =>
widget.model.themeChanged.emit(changes)
);
tracker.add(widget);
};

widgetFactory.widgetCreated.connect(widgetCreatedCallback);
};

const geojsonPlugin: JupyterFrontEndPlugin<void> = {
id: '@jupytergis/jupytergis-core:geojsonplugin',
requires: [
IJupyterGISDocTracker,
IThemeManager,
ICollaborativeDrive,
IJGISExternalCommandRegistryToken,
ConsolePanel.IContentFactory,
IEditorServices,
IRenderMimeRegistry,
IConsoleTracker,
IAnnotationToken,
ISettingRegistry
],
optional: [ICommandPalette],
autoStart: true,
activate
};

export default geojsonPlugin;
Loading
Loading