MapLibre Integration

Add CLU field boundaries to your MapLibre map in under 5 minutes using the LandMapMagic API.

Quick Start

If you already have a MapLibre map instance, fetch the style endpoint and merge the source and layers into your map.

1. Fetch and Merge the Style (Recommended)

typescript
// Fetch the CLU style from the API
const response = await fetch(
  'https://api.landmapmagic.com/v1/styles/clu/style.json?key=YOUR_API_KEY'
);
const cluStyle = await response.json();

// Add the source
map.addSource('clu', cluStyle.sources.clu);

// Add all layers
cluStyle.layers.forEach(layer => {
  map.addLayer(layer);
});

2. Manual Source and Layer Definition

Alternatively, define the source and layers manually using MVT tile URLs:

typescript
// Add the MVT tile source
map.addSource('clu', {
  type: 'vector',
  tiles: [
    'https://api.landmapmagic.com/v1/tiles/clu/{z}/{x}/{y}.mvt?key=YOUR_API_KEY'
  ],
  minzoom: 11,
  maxzoom: 15,
  attribution: 'USDA FSA CLU / LandMapMagic'
});

// Add the outline layer
map.addLayer({
  id: 'clu-outline',
  type: 'line',
  source: 'clu',
  'source-layer': 'clu',
  paint: {
    'line-color': '#fde047',
    'line-width': 2,
    'line-opacity': 0.9
  },
  layout: { 'line-cap': 'round', 'line-join': 'round' },
  minzoom: 11
});

// Add the labels layer (optional)
map.addLayer({
  id: 'clu-labels',
  type: 'symbol',
  source: 'clu',
  'source-layer': 'clu_labels',
  layout: {
    'text-field': ['concat', ['get', 'calcacres'], ' ac'],
    'text-size': 14,
    'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
    'text-anchor': 'center'
  },
  paint: {
    'text-color': '#000000',
    'text-halo-color': '#ffffff',
    'text-halo-width': 3
  },
  minzoom: 13
});

Complete Example

typescript
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

// Create your map
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/clu/style.json?key=YOUR_API_KEY'
  );
  const cluStyle = await response.json();

  map.addSource('clu', cluStyle.sources.clu);
  cluStyle.layers.forEach(layer => map.addLayer(layer));
});

Customizing Colors

Pass query parameters to the style endpoint:

typescript
const response = await fetch(
  'https://api.landmapmagic.com/v1/styles/clu/style.json?key=YOUR_API_KEY' +
  '&borderColor=ff6b35&hoverColor=ffd700&labels=false'
);
  • borderColor — Hex color for field boundaries (default: fde047)
  • hoverColor — Hex color for hover state (default: ffd700)
  • labelColor — Hex color for acreage labels (default: 000000)
  • labels — Show/hide labels (true or false, default: true)

Adding Hover Effects

The style includes hover state support. Enable it with feature state:

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);
  }
});

// Change cursor on hover
map.on('mouseenter', 'clu-outline', () => {
  map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clu-outline', () => {
  map.getCanvas().style.cursor = '';
});

Layer Visibility Toggle

typescript
// Hide CLU layers
map.setLayoutProperty('clu-outline', 'visibility', 'none');
map.setLayoutProperty('clu-labels', 'visibility', 'none');

// Show CLU layers
map.setLayoutProperty('clu-outline', 'visibility', 'visible');
map.setLayoutProperty('clu-labels', 'visibility', 'visible');

Performance Tips

  • Cache the style response — fetch /v1/styles/clu/style.json once and reuse across map instances
  • Use minzoom — CLU tiles start at zoom 11; don't render at lower zooms
  • Debounce interactions — throttle hover/click handlers if you have many features
  • Layer ordering — add CLU layers after your base layers but before 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 to level 11+ (CLU tiles start at Z11)
  • Style conflicts — rename layer IDs when adding if they conflict with your existing style
Add multiple datasets (PLSS, CDL) by fetching their respective style endpoints. Check out the Google Maps integration guide if you need to support both platforms.