GPU-accelerated Cloud-Optimized GeoTIFF visualization with automatic reprojection.
bun add @developmentseed/deck.gl-raster geotiff-geokeys-to-proj4
<script setup lang="ts">
import { VMap, VLayerDeckglCOG } from '@geoql/v-maplibre';
const mapOptions = {
style: 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json',
center: [31.5, 30.0],
zoom: 8,
};
// Sentinel-2 RGB imagery COG
const COG_URL = 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif';
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VLayerDeckglCOG
id="cog-layer"
:geotiff="COG_URL"
@geotiff-load="(tiff, { geographicBounds }) => console.log('Loaded:', geographicBounds)"
/>
</VMap>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique layer identifier |
geotiff | string | ArrayBuffer | Blob | required | GeoTIFF source URL or data |
tileSize | number | 256 | Tile size in pixels |
maxZoom | number | - | Maximum zoom level |
minZoom | number | 0 | Minimum zoom level |
opacity | number | 1 | Layer opacity (0-1) |
visible | boolean | true | Layer visibility |
pickable | boolean | false | Enable picking |
debug | boolean | false | Show debug overlay |
| Event | Payload | Description |
|---|---|---|
@geotiff-load | (tiff, { geographicBounds }) | GeoTIFF loaded with bounds |
@click | PickingInfo | Clicked on layer |
@hover | PickingInfo | Hovering over layer |
<script setup lang="ts">
import { shallowRef } from 'vue';
import type { Map } from 'maplibre-gl';
const mapInstance = shallowRef<Map | null>(null);
const handleGeotiffLoad = (
_tiff: unknown,
{ geographicBounds }: { geographicBounds: { west: number; south: number; east: number; north: number } }
) => {
const { west, south, east, north } = geographicBounds;
mapInstance.value?.fitBounds(
[[west, south], [east, north]],
{ padding: 40, duration: 1000 }
);
};
</script>
<template>
<VMap :options="mapOptions" @loaded="(map) => mapInstance = map">
<VLayerDeckglCOG
id="cog"
:geotiff="cogUrl"
@geotiff-load="handleGeotiffLoad"
/>
</VMap>
</template>
Visualize NLCD land use classification data:
<VLayerDeckglCOG
id="landcover"
:geotiff="nlcdUrl"
:opacity="0.7"
/>
Client-side COG mosaic layer that renders multiple Cloud-Optimized GeoTIFFs from STAC items. All rendering happens in the browser with no server required.
bun add @developmentseed/deck.gl-raster @developmentseed/deck.gl-geotiff geotiff flatbush proj4
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import {
VMap,
VLayerDeckglMosaic,
type MosaicSource,
type MosaicRenderMode,
} from '@geoql/v-maplibre';
const mapOptions = {
style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
center: [-104.9903, 39.7392],
zoom: 10,
};
const renderMode = ref<MosaicRenderMode>('trueColor');
const stacItems = ref<MosaicSource[]>([]);
// Fetch STAC items from Microsoft Planetary Computer
async function fetchSTACItems() {
const params = {
collections: 'naip',
bbox: '-106.6,38.7,-104.6,40.4',
datetime: '2023-01-01/2023-12-31',
limit: '1000',
};
const response = await fetch(
'https://planetarycomputer.microsoft.com/api/stac/v1/search?' +
new URLSearchParams(params)
);
const { features } = await response.json();
stacItems.value = features;
}
onMounted(fetchSTACItems);
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VLayerDeckglMosaic
v-if="stacItems.length > 0"
id="naip-mosaic"
:sources="stacItems"
:render-mode="renderMode"
/>
</VMap>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique layer identifier |
sources | MosaicSource[] | required | Array of STAC-like items with bbox and COG asset URLs |
renderMode | 'trueColor' | 'falseColor' | 'ndvi' | 'custom' | 'trueColor' | Render mode for band combination |
customRenderModules | Function | - | Custom render modules (only used when renderMode is 'custom') |
colormapData | Uint8ClampedArray | cfastie | Custom colormap texture data for NDVI (256 RGBA colors) |
maxCacheSize | number | Infinity | Maximum number of tiles to cache |
beforeId | string | - | Insert layer before this layer ID |
opacity | number | 1 | Layer opacity (0-1) |
visible | boolean | true | Layer visibility |
pickable | boolean | false | Enable picking |
| Event | Payload | Description |
|---|---|---|
@source-load | MosaicSource | COG source loaded successfully |
@error | (Error, MosaicSource?) | Error loading a COG source |
@click | PickingInfo | Clicked on layer |
@hover | PickingInfo | Hovering over layer |
| Mode | Description |
|---|---|
trueColor | RGB composite (bands 1, 2, 3) |
falseColor | False Color Infrared - NIR, Red, Green (bands 4, 1, 2) - highlights vegetation |
ndvi | Normalized Difference Vegetation Index with cfastie colormap |
custom | Use customRenderModules prop for custom band math |
interface MosaicSource {
bbox: [number, number, number, number]; // [west, south, east, north]
assets: {
image: {
href: string; // URL to the COG file
};
};
}
This matches the STAC Item format, so you can pass STAC search results directly.
The component includes built-in support for:
For other CRS, you'll need to register them with proj4 before using.
For Inverse Distance Weighting interpolation (temperature, pollution, continuous values), use maplibre-gl-interpolate-heatmap:
bun add maplibre-gl-interpolate-heatmap
This is different from deck.gl's density heatmap - it calculates weighted averages rather than point density. See the live example for implementation details.