Skip to main content

Map Visualization with WMS

API key must stay on the server

Browser-based map libraries (Leaflet, OpenLayers, Mapbox GL) cannot add custom Authorization headers to WMS tile requests. Do not expose your API key in client-side JavaScript.

Use a backend proxy that attaches the key server-side — see the GeoServer Proxy Guide for a complete, runnable Python example.

Scenario: You want to embed a Qarta WMS layer as an overlay on an interactive web map (using Leaflet, OpenLayers, or Mapbox).

Workflow:

  1. Discover available layers with GetCapabilities
  2. Build a WMS tile layer URL
  3. Add it to your web map as an overlay
  4. Optionally add a legend and click-to-query functionality

Prerequisites: A running GeoServer proxy at http://localhost:8080.


Step 1: Discover Available Layers

Call GetCapabilities to find available layers:

curl -X GET "https://graph.quarticle.ro/graph/layers/wms/GetCapabilities" \
-H "Authorization: YOUR_API_KEY" \
-G \
-d "service=WMS" \
-d "version=1.3.0" \
-d "request=GetCapabilities" \
| grep -o '<Name>[^<]*</Name>' | head -20

Step 2: Add Layer to Leaflet Map

Embed a WMS layer in Leaflet, routing tile requests through the proxy:

import L from 'leaflet';

const PROXY_URL = 'http://localhost:8080/wms/GetMap';
const layer = 'GRAPHRASTER:light_pollution';

// Create map
const map = L.map('map').setView([40.7128, -74.0060], 10);

// Base layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);

// WMS overlay — requests go to the proxy, which adds the API key server-side
const wmsLayer = L.tileLayer.wms(PROXY_URL, {
layers: layer,
styles: '',
format: 'image/png',
transparent: true,
srs: 'EPSG:3857'
}).addTo(map);

The proxy receives each tile request from Leaflet, attaches the Authorization header, and forwards it to Qarta. See the GeoServer Proxy Guide for setup instructions.


Step 3: Add OpenLayers Integration

import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import TileWMS from 'ol/source/TileWMS';
import OSM from 'ol/source/OSM';
import { fromLonLat } from 'ol/proj';

const PROXY_URL = 'http://localhost:8080/wms/GetMap';
const layer = 'GRAPHRASTER:light_pollution';

const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new OSM()
}),
new TileLayer({
source: new TileWMS({
url: PROXY_URL,
params: {
LAYERS: layer,
STYLES: '',
FORMAT: 'image/png',
TRANSPARENT: 'true',
VERSION: '1.3.0'
},
serverType: 'geoserver'
})
})
],
view: new View({
center: fromLonLat([-74.0060, 40.7128]),
zoom: 10
})
});

Step 4: Add Legend

Fetch and display the layer legend through the proxy:

async function addLegend(layer) {
const params = new URLSearchParams({
service: 'WMS',
version: '1.0.0',
request: 'GetLegendGraphic',
layer: layer,
format: 'image/png',
width: '40',
height: '40'
});

const response = await fetch(
`http://localhost:8080/wms/GetLegendGraphic?${params}`
);

const blob = await response.blob();
const url = URL.createObjectURL(blob);

const legendContainer = document.querySelector('.legend');
legendContainer.innerHTML = `
<h4>${layer}</h4>
<img src="${url}" alt="Legend" />
`;
}

addLegend('GRAPHRASTER:light_pollution');

Step 5: Add Click-to-Query (GetFeatureInfo)

Query features at a map click, routing through the proxy:

map.on('click', async (e) => {
const layer = 'GRAPHRASTER:light_pollution';
const latlng = e.latlng;

// Convert map coordinates to WMS bbox (simplified example)
const bounds = map.getBounds();
const bbox = `${bounds.getWest()},${bounds.getSouth()},${bounds.getEast()},${bounds.getNorth()}`;

const params = new URLSearchParams({
service: 'WMS',
version: '1.3.0',
request: 'GetFeatureInfo',
layers: layer,
query_layers: layer,
styles: '',
srs: 'EPSG:3857',
bbox: bbox,
width: map.getSize().x,
height: map.getSize().y,
x: Math.round((latlng.lng - bounds.getWest()) / (bounds.getEast() - bounds.getWest()) * map.getSize().x),
y: Math.round((bounds.getNorth() - latlng.lat) / (bounds.getNorth() - bounds.getSouth()) * map.getSize().y),
info_format: 'application/json',
feature_count: '5'
});

const response = await fetch(
`http://localhost:8080/wms/GetFeatureInfo?${params}`
);

const result = await response.json();
console.log('Features at click:', result.features);

// Display in popup
L.popup()
.setLatLng(latlng)
.setContent(`<pre>${JSON.stringify(result.features[0]?.properties, null, 2)}</pre>`)
.openOn(map);
});

Complete Example: Interactive Map

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<style>
#map { height: 600px; }
.legend { position: absolute; bottom: 10px; right: 10px; background: white; padding: 10px; border-radius: 5px; box-shadow: 0 0 15px rgba(0,0,0,0.2); }
</style>
</head>
<body>
<div id="map"></div>
<div class="legend"></div>

<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script type="module" src="map.js"></script>
</body>
</html>

Use Cases

  • Real estate: Display property risk layers (flood, seismic) on top of property listings
  • Environmental monitoring: Show temperature, pollution, or vegetation indices as map overlays
  • Urban planning: Visualize land use, zoning, or infrastructure layers
  • Climate analysis: Display climate or weather data on interactive maps

Next Steps