Skip to content
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
29 changes: 26 additions & 3 deletions packages/base/src/dialogs/symbology/hooks/useGetBandInfo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IJGISLayer, IJupyterGISModel } from '@jupytergis/schema';
import { useEffect, useState } from 'react';
import { fromUrl } from 'geotiff';
import { fromUrl, fromBlob } from 'geotiff';
import { loadFile } from '../../../tools';

export interface IBandHistogram {
buckets: number[];
Expand Down Expand Up @@ -38,8 +39,30 @@ const useGetBandInfo = (model: IJupyterGISModel, layer: IJGISLayer) => {
return;
}

// TODO Get band names + get band stats
const tiff = await fromUrl(sourceInfo.url);
let tiff;
if (
sourceInfo.url.startsWith('http') ||
sourceInfo.url.startsWith('https')
) {
// Handle remote GeoTIFF file
tiff = await fromUrl(sourceInfo.url);
} else {
// Handle local GeoTIFF file
const preloadedFile = await loadFile({
filepath: sourceInfo.url,
type: 'GeoTiffSource',
model
});

if (!preloadedFile.file) {
setError('Failed to load local file.');
setLoading(false);
return;
}

tiff = await fromBlob(preloadedFile.file);
}

const image = await tiff.getImage();
const numberOfBands = image.getSamplesPerPixel();

Expand Down
21 changes: 21 additions & 0 deletions packages/base/src/formbuilder/objectform/source/geotiffsource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IChangeEvent, ISubmitEvent } from '@rjsf/core';

import { getMimeType } from '../../../tools';
import { ISourceFormProps, SourcePropertiesForm } from './sourceform';
import { FileSelectorWidget } from '../fileselectorwidget';

/**
* The form to modify a GeoTiff source.
Expand All @@ -28,6 +29,26 @@ export class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
return;
}

// Customize the widget for urls
if (schema.properties && schema.properties.urls) {
const docManager =
this.props.formChangedSignal?.sender.props.formSchemaRegistry.getDocManager();

uiSchema.urls = {
...uiSchema.urls,
items: {
...uiSchema.urls.items,
url: {
'ui:widget': FileSelectorWidget,
'ui:options': {
docManager,
formOptions: this.props
}
}
}
};
}

// This is not user-editable
delete schema.properties.valid;
}
Expand Down
31 changes: 25 additions & 6 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -634,12 +634,31 @@ export class MainView extends React.Component<IProps, IStates> {
};
const sources = await Promise.all(
sourceParameters.urls.map(async sourceInfo => {
return {
...addNoData(sourceInfo),
min: sourceInfo.min,
max: sourceInfo.max,
url: sourceInfo.url
};
const isRemote =
sourceInfo.url?.startsWith('http://') ||
sourceInfo.url?.startsWith('https://');

if (isRemote) {
return {
...addNoData(sourceInfo),
min: sourceInfo.min,
max: sourceInfo.max,
url: sourceInfo.url
};
} else {
const geotiff = await loadFile({
filepath: sourceInfo.url ?? '',
type: 'GeoTiffSource',
model: this._model
});
return {
...addNoData(sourceInfo),
min: sourceInfo.min,
max: sourceInfo.max,
geotiff,
url: URL.createObjectURL(geotiff.file)
};
}
})
);

Expand Down
69 changes: 68 additions & 1 deletion packages/base/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import Protobuf from 'pbf';
import { VectorTile } from '@mapbox/vector-tile';

import { PathExt, URLExt } from '@jupyterlab/coreutils';
import { ServerConnection } from '@jupyterlab/services';
import { Contents, ServerConnection } from '@jupyterlab/services';
import { showErrorMessage } from '@jupyterlab/apputils';
import * as d3Color from 'd3-color';
import shp from 'shpjs';
import { getGdal } from './gdal';

import {
IDict,
Expand Down Expand Up @@ -435,6 +436,63 @@ const fetchWithProxies = async <T>(
return null;
};

/**
* Load a GeoTIFF file from IndexedDB database cache or fetch it .
*
* @param sourceInfo object containing the URL of the GeoTIFF file.
* @returns A promise that resolves to the file as a Blob, or undefined .
*/
export const loadGeoTiff = async (
sourceInfo: { url?: string | undefined },
file?: Contents.IModel | null
) => {
if (!sourceInfo?.url) {
return null;
}

const url = sourceInfo.url;
const mimeType = getMimeType(url);
if (!mimeType || !mimeType.startsWith('image/tiff')) {
throw new Error('Invalid file type. Expected GeoTIFF (image/tiff).');
}

const cachedData = await getFromIndexedDB(url);
if (cachedData) {
return {
file: cachedData.file,
metadata: cachedData.metadata,
sourceUrl: url
};
}

let fileBlob: Blob | null = null;

if (!file) {
fileBlob = await fetchWithProxies(url, async response => response.blob());
if (!fileBlob) {
showErrorMessage('Network error', `Failed to fetch ${url}`);
throw new Error(`Failed to fetch ${url}`);
}
} else {
fileBlob = await base64ToBlob(file.content, mimeType);
}

const geotiff = new File([fileBlob], 'loaded.tif');
const Gdal = await getGdal();
const result = await Gdal.open(geotiff);
const tifDataset = result.datasets[0];
const metadata = await Gdal.gdalinfo(tifDataset, ['-stats']);
Gdal.close(tifDataset);

await saveToIndexedDB(url, fileBlob, metadata);

return {
file: fileBlob,
metadata,
sourceUrl: url
};
};

/**
* Generalized file reader for different source types.
*
Expand Down Expand Up @@ -567,6 +625,15 @@ export const loadFile = async (fileInfo: {
}
}

case 'GeoTiffSource': {
if (typeof file.content === 'string') {
const tiff = loadGeoTiff({ url: filepath }, file);
return tiff;
} else {
throw new Error('Invalid file format for tiff content.');
}
}

default: {
throw new Error(`Unsupported source type: ${type}`);
}
Expand Down
Loading