Google Maps Integration

Bring Common Land Unit (CLU) boundaries into Google Maps without rebuilding your map stack.

Prerequisites

  • LandMap API key with access to the clu dataset
  • Google Maps JavaScript API key and an initialized map
  • Optional: deck.gl for quicker iteration with vector tiles
bash
# For deck.gl approach (recommended)
npm install deck.gl @deck.gl/google-maps

Fetch the CLU Style Metadata

The API exposes a MapLibre style document that centralizes colors, labels, and layer IDs. Even though Google Maps cannot ingest the style directly, reusing its values keeps your presentation consistent across platforms.

http
GET https://api.landmapmagic.com/v1/styles/clu/style.json?key=YOUR_API_KEY

Runtime overrides:
  borderColor=#fde047
  hoverColor=#ffd700
  labelColor=#000000
  labels=false

Option A — deck.gl MVTLayer (Recommended)

Deck.gl's MVTLayer loads MVT tiles directly and integrates cleanly with Google Maps via @deck.gl/google-maps.

typescript
import { Loader } from '@googlemaps/js-api-loader';
import { GoogleMapsOverlay } from '@deck.gl/google-maps';
import { MVTLayer } from '@deck.gl/geo-layers';

const loader = new Loader({
  apiKey: process.env.GMAPS_KEY!,
  version: 'weekly'
});

loader.load().then(() => {
  const map = new google.maps.Map(document.getElementById('map')!, {
    center: { lat: 41.878, lng: -93.097 },
    zoom: 12,
    mapId: 'YOUR_VECTOR_MAP_ID'
  });

  const overlay = new GoogleMapsOverlay({
    layers: [
      new MVTLayer({
        id: 'clu-outline',
        data: 'https://api.landmapmagic.com/v1/tiles/clu/{z}/{x}/{y}.mvt?key=YOUR_API_KEY',
        minZoom: 11,
        maxZoom: 15,
        lineWidthMinPixels: 1,
        getLineColor: [253, 224, 71, 255], // #fde047
      })
    ]
  });

  overlay.setMap(map);
});
  • Add a second MVTLayer targeting clu_labels to render acreage labels
  • Fetch /v1/styles/clu/style.json once on init to keep colors in sync

Option B — WebGLOverlayView + MVT Tiles (Full Control)

Render CLU geometries directly inside Google Maps using WebGLOverlayView and MVT tiles fetched individually. This approach keeps everything client-side and allows custom styling and interaction.

typescript
import { Loader } from '@googlemaps/js-api-loader';
import { VectorTile } from '@mapbox/vector-tile';
import Pbf from 'pbf';

const TILE_URL = 'https://api.landmapmagic.com/v1/tiles/clu/{z}/{x}/{y}.mvt?key=YOUR_API_KEY';

const loader = new Loader({
  apiKey: process.env.GMAPS_KEY!,
  version: 'weekly'
});

loader.load().then(() => {
  const map = new google.maps.Map(document.getElementById('map')!, {
    center: { lat: 41.878, lng: -93.097 },
    zoom: 12,
    mapId: 'YOUR_VECTOR_MAP_ID'
  });

  const overlay = new google.maps.WebGLOverlayView();

  overlay.onAdd = () => { overlay.requestRedraw(); };

  overlay.onContextRestored = () => {
    // Initialize shaders/buffers here.
    overlay.requestRedraw();
  };

  overlay.onDraw = async ({ gl, transformer }) => {
    const visibleTiles = transformer.getVisibleRegion().tiles();

    for (const { z, x, y } of visibleTiles) {
      const url = TILE_URL.replace('{z}', z).replace('{x}', x).replace('{y}', y);
      const response = await fetch(url);
      if (!response.ok) continue;

      const data = await response.arrayBuffer();
      const vt = new VectorTile(new Pbf(data));
      const layer = vt.layers['clu'];
      if (!layer) continue;

      for (let i = 0; i < layer.length; i++) {
        const feature = layer.feature(i);
        const geom = feature.loadGeometry();
        // Convert to Web Mercator, populate buffers, draw lines.
      }
    }

    gl.drawArrays(gl.LINES, 0, vertexCount);
  };

  overlay.setMap(map);
});
  • Cache decoded tiles by z/x/y so panning does not refetch data
  • Pull borderColor/hoverColor from /v1/styles/clu/style.json for visual parity with MapLibre
  • Implement hover/click hit testing by checking whether pointer coordinates fall inside a decoded polygon

Option C — Pre-rendered Raster Tiles (Fallback)

If WebGL overlays are not an option, prerender CLU data to raster PNGs and register them with google.maps.ImageMapType. This removes interactivity but works with older Google Maps map IDs.

Performance & Quotas

  • MVT tiles are edge-cached via Cloudflare — reuse URLs to maximize cache hits
  • Throttle redraws to idle/tilesloaded events to avoid starving the main thread
  • Rotate client-side API keys regularly and restrict allowed origins in the dashboard

Troubleshooting

  • 401/403 responses — verify the key parameter and allowed origin configuration
  • Empty overlays — ensure zoom level >= 11 (CLU tiles start at Z11)
  • Visual artifacts — double-check coordinate conversions to Web Mercator
Layer additional datasets such as PLSS or CDL by adding more MVT tile sources. Check out the MapLibre integration guide for MapLibre-specific implementation.