MapLibre Integration
Add LandMapMagic layers to your MapLibre (or Mapbox GL JS) map. Fetch a renderer-ready style for any combination of layers, then drop it into setStyle or merge into your existing style.
Quick Start
The unified styles endpoint returns a complete MapLibre Style Spec v8 document for any combination of layers. Sources, layers, glyphs, attribution, and label deduplication are all handled server-side.
1. One-line install with landmapmagic (Recommended)
The npm package is the simplest way: it fetches the right style and mounts it for you. See the One-line install guide.
import { LandMap } from 'landmapmagic';
export default function App() {
return (
<LandMap
apiKey="YOUR_API_KEY"
layers={['clu', 'counties']}
initialView={{ center: [-93.5, 42.0], zoom: 12 }}
/>
);
}2. Vanilla MapLibre — fetch + setStyle
Already running your own map? Fetch the style and apply it directly. The response is a complete v8 doc, so setStyle just works.
const response = await fetch(
'https://api.landmapmagic.com/v1/styles?' +
new URLSearchParams({
key: 'YOUR_API_KEY',
target: 'maplibre',
layers: 'clu',
})
);
const style = await response.json();
map.setStyle(style);3. Merge into an existing style
If you already have a basemap loaded, merge sources and layers instead of replacing the style:
const response = await fetch(
'https://api.landmapmagic.com/v1/styles?' +
new URLSearchParams({
key: 'YOUR_API_KEY',
target: 'maplibre',
layers: 'clu,counties',
})
);
const style = await response.json();
for (const [id, source] of Object.entries(style.sources)) {
if (!map.getSource(id)) map.addSource(id, source);
}
for (const layer of style.layers) {
if (!map.getLayer(layer.id)) map.addLayer(layer);
}Complete Example
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [-93.5, 42.0],
zoom: 12,
});
map.on('load', async () => {
const response = await fetch(
'https://api.landmapmagic.com/v1/styles?' +
new URLSearchParams({
key: 'YOUR_API_KEY',
target: 'maplibre',
layers: 'clu',
})
);
const style = await response.json();
for (const [id, source] of Object.entries(style.sources)) {
if (!map.getSource(id)) map.addSource(id, source);
}
for (const layer of style.layers) {
if (!map.getLayer(layer.id)) map.addLayer(layer);
}
});Mapbox GL JS
The MapLibre style spec v8 doc returned by target=maplibre is a drop-in style for Mapbox GL JS too. Use the same fetch + setStyle pattern, or import the React adapter from landmapmagic/mapbox.
import { LandMap } from 'landmapmagic/mapbox';
<LandMap
apiKey="YOUR_API_KEY"
mapboxAccessToken="pk...."
layers={['clu']}
initialView={{ center: [-93.5, 42.0], zoom: 12 }}
/>;Customizing Styles
The styles API has no override query parameters — colors, halos, and label fields are mutated client-side after fetch. See the Customizing Styles guide for renderer-specific recipes.
const style = await fetchLandStyle({
apiKey: 'YOUR_API_KEY',
target: 'maplibre',
layers: ['clu'],
});
for (const layer of style.layers) {
if (layer.id === 'clu-outline' && layer.paint) {
layer.paint['line-color'] = '#ff6b35';
layer.paint['line-width'] = 3;
}
}
map.setStyle(style);Hover Effects
Outline layers ship with feature-state-aware paint expressions. Wire up MapLibre's feature state hooks:
let hoveredFeatureId = null;
map.on('mousemove', 'clu-outline', (e) => {
if (e.features.length > 0) {
if (hoveredFeatureId !== null) {
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredFeatureId },
{ hover: false }
);
}
hoveredFeatureId = e.features[0].id;
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredFeatureId },
{ hover: true }
);
}
});
map.on('mouseleave', 'clu-outline', () => {
if (hoveredFeatureId !== null) {
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredFeatureId },
{ hover: false }
);
}
hoveredFeatureId = null;
});Click Events for Feature Info
map.on('click', 'clu-outline', (e) => {
if (e.features.length > 0) {
const feature = e.features[0];
const acres = feature.properties.calcacres;
const id = feature.properties.id;
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
<h3>CLU Field</h3>
<p><strong>Field ID:</strong> ${id}</p>
<p><strong>Acres:</strong> ${acres?.toFixed(2)}</p>
`)
.addTo(map);
}
});
map.on('mouseenter', 'clu-outline', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clu-outline', () => {
map.getCanvas().style.cursor = '';
});Layer Visibility Toggle
map.setLayoutProperty('clu-outline', 'visibility', 'none');
map.setLayoutProperty('clu-labels', 'visibility', 'none');
map.setLayoutProperty('clu-outline', 'visibility', 'visible');
map.setLayoutProperty('clu-labels', 'visibility', 'visible');Performance Tips
- Cache the style response — call
fetchLandStyleonce and reuse across map instances. The npm helper caches per session automatically. - Use
minzoom/maxzoomfrom the returned source — they come straight from the authoritative product catalog. - Debounce interactions — throttle hover/click handlers if you have many features.
- Layer ordering — merge LandMap layers after your basemap but before your top labels.
Troubleshooting
- Tiles not loading — check API key validity and that your domain is whitelisted.
- 401/403 errors — confirm your domain is whitelisted in the dashboard.
- No features visible — zoom into the layer's minzoom (e.g. CLU tiles start at Z11).
- Style conflicts — rename layer IDs only if they collide with your existing style; the API uses stable, namespaced ids.
layers param. See the Google Maps integration guide if you need to support both platforms.