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.

tsx
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.

typescript
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:

typescript
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

typescript
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.

tsx
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.

typescript
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:

typescript
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

typescript
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

typescript
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 fetchLandStyle once and reuse across map instances. The npm helper caches per session automatically.
  • Use minzoom / maxzoom from 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.
Layer additional datasets (PLSS, CDL, parcels, etc.) by listing them in the layers param. See the Google Maps integration guide if you need to support both platforms.