-
Notifications
You must be signed in to change notification settings - Fork 57
[WIP] Add GeoJSON plugin to view files in GIS viewer #725
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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") | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { | ||
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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be Similarly there should be getters for |
||
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); | ||
} |
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; | ||
} |
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; |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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 importantlylayers
&sources
instead ofobjects
, for context https://github.com/geojupyter/jupytergis/blob/main/packages/schema/src/doc.ts