# How to Implement Drag-and-Drop File Handling for Vector and Raster Formats in GeoLibre

> Implement drag-and-drop file handling for vector and raster formats in GeoLibre. Learn how GeoLibre processes data client-side or via a FastAPI side-car.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/DesktopShell.tsx) but can be dropped into any custom import panel. It demonstrates vector conversion via DuckDB-WASM and the dual-path raster handling.

```tsx
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 `detectFileType` utility in [`packages/core/src/lib/utils/file-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/lib/utils/file-utils.ts) with new MIME type and extension mappings.
- **Custom processing**: Register new WASM tools in `packages/processing` and expose them through the `whitebox-menu-catalog` for 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 in [`apps/geolibre-desktop/src-tauri/src/lib.rs`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs).

## Summary

- **Event capture** happens at the application root in [`DesktopShell.tsx`](https://github.com/opengeos/GeoLibre/blob/main/DesktopShell.tsx) using 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_Read` function in [`wasm-convert.ts`](https://github.com/opengeos/GeoLibre/blob/main/wasm-convert.ts) to 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.tsx`](https://github.com/opengeos/GeoLibre/blob/main/MapCanvas.tsx) and `syncLayers`, 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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/src-tauri/src/lib.rs) exposes these capabilities to the frontend without changing the React component structure.