Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
415713a
Add GeoPackage data format
elifsu-simula May 19, 2025
dd00081
Fix formatting
elifsu-simula May 19, 2025
b9e724c
Merge branch 'main' into add_geopackage_data_format
elifsu-simula May 19, 2025
a1998f7
Fix merge conflicts and adapt code to updated branch
elifsu-simula May 19, 2025
7ce7535
Use ol-load-geopackage to read file
elifsu-simula Jun 13, 2025
973d27c
Fix copying sql wasm
elifsu-simula Jul 11, 2025
e8cc15c
Load local file
elifsu-simula Jul 14, 2025
aace235
Read gpkg URLs in Python API
elifsu-simula Jul 15, 2025
4300039
Read gpkg local files in Python API
elifsu-simula Jul 15, 2025
74a680d
Add gpkg raster layers from local files
elifsu-simula Jul 16, 2025
ba9a184
Add gpkg raster layers from URLs and set projection for vector layers
elifsu-simula Jul 17, 2025
7c849c7
Add gpkg raster layer from Python API
elifsu-simula Jul 18, 2025
89adb49
Fix issue with proj4 register
elifsu-simula Jul 18, 2025
dd2657a
Read tile width height from tile dao
elifsu-simula Jul 21, 2025
340323d
Make sure user entered table name is valid
elifsu-simula Jul 21, 2025
15412fb
Add more properties to GeoPackage Raster source
elifsu-simula Jul 21, 2025
822a304
Add example geopackage project
elifsu-simula Jul 21, 2025
793b349
Remove downloaded gpkg file after reading
elifsu-simula Jul 21, 2025
fa77f92
Merge branch 'main' into add_geopackage_data_format
elifsu-simula Jul 21, 2025
0d77c6c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 21, 2025
161d681
Formatting and linting
elifsu-simula Jul 21, 2025
c8f0c31
Make webpack config path relative
elifsu-simula Jul 21, 2025
d62ac7a
Linearize curved geometries
elifsu-simula Aug 13, 2025
8522e1d
Reproject data
elifsu-simula Aug 13, 2025
4aab3f9
Add newline to docstring
elifsu-simula Aug 15, 2025
15e6ddd
Merge branch 'main' into add_geopackage_data_format
elifsu-simula Aug 15, 2025
8f9df16
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 15, 2025
9ba4e68
Formatting
elifsu-simula Aug 15, 2025
a0ceb1b
Merge branch 'add_geopackage_data_format' of https://github.com/elifs…
elifsu-simula Aug 15, 2025
6d8fde2
Linting
elifsu-simula Aug 15, 2025
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
1 change: 1 addition & 0 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@lumino/widgets": "^2.0.0",
"@mapbox/vector-tile": "^2.0.3",
"@naisutech/react-tree": "^3.0.1",
"@ngageoint/geopackage": "^4.2.6",
"@rjsf/core": "^4.2.0",
"@rjsf/validator-ajv8": "^5.23.1",
"ajv": "^8.14.0",
Expand Down
20 changes: 20 additions & 0 deletions packages/base/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,26 @@ export function addCommands(
}),
...icons.get(CommandIDs.newShapefileEntry)
});
commands.addCommand(CommandIDs.newGeoPackageEntry, {
label: trans.__('New GeoPackage Layer'),
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.model.sharedModel.editable
: false;
},
execute: Private.createEntry({
tracker,
formSchemaRegistry,
title: 'Create GeoPackage Layer',
createLayer: true,
createSource: true,
sourceData: { name: 'Custom GeoPackage Source' },
layerData: { name: 'Custom GeoPackage Layer' },
sourceType: 'GeoPackageSource',
layerType: 'VectorLayer'
}),
...icons.get(CommandIDs.newGeoPackageEntry)
});

/**
* LAYERS and LAYER GROUP actions.
Expand Down
3 changes: 3 additions & 0 deletions packages/base/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
bookOpenIcon,
clockIcon,
geoJSONIcon,
geoPackageIcon,
infoIcon,
moundIcon,
rasterIcon,
Expand Down Expand Up @@ -35,6 +36,7 @@ export namespace CommandIDs {
export const newImageEntry = 'jupytergis:newImageEntry';
export const newVideoEntry = 'jupytergis:newVideoEntry';
export const newGeoTiffEntry = 'jupytergis:newGeoTiffEntry';
export const newGeoPackageEntry = 'jupytergis:newGeoPackageEntry';

// Processing commands
export const buffer = 'jupytergis:buffer';
Expand Down Expand Up @@ -94,6 +96,7 @@ const iconObject = {
[CommandIDs.newHillshadeEntry]: { icon: moundIcon },
[CommandIDs.newImageEntry]: { iconClass: 'fa fa-image' },
[CommandIDs.newVideoEntry]: { iconClass: 'fa fa-video' },
[CommandIDs.newGeoPackageEntry]: { icon: geoPackageIcon },
[CommandIDs.newShapefileEntry]: { iconClass: 'fa fa-file' },
[CommandIDs.newGeoTiffEntry]: { iconClass: 'fa fa-image' },
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
Expand Down
3 changes: 3 additions & 0 deletions packages/base/src/formbuilder/formselectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export function getSourceTypeForm(
case 'VectorTileSource':
SourceForm = TileSourcePropertiesForm;
break;
case 'GeoPackageSource':
SourceForm = PathBasedSourcePropertiesForm;
break;
// ADD MORE FORM TYPES HERE
}
return SourceForm;
Expand Down
6 changes: 6 additions & 0 deletions packages/base/src/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import terminalToolbarSvgStr from '../style/icons/terminal_toolbar.svg';
import geolocationSvgStr from '../style/icons/geolocation.svg';
import targetWithoutCenterSvgStr from '../style/icons/target_without_center.svg';
import targetWithCenterSvgStr from '../style/icons/target_with_center.svg';
import geoPackageSvgStr from '../style/icons/geopackage.svg';

export const logoIcon = new LabIcon({
name: 'jupytergis::logo',
Expand Down Expand Up @@ -108,3 +109,8 @@ export const targetWithCenterIcon = new LabIcon({
name: 'jupytergis::targetWithoutCenter',
svgstr: targetWithoutCenterSvgStr
});

export const geoPackageIcon = new LabIcon({
name: 'jupytergis::geoPackage',
svgstr: geoPackageSvgStr
});
22 changes: 22 additions & 0 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MapChange } from '@jupyter/ydoc';
import {
IAnnotation,
IDict,
IGeoPackageSource,
IGeoTiffSource,
IHeatmapLayer,
IHillshadeLayer,
Expand Down Expand Up @@ -695,6 +696,27 @@ export class MainView extends React.Component<IProps, IStates> {

break;
}
case 'GeoPackageSource': {
const parameters = source.parameters as IGeoPackageSource;

const geojson = await loadFile({
filepath: parameters.path,
type: 'GeoPackageSource',
model: this._model
});

const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;

const format = new GeoJSON();

newSource = new VectorSource({
features: format.readFeatures(geojsonData, {
dataProjection: parameters.projection,
featureProjection: this._Map.getView().getProjection()
})
});
break;
}
}

newSource.set('id', id);
Expand Down
5 changes: 5 additions & 0 deletions packages/base/src/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export const vectorSubMenu = (commands: CommandRegistry) => {
command: CommandIDs.newShapefileEntry
});

subMenu.addItem({
type: 'command',
command: CommandIDs.newGeoPackageEntry
});

return subMenu;
};

Expand Down
59 changes: 59 additions & 0 deletions packages/base/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SourceType
} from '@jupytergis/schema';
import RASTER_LAYER_GALLERY from '../rasterlayer_gallery/raster_layer_gallery.json';
import { GeoPackageAPI } from '@ngageoint/geopackage';

export const debounce = (
func: CallableFunction,
Expand Down Expand Up @@ -489,6 +490,31 @@ export const loadGeoTiff = async (
};
};

/**
* Read a GeoPackage file
*
* @param file The GeoPackage file content as an ArrayBuffer
*
* @returns A promise that resolves to a GeoJSON FeatureCollection
*
*/
const loadGeoPackageFile = async (file: ArrayBuffer) => {
const bytes = new Uint8Array(file);
const gpkg = await GeoPackageAPI.open(bytes);

const tables = gpkg.getFeatureTables();
const features: GeoJSON.Feature[] = [];
for (const tableName of tables) {
Copy link
Member

Choose a reason for hiding this comment

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

Ideally, the user should get a chance to choose which table(s) they want to load. Maybe that could be a future PR though :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Users are now able to choose which tables to load

const dao = gpkg.getFeatureDao(tableName);
const bbox = dao.getBoundingBox();
const iter = gpkg.queryForGeoJSONFeaturesInTable(tableName, bbox);
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how I feel about converting the data to GeoJSON. Have you looked in to https://github.com/richard-thomas/ol-load-geopackage ? (I'm not sure how it works under the hood, maybe it loads from GeoJSON too 😆 )

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now we are using the library you suggested for vector layers, but for raster layers we're still using ngageoint

for (const feat of iter) {
features.push(feat);
}
}
return { type: 'FeatureCollection', features };
};

/**
* Generalized file reader for different source types.
*
Expand Down Expand Up @@ -570,6 +596,30 @@ export const loadFile = async (fileInfo: {
throw new Error(`Failed to fetch ${filepath}`);
}

case 'GeoPackageSource': {
const cached = await getFromIndexedDB(filepath);
if (cached) {
return cached.file;
}

const geojson = await fetchWithProxies(
filepath,
model,
async response => {
const arrayBuffer = await response.arrayBuffer();
return await loadGeoPackageFile(arrayBuffer);
}
);

if (geojson) {
await saveToIndexedDB(filepath, geojson);
return geojson;
}

showErrorMessage('Network error', `Failed to fetch ${filepath}`);
throw new Error(`Failed to fetch ${filepath}`);
}

default: {
throw new Error(`Unsupported URL handling for source type: ${type}`);
}
Expand Down Expand Up @@ -636,6 +686,15 @@ export const loadFile = async (fileInfo: {
}
}

case 'GeoPackageSource': {
if (typeof file.content === 'string') {
const arrayBuffer = await stringToArrayBuffer(file.content);
return await loadGeoPackageFile(arrayBuffer);
} else {
throw new Error('Invalid file format for GeoPackage content.');
}
}

default: {
throw new Error(`Unsupported source type: ${type}`);
}
Expand Down
95 changes: 95 additions & 0 deletions packages/base/style/icons/geopackage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion packages/schema/src/schema/project/jgis.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"VideoSource",
"ImageSource",
"ShapefileSource",
"GeoTiffSource"
"GeoTiffSource",
"GeoPackageSource"
]
},
"jGISLayer": {
Expand Down
24 changes: 24 additions & 0 deletions packages/schema/src/schema/project/sources/geoPackageSource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"type": "object",
"description": "GeoPackageSource",
"title": "IGeoPackageSource",
"required": ["path"],
"additionalProperties": false,
"properties": {
"path": {
"type": "string",
"description": "The path to the GeoPackage source"
},
"attribution": {
"type": "string",
"readOnly": true,
"description": "The attribution for the GeoPackage source.",
"default": ""
},
"projection": {
"type": "string",
"description": "The projection information for the GeoPackage data (optional).",
"default": "EPSG:4326"
}
}
}
1 change: 1 addition & 0 deletions packages/schema/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './_interface/project/jgis';

// Sources
export * from './_interface/project/sources/geoPackageSource';
export * from './_interface/project/sources/geoTiffSource';
export * from './_interface/geojsonsource';
export * from './_interface/project/sources/imageSource';
Expand Down
1 change: 1 addition & 0 deletions python/jupytergis_core/jupytergis_core/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .interfaces.project.sources.imageSource import IImageSource # noqa
from .interfaces.project.sources.geoTiffSource import IGeoTiffSource # noqa
from .interfaces.project.sources.rasterDemSource import IRasterDemSource # noqa
from .interfaces.project.sources.geoPackageSource import IGeoPackageSource # noqa

from .interfaces.processing.buffer import IBuffer # noqa

Expand Down
4 changes: 4 additions & 0 deletions python/jupytergis_core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,14 @@
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@types/react-addons-linked-state-mixin": "^0.14.22",
"buffer": "^6.0.3",
"copy-webpack-plugin": "^10.0.0",
"css-loader": "^6.7.1",
"mkdirp": "^1.0.3",
"npm-run-all": "^4.1.5",
"process": "^0.11.10",
"rimraf": "^3.0.2",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"typescript": "^5",
"webpack": "^5.76.3",
Expand Down Expand Up @@ -105,6 +108,7 @@
},
"extension": true,
"outputDir": "jupytergis_core/labextension",
"webpackConfig": "webpack.config.js",
"sharedPackages": {
"@jupytergis/base": {
"singleton": true,
Expand Down
4 changes: 4 additions & 0 deletions python/jupytergis_core/src/@types/wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.wasm' {
const url: string;
export default url;
}
Loading
Loading