How to Implement Drag-and-Drop File Handling for Vector and Raster Formats in GeoLibre
GeoLibre processes dragged files through a store-driven pipeline that routes vector data to DuckDB-WASM for client-side conversion and raster data to either MapLibre directly or a FastAPI side-car for heavy processing.
GeoLibre is an open-source GIS application built with React, MapLibre, and Tauri that manages geospatial data through a centralized Zustand store. Implementing drag-and-drop file handling requires coordinating native HTML5 events, format detection, and specialized ingestion paths for vector versus raster data. This guide explains the complete architecture using actual source files from the opengeos/GeoLibre repository.
The Five-Step Ingestion Pipeline
When a user drops files onto the application canvas, GeoLibre executes a strict separation of concerns: UI components capture events, processing engines transform data, and the store drives all rendering updates. This pipeline ensures that adding support for new formats requires changing only specific conversion modules without touching the map rendering logic.
Capture Native Drop Events
The drag-and-drop entry point lives in apps/geolibre-desktop/src/components/layout/DesktopShell.tsx. This root-level component attaches HTML5 dragenter, dragover, and drop event listeners to the entire application shell.
The handleDrop function extracts the DataTransfer.files list from the native event and passes it to the ingestion pipeline. By capturing events at the shell level rather than individual components, GeoLibre ensures consistent behavior regardless of where users drop files on the canvas.
Detect Vector and Raster Formats
Once files are captured, GeoLibre inspects MIME types and extensions to determine the processing route. The detection logic resides in apps/geolibre-desktop/src/components/panels/LayerPanel.tsx, specifically within the handleLayerDrop and handleGroupHeaderDrop functions.
Vector formats—including .geojson, .shp, .gpkg, and .kmz—are flagged for the DuckDB-WASM pipeline. Raster formats—including .tif, .png, .jpg, and .pmtiles—are routed to either the client-side MapLibre source builder or the Python side-car. The utility functions in packages/core/src/lib/utils/file-utils.ts centralize extension-to-type mappings, making it straightforward to register new formats.
Convert Vector Data with DuckDB-WASM
Vector processing happens entirely in the browser through the stReadAndConvert function exported from packages/processing/src/wasm-convert.ts. This module initializes DuckDB-WASM with spatial capabilities using INSTALL spatial; LOAD spatial; and invokes the ST_Read SQL function to parse the incoming file.
The conversion returns standard GeoJSON, which is then wrapped in a GeoLibreLayer record and pushed into the global Zustand store via addGeoJsonLayer. This architecture avoids server round-trips for common GIS files, keeping sensitive data on the client machine while maintaining interactive performance.
Handle Raster Ingestion
Raster processing splits based on complexity and file size. Simple rasters are converted to object URLs and passed directly to addRasterLayer, which creates a MapLibre raster source type immediately.
For large GeoTIFFs, multi-band imagery, or files requiring reprojection, GeoLibre delegates to the FastAPI side-car defined in backend/geolibre_server/app/raster.py. The client calls endpoints such as /raster/info for metadata extraction and /raster/tiles for dynamic tiling, then uses the returned tile URLs as the source for the raster layer. This hybrid approach leverages the side-car only when browser-based processing would exceed memory or computational limits.
Synchronize the Map Canvas
The final step occurs in packages/map/src/MapCanvas.tsx, which subscribes to the Zustand store's layers state. When new layer records appear—whether from vector conversion or raster ingestion—the component triggers MapController.syncLayers to reconcile MapLibre's sources and layers with the current store state.
This store-driven approach means the drop handler never manipulates the map directly; it only mutates state, and the UI updates reactively.
Implementation Example
Below is a minimal React hook that mirrors the logic in DesktopShell.tsx but can be dropped into any custom import panel. It demonstrates vector conversion via DuckDB-WASM and the dual-path raster handling.
import { useCallback } from 'react';
import { addGeoJsonLayer, addRasterLayer } from '@geolibre/core';
import { stReadAndConvert } from '@geolibre/processing';
import { uploadRaster } from '@geolibre/backend';
function useFileDrop() {
const handleDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
for (const file of files) {
const ext = file.name.split('.').pop()?.toLowerCase();
// Vector pipeline
if (['geojson', 'shp', 'gpkg', 'kmz'].includes(ext!)) {
const arrayBuf = await file.arrayBuffer();
const geojson = await stReadAndConvert(arrayBuf, ext!);
addGeoJsonLayer(geojson, file.name);
continue;
}
// Raster pipeline
if (['tif', 'tiff', 'png', 'jpg', 'jpeg', 'pmtiles'].includes(ext!)) {
const url = URL.createObjectURL(file);
addRasterLayer({ id: file.name, source: { type: 'raster', url } });
// For large rasters, uncomment to delegate to side-car:
// await uploadRaster(file);
continue;
}
console.warn(`Unsupported file type: ${file.name}`);
}
}, []);
return { handleDrop };
}
Extending the Pipeline
To add support for additional formats without modifying core UI components, focus on three extension points:
- File type detection: Extend the
detectFileTypeutility inpackages/core/src/lib/utils/file-utils.tswith new MIME type and extension mappings. - Custom processing: Register new WASM tools in
packages/processingand expose them through thewhitebox-menu-catalogfor client-side execution. - Side-car services: Implement additional FastAPI routes in
backend/geolibre_server(e.g., for NetCDF or HDF5) and expose them via the Tauri command wrapper inapps/geolibre-desktop/src-tauri/src/lib.rs.
Summary
- Event capture happens at the application root in
DesktopShell.tsxusing native HTML5 drag events. - Format detection routes files to vector or raster pipelines based on extensions and MIME types.
- Vector conversion uses DuckDB-WASM's
ST_Readfunction inwasm-convert.tsto produce GeoJSON without server interaction. - Raster handling splits between direct MapLibre sources for simple files and FastAPI side-car endpoints (
/raster/tiles) for heavy processing. - Store synchronization ensures the map updates automatically via
MapCanvas.tsxandsyncLayers, keeping UI code decoupled from data logic.
Frequently Asked Questions
What file formats does GeoLibre support for drag-and-drop?
GeoLibre supports vector formats including GeoJSON, Shapefile, GeoPackage, and KMZ through the DuckDB-WASM pipeline. For rasters, it accepts GeoTIFF, PNG, JPEG, and PMTiles, with large rasters automatically delegating to the FastAPI side-car for tiling and processing.
Why does GeoLibre use DuckDB-WASM for vector processing instead of server-side conversion?
DuckDB-WASM runs entirely in the browser, eliminating network latency and keeping sensitive geospatial data on the local machine. The ST_Read function in wasm-convert.ts can parse complex vector formats without requiring a backend service, making the application feel instantaneous while maintaining privacy.
How does GeoLibre handle large raster files that exceed browser memory limits?
When raster files exceed client-side processing thresholds, GeoLibre invokes the FastAPI side-car via endpoints defined in backend/geolibre_server/app/raster.py. The side-car generates tiles on demand through /raster/tiles, allowing the application to display massive GeoTIFFs without loading the full dataset into browser memory.
Can I add custom file format support without modifying the core codebase?
Yes. You can extend the detectFileType utility in file-utils.ts to recognize new extensions, add corresponding conversion logic in packages/processing for client-side formats, or implement new FastAPI routes for server-heavy formats. The Tauri command wrapper in src-tauri/src/lib.rs exposes these capabilities to the frontend without changing the React component structure.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →