Map Visualization with WMS
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:
- Discover available layers with
GetCapabilities - Build a WMS tile layer URL
- Add it to your web map as an overlay
- 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
- Node.js (server-side)
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
// Run this on your server or terminal — not in the browser.
// GetCapabilities is a one-time discovery call to find available layer names.
async function getAvailableLayers() {
const response = await fetch(
'http://localhost:8080/wms/GetCapabilities?' +
new URLSearchParams({
service: 'WMS',
version: '1.3.0',
request: 'GetCapabilities'
})
);
const xml = await response.text();
const { DOMParser } = await import('xmldom');
const doc = new DOMParser().parseFromString(xml, 'text/xml');
const layers = [];
const nameElements = doc.getElementsByTagName('Name');
for (let i = 0; i < nameElements.length; i++) {
const text = nameElements[i].textContent;
if (text && text.includes(':')) {
layers.push(text);
}
}
return layers;
}
const layers = await getAvailableLayers();
console.log('Available layers:', layers);
// Output: ['GRAPHRASTER:light_pollution', 'GRAPHRASTER:temperature', ...]
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
- OpenLayers
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:
- JavaScript
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:
- Leaflet with Query
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
- HTML
- map.js
<!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>
import L from 'https://esm.sh/leaflet';
const LAYER = 'GRAPHRASTER:light_pollution';
const PROXY_URL = 'http://localhost:8080/wms'; // GeoServer proxy
// 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'
}).addTo(map);
// WMS overlay (via proxy)
const wmsLayer = L.tileLayer.wms(`${PROXY_URL}/GetMap`, {
layers: LAYER,
transparent: true,
format: 'image/png'
}).addTo(map);
// Add legend
const legendContainer = document.querySelector('.legend');
legendContainer.innerHTML = `<h4>Loading legend...</h4>`;
async function addLegend() {
const response = await fetch(
`${PROXY_URL}/GetLegendGraphic?` + new URLSearchParams({
request: 'GetLegendGraphic',
layer: LAYER,
format: 'image/png'
})
);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
legendContainer.innerHTML = `<h4>${LAYER}</h4><img src="${url}" style="max-width: 200px;" />`;
}
addLegend();
// Click to query (via proxy)
map.on('click', async (e) => {
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((e.latlng.lng - bounds.getWest()) / (bounds.getEast() - bounds.getWest()) * map.getSize().x),
y: Math.round((bounds.getNorth() - e.latlng.lat) / (bounds.getNorth() - bounds.getSouth()) * map.getSize().y),
info_format: 'application/json',
feature_count: '5'
});
const result = await fetch(`${PROXY_URL}/GetFeatureInfo?${params}`).then(r => r.json());
L.popup()
.setLatLng(e.latlng)
.setContent(`<pre>${JSON.stringify(result.features?.[0]?.properties, null, 2)}</pre>`)
.openOn(map);
});
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
- GeoServer Proxy Guide — Set up the Python proxy for WMS/WFS
- WMS Guide — Learn GetMap, GetCapabilities, GetFeatureInfo parameters
- Feature Querying Use Case — Query raw features with WFS
- GeoServer Overview — Understand the proxy and authentication model