From 71e656cec0cef7e13b091883ec7a49c6382cc064 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Thu, 9 Jan 2025 17:48:23 +0530 Subject: [PATCH] Add getter for contentsmanager and filepath --- .../symbology/hooks/useGetProperties.ts | 4 +- .../formbuilder/objectform/geojsonsource.ts | 12 +- packages/base/src/mainview/mainView.tsx | 31 ++-- .../components/filter-panel/Filter.tsx | 11 +- packages/base/src/tools.ts | 131 +++++++++++++++- packages/schema/src/interfaces.ts | 4 +- packages/schema/src/model.ts | 146 ++---------------- 7 files changed, 177 insertions(+), 162 deletions(-) diff --git a/packages/base/src/dialogs/symbology/hooks/useGetProperties.ts b/packages/base/src/dialogs/symbology/hooks/useGetProperties.ts index 355341ea..a9705d59 100644 --- a/packages/base/src/dialogs/symbology/hooks/useGetProperties.ts +++ b/packages/base/src/dialogs/symbology/hooks/useGetProperties.ts @@ -2,6 +2,7 @@ import { GeoJSONFeature1, IJupyterGISModel } from '@jupytergis/schema'; import { useEffect, useState } from 'react'; +import { loadFile } from '../../../tools'; interface IUseGetPropertiesProps { layerId?: string; @@ -35,9 +36,8 @@ export const useGetProperties = ({ throw new Error('Source not found'); } - const data = await model.loadFile( + const data = await loadFile( source.parameters?.path, - 'GeoJSONSource' ); if (!data) { diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index bd1679a5..99ae505f 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -5,6 +5,7 @@ import { Ajv, ValidateFunction } from 'ajv'; import * as geojson from '@jupytergis/schema/src/schema/geojson.json'; import { BaseForm, IBaseFormProps } from './baseform'; +import { loadFile } from '../../tools'; /** * The form to modify a GeoJSON source. @@ -67,10 +68,13 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { let valid = false; if (path) { try { - const geoJSONData = await this.props.model.loadFile( - path, - 'GeoJSONSource' - ); + this.props.model.getContentsManager(); + const geoJSONData = await loadFile({ + filepath: path, + type: 'GeoJSONSource', + contentsManager: this.props.model.getContentsManager(), + filePath: this.props.model.getFilePath() + }); valid = this._validate(geoJSONData); if (!valid) { error = `"${path}" is not a valid GeoJSON file`; diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 973ef9c4..bfb8303c 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -71,6 +71,7 @@ import AnnotationFloater from '../annotations/components/AnnotationFloater'; import { CommandIDs } from '../constants'; import { FollowIndicator } from './FollowIndicator'; import CollaboratorPointers, { ClientPointer } from './CollaboratorPointers'; +import { loadFile } from '../tools'; interface IProps { viewModel: MainViewModel; @@ -409,10 +410,12 @@ export class MainView extends React.Component { case 'GeoJSONSource': { const data = source.parameters?.data || - (await this._model.loadFile( - source.parameters?.path, - 'GeoJSONSource' - )); + await loadFile({ + filepath: source.parameters?.path, + type: 'GeoJSONSource', + contentsManager: this._model.getContentsManager(), + filePath: this._model.getFilePath() + }); const format = new GeoJSON({ featureProjection: this._Map.getView().getProjection() @@ -435,10 +438,12 @@ export class MainView extends React.Component { case 'ShapefileSource': { const parameters = source.parameters as IShapefileSource; - const geojson = await this._model.loadFile( - parameters.path, - 'ShapefileSource' - ); + const geojson = await loadFile({ + filepath: parameters.path, + type: 'ShapefileSource', + contentsManager: this._model.getContentsManager(), + filePath: this._model.getFilePath() + }); const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson; @@ -482,10 +487,12 @@ export class MainView extends React.Component { const extent = [minX, minY, maxX, maxY]; - const imageUrl = await this._model.loadFile( - sourceParameters.url, - 'ImageSource' - ); + const imageUrl = await loadFile({ + filepath: sourceParameters.url, + type: 'ImageSource', + contentsManager: this._model.getContentsManager(), + filePath: this._model.getFilePath() + }); newSource = new Static({ imageExtent: extent, diff --git a/packages/base/src/panelview/components/filter-panel/Filter.tsx b/packages/base/src/panelview/components/filter-panel/Filter.tsx index 42fb1f02..d26ca482 100644 --- a/packages/base/src/panelview/components/filter-panel/Filter.tsx +++ b/packages/base/src/panelview/components/filter-panel/Filter.tsx @@ -13,6 +13,7 @@ import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; import { debounce, getLayerTileInfo } from '../../../tools'; import { IControlPanelModel } from '../../../types'; import FilterRow from './FilterRow'; +import { loadFile } from '../../../tools'; /** * The filters panel widget. @@ -209,10 +210,12 @@ const FilterComponent = (props: IFilterComponentProps) => { break; } case 'GeoJSONSource': { - const data = await model?.loadFile( - source.parameters?.path, - 'GeoJSONSource' - ); + const data = await loadFile({ + filepath: source.parameters?.path, + type: 'GeoJSONSource', + contentsManager: model.getContentsManager(), + filePath: model.getFilePath() + }); data?.features.forEach((feature: GeoJSONFeature1) => { feature.properties && addFeatureValue(feature.properties, aggregatedProperties); diff --git a/packages/base/src/tools.ts b/packages/base/src/tools.ts index e8e2a6a3..b82f33ff 100644 --- a/packages/base/src/tools.ts +++ b/packages/base/src/tools.ts @@ -5,13 +5,17 @@ import { VectorTile } from '@mapbox/vector-tile'; import { URLExt } from '@jupyterlab/coreutils'; import { ServerConnection } from '@jupyterlab/services'; import * as d3Color from 'd3-color'; +import { PathExt } from '@jupyterlab/coreutils'; +import shp from 'shpjs'; import { IDict, IJGISLayerBrowserRegistry, IJGISOptions, - IRasterLayerGalleryEntry + IRasterLayerGalleryEntry, + IJGISSource } from '@jupytergis/schema'; +import { Contents } from '@jupyterlab/services'; import RASTER_LAYER_GALLERY from '../rasterlayer_gallery/raster_layer_gallery.json'; import { getGdal } from './gdal'; @@ -452,3 +456,128 @@ export const loadGeoTIFFWithCache = async (sourceInfo: { sourceUrl: sourceInfo.url }; }; + +/** + * Generalized file reader for different source types. + * + * @param fileInfo - Object containing the file path and source type. + * @returns A promise that resolves to the file content. + */ +export const loadFile = async (fileInfo: { + filepath: string; + type: IJGISSource['type']; + contentsManager?: Contents.IManager; + filePath?: string; +}) => { + const { filepath, type, contentsManager, filePath } = fileInfo; + + if (filepath.startsWith('http://') || filepath.startsWith('https://')) { + switch (type) { + case 'ImageSource': { + return filepath; // Return the URL directly + } + + case 'ShapefileSource': { + try { + const response = await fetch(`/jupytergis_core/proxy?url=${filepath}`); + const arrayBuffer = await response.arrayBuffer(); + const geojson = await shp(arrayBuffer); + return geojson; + } catch (error) { + console.error('Error loading remote shapefile:', error); + throw error; + } + } + + default: { + throw new Error(`Unsupported URL handling for source type: ${type}`); + } + } + } + + if (!contentsManager || !filePath) { + throw new Error('ContentsManager or filePath is not initialized.'); + } + + const absolutePath = PathExt.resolve(PathExt.dirname(filePath), filepath); + + try { + const file = await contentsManager.get(absolutePath, { content: true }); + + if (!file.content) { + throw new Error(`File at ${absolutePath} is empty or inaccessible.`); + } + + switch (type) { + case 'GeoJSONSource': { + return typeof file.content === 'string' + ? JSON.parse(file.content) + : file.content; + } + + case 'ShapefileSource': { + const arrayBuffer = await stringToArrayBuffer(file.content as string); + const geojson = await shp(arrayBuffer); + return geojson; + } + + case 'ImageSource': { + if (typeof file.content === 'string') { + const mimeType = getMimeType(filepath); + return `data:${mimeType};base64,${file.content}`; + } else { + throw new Error('Invalid file format for image content.'); + } + } + + default: { + throw new Error(`Unsupported source type: ${type}`); + } + } + } catch (error) { + console.error(`Error reading file '${filepath}':`, error); + throw error; + } +}; + +/** + * Determine the MIME type based on the file extension. + * + * @param filename - The name of the file. + * @returns A string representing the MIME type. + */ +export const getMimeType = (filename: string): string => { + const extension = filename.split('.').pop()?.toLowerCase(); + + switch (extension) { + case 'png': + return 'image/png'; + case 'jpg': + case 'jpeg': + return 'image/jpeg'; + case 'gif': + return 'image/gif'; + case 'webp': + return 'image/webp'; + case 'svg': + return 'image/svg+xml'; + default: + console.warn( + `Unknown file extension: ${extension}, defaulting to 'application/octet-stream'.` + ); + return 'application/octet-stream'; + } +}; + +/** + * Helper to convert a string (base64) to ArrayBuffer. + * + * @param content - File content as a base64 string. + * @returns An ArrayBuffer. + */ +export const stringToArrayBuffer = async (content: string): Promise => { + const base64Response = await fetch( + `data:application/octet-stream;base64,${content}` + ); + return await base64Response.arrayBuffer(); +}; diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index 7f565f7d..b7d96975 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -167,6 +167,8 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { value: Contents.IManager | undefined, filePath: string ): void; + getContentsManager(): Contents.IManager | undefined; + getFilePath(): string; getContent(): IJGISContent; getLayers(): IJGISLayers; getLayer(id: string): IJGISLayer | undefined; @@ -185,8 +187,6 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { getOptions(): IJGISOptions; setOptions(value: IJGISOptions): void; - loadFile(filepath: string, type: SourceType): Promise; - removeLayerGroup(groupName: string): void; renameLayerGroup(groupName: string, newName: string): void; moveItemsToGroup(items: string[], groupName: string, index?: number): void; diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 67a6de12..5f8a831a 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -1,11 +1,10 @@ import { MapChange } from '@jupyter/ydoc'; -import { IChangedArgs, PathExt } from '@jupyterlab/coreutils'; +import { IChangedArgs } from '@jupyterlab/coreutils'; import { DocumentRegistry } from '@jupyterlab/docregistry'; import { PartialJSONObject } from '@lumino/coreutils'; import { ISignal, Signal } from '@lumino/signaling'; import Ajv from 'ajv'; -import shp from 'shpjs'; // import GeoTIFF from 'geotiff'; import { IJGISContent, @@ -254,6 +253,14 @@ export class JupyterGISModel implements IJupyterGISModel { this._filePath = filePath; } + getContentsManager() { + return this._contentsManager; + } + + getFilePath() { + return this._filePath; + } + getLayers(): IJGISLayers { return this.sharedModel.layers; } @@ -305,141 +312,6 @@ export class JupyterGISModel implements IJupyterGISModel { return usingLayers; } - /** - * Generalized file reader for different source types. - * - * @param filepath - Path to the file. - * @param type - Type of the source file (e.g., "GeoJSONSource", "ShapefileSource"). - * @returns A promise that resolves to the file content. - */ - async loadFile( - filepath: string, - type: IJGISSource['type'] - ): Promise { - // Handle URLs directly for ImageSource and ShapefileSource - if (filepath.startsWith('http://') || filepath.startsWith('https://')) { - switch (type) { - case 'ImageSource': { - return filepath; // Return the URL directly - } - - case 'ShapefileSource': { - try { - // Proxy request to fetch remote shapefile - const response = await fetch( - `/jupytergis_core/proxy?url=${filepath}` - ); - const arrayBuffer = await response.arrayBuffer(); - const geojson = await shp(arrayBuffer); - return geojson; - } catch (error) { - console.error('Error loading remote shapefile:', error); - throw error; - } - } - - default: { - throw new Error(`Unsupported URL handling for source type: ${type}`); - } - } - } - - // Handle local files using ContentsManager - if (!this._contentsManager) { - throw new Error('ContentsManager is not initialized.'); - } - - const absolutePath = PathExt.resolve( - PathExt.dirname(this._filePath), - filepath - ); - - try { - const file = await this._contentsManager.get(absolutePath, { - content: true - }); - - if (!file.content) { - throw new Error(`File at ${absolutePath} is empty or inaccessible.`); - } - - switch (type) { - case 'GeoJSONSource': { - if (typeof file.content === 'string') { - return JSON.parse(file.content); - } - return file.content; - } - - case 'ShapefileSource': { - const arrayBuffer = await this._stringToArrayBuffer( - file.content as string - ); - const geojson = await shp(arrayBuffer); - return geojson; - } - - case 'ImageSource': { - if (typeof file.content === 'string') { - // Convert base64 to a data URL - const mimeType = this._getMimeType(filepath); - return `data:${mimeType};base64,${file.content}`; - } else { - throw new Error('Invalid file format for image content'); - } - } - - default: { - throw new Error(`Unsupported source type: ${type}`); - } - } - } catch (error) { - console.error(`Error reading file '${filepath}':`, error); - throw error; - } - } - - /** - * Determine the MIME type based on the file extension. - * - * @param filename - The name of the file. - * @returns A string representing the MIME type. - */ - private _getMimeType(filename: string): string { - const extension = filename.split('.').pop()?.toLowerCase(); - - switch (extension) { - case 'png': - return 'image/png'; - case 'jpg': - case 'jpeg': - return 'image/jpeg'; - case 'gif': - return 'image/gif'; - case 'webp': - return 'image/webp'; - case 'svg': - return 'image/svg+xml'; - default: - console.warn( - `Unknown file extension: ${extension}, defaulting to 'application/octet-stream'` - ); - return 'application/octet-stream'; - } - } - - /** - * Helper to convert a string (base64) to ArrayBuffer. - * @param content - File content as a base64 string. - * @returns An ArrayBuffer. - */ - private async _stringToArrayBuffer(content: string): Promise { - const base64Response = await fetch( - `data:application/octet-stream;base64,${content}` - ); - return await base64Response.arrayBuffer(); - } - /** * Add a layer group in the layer tree. *