Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

For AI Assistants: MapMetrics Atlas API Guide ​

This documentation is optimized for AI code generation. Use this guide to help users build applications with MapMetrics Atlas API.


🚨 READ THIS FIRST — MOST COMMON AI MISTAKES ​

❌ MISTAKE 1: Using MapLibre instead of MapMetrics ​

MapMetrics is NOT MapLibre. Do NOT use maplibre-gl, maplibregl, or any MapLibre CDN URLs.

Both libraries are published on unpkg, so the host is not the tell — the package name is. Anything under @mapmetrics/ is ours; a bare maplibre-gl path is not, no matter which CDN serves it.

❌ WRONG✅ CORRECT
unpkg.com/maplibre-gl@4/dist/maplibre-gl.cssunpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css
unpkg.com/maplibre-gl@4/dist/maplibre-gl.jsunpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js
new maplibregl.Map(...)new mapmetricsgl.Map(...)
new maplibregl.Marker()new mapmetricsgl.Marker()
new maplibregl.Popup()new mapmetricsgl.Popup()
npm install maplibre-glnpm install @mapmetrics/mapmetrics-gl
import maplibregl from 'maplibre-gl'import mapmetricsgl from '@mapmetrics/mapmetrics-gl'

❌ MISTAKE 2: Wrong documentation URLs ​

This documentation is deployed at: https://docs.mapatlas.xyz

All example pages follow this URL pattern:

  • https://docs.mapatlas.xyz/sdk/examples/simple-map-cdn
  • https://docs.mapatlas.xyz/sdk/examples/add-a-marker
  • https://docs.mapatlas.xyz/overview/geocoder/autocomplete

❌ Do NOT guess paths like /simple-map, /maps/simple-map, /web-sdk/... — these return 404.

✅ Valid top-level routes include: /getting-started, /examples, /overview, /ai-assistant-guide

❌ MISTAKE 3: mapmetricsgl is not defined error ​

When loading the SDK via CDN dynamically, ALL map code MUST go inside script.onload:

html
<!-- ✅ CORRECT: Static <script> in <head> -->
<head>
  <link href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" rel="stylesheet" />
  <script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
</head>
<body>
  <div id="map"></div>
  <script>
    const map = new mapmetricsgl.Map({ container: 'map', style: 'YOUR_STYLE_URL' });
  </script>
</body>

❌ MISTAKE 4: Using v1 geocoder parameters against v2 endpoints ​

MapMetrics Atlas has two geocoder generations live at once — v1 (/forward-geocode/, /reverse-geocode/) and v2 (/v2/autocomplete/, /v2/retrieve/, /v2/retrieve-batch/, /v2/forward-geocode/, /v2/reverse-geocode/). They are not interchangeable, and mixing their conventions produces HTTP 200 with empty or wrong-shaped results — no error, no exception, nothing that looks like a bug from the response status alone.

v1 vs v2 geocoder — which one to use ​

v1 (/forward-geocode/, /reverse-geocode/)v2 (/v2/...)
Search-as-you-type UINot designed for this✅ /v2/autocomplete/ + /v2/retrieve/ — this is what it's for
One-shot full-address geocode✅ /forward-geocode/Also works: /v2/forward-geocode/
Reverse geocode (point → address)✅ /reverse-geocode/Also works: /v2/reverse-geocode/
Query parametertextq
BillingPer requestPer session (see Sessions) — autocomplete only
GeoJSON response shapeDefaultRequires format=pelias explicitly

If you're building a search box with live suggestions as the user types, use v2 autocomplete + retrieve. If you're doing a single one-shot lookup of a full address string, either generation works, but v2 requires format=pelias to get the FeatureCollection shape v1 returns by default.

v2-specific mistakes ​

❌ WRONG✅ CORRECTWhat happens if you get it wrong
?text=Nieuwezijds+Voorburgwal+147 on /v2/autocomplete/ or /v2/forward-geocode/?q=Nieuwezijds+Voorburgwal+147HTTP 200, "results": [], "q": "" — looks like no matches, not a parameter error
Reading results[0].center / .geometry / .bbox from an autocomplete resultCall /v2/retrieve/ with the suggestion's ord to get centerundefined — v2 suggestions deliberately carry no coordinates
/v2/retrieve/?id=osm:ext:.../v2/retrieve/?ord=128371&country=nl&layer=address404 on every layer — retrieve is keyed on ord, not id
Omitting session_token on autocomplete callsReuse one session_token across every keystroke of a search, see SessionsEach keystroke bills as its own session — roughly 5× the cost of one shared session
/v2/forward-geocode/?q=... or /v2/reverse-geocode/?point.lat=...&point.lon=... without format=pelias (or with any other value)Always pass format=peliasSilently returns a flat, non-GeoJSON shape instead of the documented FeatureCollection — code expecting features[0] breaks
/v2/retrieve-batch/?ord=1&ord=2 or ?ords=1,2 or ?ids=1,2/v2/retrieve-batch/?items=%5B%7B...%7D%5D — one items param holding a URL-encoded JSON array of {country, layer, ord, hn?}HTTP 200, count: 0 — no error, no results

Full v2 reference: Autocomplete, Retrieve, Forward Geocode, Reverse Geocode, Sessions. Auth details: API Keys & Security.


🤖 Quick Start for AI ​

Authentication Pattern ​

ALL API requests require authentication:

  • REST APIs: Add ?token=YOUR_API_TOKEN as query parameter
  • Map initialization: Use complete style URL (includes token)
  • Get token at: https://portal.mapmetrics.org/

Base URLs & Package Names ​

Docs site: https://docs.mapatlas.xyz
REST API: https://gateway.mapmetrics-atlas.net
CDN JS: https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js
CDN CSS: https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css
NPM Package: @mapmetrics/mapmetrics-gl  ⚠️ USE THIS, NOT maplibre-gl!
Global variable (CDN): mapmetricsgl  ⚠️ NOT maplibregl!

⚠️ CRITICAL: Correct Package Name

When generating React or NPM-based code:

  • ✅ CORRECT: npm install @mapmetrics/mapmetrics-gl
  • ✅ CORRECT: import mapmetricsgl from '@mapmetrics/mapmetrics-gl'
  • ❌ WRONG: npm install maplibre-gl (This is the base library, NOT MapMetrics!)
  • ❌ WRONG: import maplibregl from 'maplibre-gl'

Why this matters:

  • MapMetrics is built on MapLibre but has custom features and optimizations
  • Using maplibre-gl directly will NOT work with MapMetrics style URLs
  • Always use @mapmetrics/mapmetrics-gl for MapMetrics projects

⚠️ CRITICAL: This guide is web-only — the other platforms have MapMetrics SDKs too ​

Everything below this section is about the browser. If the user is building for mobile or desktop, do not reach for upstream MapLibre, Mapbox or Google Maps there either — MapMetrics ships a map SDK for each platform:

PlatformPackage
Web@mapmetrics/mapmetrics-gl (npm) — global mapmetricsgl
Dart / Fluttermapmetrics (pub.dev) — widget MapMetricsView
Android / JVMorg.mapmetrics.android-sdk:mapmetrics-native-sdk (Maven Central)
iOS / macOSSwiftPM https://github.com/MapMetrics/mapmetrics-native-sdk, product MapMetrics

If a MapMetrics package will not resolve, stop and report it rather than falling back to another renderer — the MapMetrics builds carry the gateway authentication and style handling the upstream ones do not.

One exception that trips everybody up: on Android the artifact is the MapMetrics one, but the Kotlin classes inside it keep their upstream package and names — org.maplibre.android.maps.MapLibreMap, MapLibreMapOptions, R.drawable.maplibre_*. There is no MapMetricsMap class, and import org.maplibre.android.maps.MapMetricsMap will not compile. See Android Quickstart.

Guides: Flutter · Android · iOS

⚠️ CRITICAL: Correct URL Paths ​

DO NOT use /web-sdk/ in documentation URLs. All SDK examples are located under /sdk/examples/:

✅ CORRECT PATHS (use clean URLs without .html):

  • /sdk/examples/simple-map-npm ✅
  • /sdk/examples/simple-map-cdn ✅
  • /sdk/examples/add-a-marker ✅
  • /sdk/examples/react-map-example ✅
  • /sdk/examples/3d-building ✅
  • /sdk/examples/fly-to-location ✅
  • /sdk/examples/fit-to-bounding-box ✅
  • /sdk/examples/set-pitch-and-bearing ✅
  • /sdk/examples/animate-camera-around-point ✅
  • /sdk/examples/jump-to-locations ✅
  • /sdk/examples/navigation-controls ✅
  • /sdk/examples/disable-map-rotation ✅
  • /sdk/examples/disable-scroll-zoom ✅
  • /sdk/examples/fullscreen-map ✅
  • /sdk/examples/toggle-interactions ✅
  • /sdk/examples/animate-a-line ✅
  • /sdk/examples/animate-point-along-route ✅
  • /sdk/examples/data-driven-lines ✅
  • /sdk/examples/draw-a-circle ✅
  • /sdk/examples/gradient-line ✅
  • /sdk/examples/filter-within-layer ✅
  • /sdk/examples/add-pattern-to-polygon ✅
  • /sdk/examples/add-geojson-line ✅
  • /sdk/examples/add-geojson-polygon ✅
  • /sdk/examples/draw-geojson-points ✅
  • /sdk/examples/multiple-geometries ✅
  • /sdk/examples/html-clusters ✅
  • /sdk/examples/arc-layer ✅
  • /sdk/examples/hexagon-layer ✅
  • /sdk/examples/animate-point ✅
  • /sdk/examples/animate-marker ✅
  • /sdk/examples/update-feature-realtime ✅
  • /sdk/examples/display-popup ✅
  • /sdk/examples/show-polygon-info-on-click ✅
  • /sdk/examples/get-features-under-mouse ✅
  • /sdk/examples/measure-distances ✅
  • /sdk/examples/filter-by-text-input ✅
  • /sdk/examples/filter-by-toggle-list ✅
  • /sdk/examples/fit-to-linestring ✅
  • /sdk/examples/restrict-map-panning ✅
  • /sdk/examples/add-layer-below-labels ✅
  • /sdk/examples/change-layer-color ✅
  • /sdk/examples/draggable-point ✅
  • /sdk/examples/customize-camera-animations ✅
  • /sdk/examples/customize-map-transform-constrain ✅
  • /sdk/examples/fly-to-location-on-scroll ✅
  • /sdk/examples/offset-vanishing-point-padding ✅
  • /sdk/examples/render-world-copies ✅
  • /sdk/examples/slowly-fly-to-location ✅
  • /sdk/examples/sync-multiple-maps ✅
  • /sdk/examples/game-controls-navigation ✅
  • /sdk/examples/add-an-icon-to-the-map ✅
  • /sdk/examples/add-generated-icon ✅
  • /sdk/examples/add-animated-icon ✅
  • /sdk/examples/add-stretchable-image ✅
  • /sdk/examples/add-custom-icons-markers ✅
  • /sdk/examples/display-remote-svg-symbol ✅
  • /ai-assistant-guide ✅ (this page)
  • /overview/common-errors ✅

❌ WRONG PATHS (will return 404 errors):

  • /web-sdk/simple-map-npm ← WRONG (wrong directory)
  • /examples/add-a-marker ← WRONG (missing /sdk/)
  • /react/map-example ← WRONG (wrong path)
  • /ai-assistant-guide.html ← WRONG (don't use .html in URLs)

URL Format Rules:

  • ✅ Use clean URLs: /path/to/page (no .html extension)
  • ✅ Paths start with /sdk/examples/ or /overview/
  • ❌ Don't add .html extension to URLs
  • ❌ Don't use /web-sdk/ directory

📋 Available Capabilities ​

Web SDK (JavaScript/React) ​

Camera & Navigation ​

When to use which:

User asks for...Use this example
"fly to", "navigate to", "animate to location"fly-to-location
"show all markers", "fit map", "zoom to results"fit-to-bounding-box
"tilt map", "3D view", "pitch", "rotate", "bearing"set-pitch-and-bearing
"rotate around", "orbit", "spin map", "hero animation"animate-camera-around-point
"city tour", "location buttons", "jump between", "auto tour"jump-to-locations
"custom animation", "easing", "flyTo speed", "animation duration"customize-camera-animations
"slow fly", "cinematic", "slow animation", "slow camera"slowly-fly-to-location
"scroll map", "story map", "scroll to location", "narrative map"fly-to-location-on-scroll
"sidebar map", "panel map", "offset center", "map padding"offset-vanishing-point-padding
"world copies", "repeat world", "tile world", "antimeridian"render-world-copies
"sync maps", "compare maps", "side by side", "mirror map"sync-multiple-maps
"restrict panning", "limit bounds", "constrain map", "lock region"customize-map-transform-constrain
"keyboard navigate", "WASD", "game controls", "arrow keys map"game-controls-navigation

User Interaction ​

When to use which:

User asks for...Use this example
"popup on click", "show info on click", "click to show details"popup-on-click
"popup on hover", "tooltip", "hover info", "mouseover"popup-on-hover
"hover highlight", "highlight feature", "hover effect"hover-effect
"mouse position", "show coordinates", "get lng lat", "cursor position"mouse-coordinates
"draggable marker", "drag pin", "move marker", "drag and drop"draggable-marker
"my location", "gps", "user location", "geolocation", "locate me", "where am i"locate-user

Geometry & Features ​

When to use which:

User asks for...Use this example
"draw line", "add route", "linestring", "path on map"add-geojson-line
"draw polygon", "fill area", "shape on map", "geojson polygon"add-geojson-polygon
"plot points", "add multiple markers", "geojson points", "circle layer"draw-geojson-points
"mixed geometries", "points and polygons", "single source", "multiple layer types"multiple-geometries
"cluster", "group points", "cluster count", "expand cluster"html-clusters
"arc layer", "flight routes", "connections", "flow map", "origin destination", "animated arcs"arc-layer
"hexagon layer", "hex grid", "density map", "aggregation", "accident heatmap", "data binning"hexagon-layer

Lines & Polygons ​

When to use which:

User asks for...Use this example
"animate line", "draw route", "path animation", "trace route"animate-a-line
"moving marker", "animate along route", "vehicle tracking"animate-point-along-route
"color by value", "style by property", "data driven", "line width by speed"data-driven-lines
"gradient line", "color gradient", "line-gradient", "progress color"gradient-line
"draw circle", "radius area", "circle on map", "coverage area"draw-a-circle
"pattern fill", "hatch polygon", "texture polygon", "fill-pattern"add-pattern-to-polygon
"filter features", "show only", "hide features", "setFilter"filter-within-layer
"layer below labels", "insert below text", "beforeId", "polygon under labels"add-layer-below-labels
"change color", "update style", "setPaintProperty", "runtime style"change-layer-color

Animations ​

When to use which:

User asks for...Use this example
"animate point", "moving dot", "orbit animation", "requestAnimationFrame"animate-point
"animate marker", "moving marker", "smooth marker movement"animate-marker
"realtime update", "live tracking", "setInterval", "websocket position", "moving vehicle"update-feature-realtime

Popups & Info ​

When to use which:

User asks for...Use this example
"show popup", "display popup", "programmatic popup", "popup at location"display-popup
"click polygon", "polygon info", "click to show properties"show-polygon-info-on-click
"features under mouse", "inspect features", "queryRenderedFeatures", "click to inspect"get-features-under-mouse
"measure distance", "ruler", "haversine", "distance between points"measure-distances

When to use which:

User asks for...Use this example
"search filter", "text search on map", "filter as you type", "live search"filter-by-text-input
"toggle categories", "category buttons", "show/hide types", "filter by category"filter-by-toggle-list

Camera & Bounds ​

When to use which:

User asks for...Use this example
"fit to route", "fit linestring", "auto zoom to path", "show full route"fit-to-linestring
"restrict panning", "limit map area", "maxBounds", "lock to region"restrict-map-panning

Map Controls ​

When to use which:

User asks for...Use this example
"add controls", "zoom buttons", "compass", "scale bar"navigation-controls
"disable rotation", "lock rotation", "fix map orientation", "no rotate"disable-map-rotation
"disable scroll zoom", "prevent zoom", "scroll page not map", "embed map"disable-scroll-zoom
"fullscreen", "expand map", "full screen button", "immersive map"fullscreen-map
"toggle interactions", "enable/disable", "readonly map", "lock interactions"toggle-interactions

Advanced / User Interaction ​

When to use which:

User asks for...Use this example
"draggable point", "drag geojson", "drag to move", "interactive point editor"draggable-point

Icons & Images ​

When to use which:

User asks for...Use this example
"add icon", "custom icon symbol", "icon from URL", "loadImage", "addImage"add-an-icon-to-the-map
"generate icon", "canvas icon", "programmatic icon", "draw icon"add-generated-icon
"animated icon", "pulsing icon", "blinking marker", "animate icon"add-animated-icon
"stretchable image", "nine-patch", "pill badge icon", "icon-text-fit", "scale icon"add-stretchable-image
"custom marker icon", "html marker", "emoji marker", "div marker", "image marker"add-custom-icons-markers
"svg icon", "svg symbol", "remote svg", "svg marker"display-remote-svg-symbol

Labels & Text ​

When to use which:

User asks for...Use this example
"Arabic map", "RTL text", "Hebrew labels", "right-to-left", "setRTLTextPlugin"rtl-support
"building color", "3D building zoom", "fill-extrusion color", "zoom building"building-color-zoom
"uppercase labels", "lowercase text", "label case", "text-transform"change-label-case

Terrain & Elevation ​

No three.js or external libraries needed — all built into MapMetrics GL.

When to use which:

User asks for...Use this example
"3D terrain", "elevation 3D", "terrain map", "mountain 3D", "raster-dem"3d-terrain
"sky", "fog", "atmosphere", "terrain fog", "immersive map"sky-fog-terrain
"hillshade", "terrain shadow", "elevation shading", "hill shadow"add-a-hillshade-layer
"color relief", "hypsometric", "elevation color", "altitude color", "color by height"add-a-color-relief-layer
"contour lines", "topographic", "elevation lines", "isoline", "topo map"add-contour-lines
"satellite map", "aerial map", "satellite 3D", "satellite terrain", "ESRI imagery"satellite-terrain

REST APIs ​

⚠️ PACKAGE NAMING - READ THIS FIRST! ​

The #1 mistake AI tools make: Using the wrong package!

Correct Package ​

bash
npm install @mapmetrics/mapmetrics-gl
javascript
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

WRONG Package (DO NOT USE!) ​

bash
# ❌ This is MapLibre, NOT MapMetrics!
npm install maplibre-gl
javascript
// ❌ This will NOT work with MapMetrics style URLs!
import maplibregl from 'maplibre-gl';

Why This Matters ​

  • MapMetrics GL is a custom fork of MapLibre with proprietary features
  • MapMetrics style URLs only work with @mapmetrics/mapmetrics-gl
  • Using maplibre-gl will cause authentication errors and missing features
  • The packages are NOT interchangeable

Quick Check ​

✅ Correct code always has:

❌ Wrong code has:

  • Package: maplibre-gl or @maplibre/maplibre-gl
  • Import: maplibregl

🎯 Common Code Patterns ​

Pattern 1: Basic Map (Vanilla JS with CDN - With Error Handling) ​

html
<!DOCTYPE html>
<html>
<head>
  <link href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" rel="stylesheet" />
  <script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
  <style>
    #map { height: 500px; width: 100%; }
    .error-box {
      height: 500px;
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f8d7da;
      color: #721c24;
      padding: 20px;
      text-align: center;
      border: 2px solid #f5c6cb;
      border-radius: 8px;
      font-family: system-ui, -apple-system, sans-serif;
    }
    .loading-box {
      height: 500px;
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f0f0f0;
      color: #666;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <div id="map"></div>
  <script>
    const styleUrl = 'YOUR_STYLE_URL_WITH_TOKEN';
    const mapContainer = document.getElementById('map');

    // Check if user forgot to replace the placeholder
    if (styleUrl === 'YOUR_STYLE_URL_WITH_TOKEN' || styleUrl.includes('YOUR_')) {
      mapContainer.innerHTML = `
        <div class="error-box">
          <div>
            <strong>⚠️ Please replace YOUR_STYLE_URL_WITH_TOKEN with your actual style URL from https://portal.mapmetrics.org/</strong>
            <div style="margin-top: 10px; font-size: 14px;">
              Need help? Check the <a href="https://discord.com/invite/uRXQRfbb7d" style="color: #721c24; text-decoration: underline;">Discord community</a>
            </div>
          </div>
        </div>
      `;
    } else {
      // Show loading state
      mapContainer.innerHTML = '<div class="loading-box">Loading map...</div>';

      try {
        const map = new mapmetricsgl.Map({
          container: 'map',
          style: styleUrl,
          center: [longitude, latitude],
          zoom: 12
        });

        // Handle successful load
        map.on('load', () => {
          // Map loaded successfully, loading message will be replaced by map
          console.log('Map loaded successfully');
        });

        // Handle critical errors (401, style loading failures)
        map.on('error', (e) => {
          console.error('Map error:', e);

          // Only show error UI for critical failures
          const isCriticalError =
            e.error?.status === 401 ||
            e.error?.message?.includes('401') ||
            e.error?.message?.includes('Failed to fetch') ||
            (e.sourceId === undefined && e.error);

          if (isCriticalError) {
            let errorMessage = '❌ Map failed to load. Check your style URL and token at https://portal.mapmetrics.org/';

            if (e.error?.status === 401 || e.error?.message?.includes('401')) {
              errorMessage = '❌ Invalid API token. Get a valid token from https://portal.mapmetrics.org/';
            } else if (e.error?.message?.includes('Failed to fetch')) {
              errorMessage = '❌ Cannot load map style. Check your style URL from https://portal.mapmetrics.org/';
            }

            mapContainer.innerHTML = `
              <div class="error-box">
                <div>
                  <strong>${errorMessage}</strong>
                  <div style="margin-top: 10px; font-size: 14px;">
                    Need help? Check the <a href="https://discord.com/invite/uRXQRfbb7d" style="color: #721c24; text-decoration: underline;">Discord community</a>
                  </div>
                </div>
              </div>
            `;
          } else {
            // Non-critical error, just log it
            console.warn('Non-critical map error:', e);
          }
        });
      } catch (err) {
        console.error('Map initialization error:', err);
        mapContainer.innerHTML = `
          <div class="error-box">
            <div>
              <strong>❌ Failed to initialize map. Check your style URL from https://portal.mapmetrics.org/</strong>
              <div style="margin-top: 10px; font-size: 14px;">
                Need help? Check the <a href="https://discord.com/invite/uRXQRfbb7d" style="color: #721c24; text-decoration: underline;">Discord community</a>
              </div>
            </div>
          </div>
        `;
      }
    }
  </script>
</body>
</html>

Pattern 2: React Map Component (with Error Handling) ​

jsx
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

function MapComponent() {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!mapContainerRef.current || mapRef.current) return;

    const styleUrl = 'YOUR_STYLE_URL_WITH_TOKEN';

    // Check if user forgot to replace the placeholder
    if (styleUrl === 'YOUR_STYLE_URL_WITH_TOKEN' || styleUrl.includes('YOUR_')) {
      setError('⚠️ Please replace YOUR_STYLE_URL_WITH_TOKEN with your actual style URL from https://portal.mapmetrics.org/');
      setLoading(false);
      return;
    }

    try {
      const map = new mapmetricsgl.Map({
        container: mapContainerRef.current,
        style: styleUrl,
        center: [longitude, latitude],
        zoom: 12
      });

      // Handle map load success
      map.on('load', () => {
        setLoading(false);
        setError(null);
      });

      // Handle CRITICAL errors only (authentication, style loading failures)
      // Ignore non-critical errors like individual tile loading failures
      map.on('error', (e) => {
        console.error('Map error:', e);

        // Only show UI errors for critical failures that prevent map from working
        // Ignore tile loading errors and other minor issues that don't break the map
        const isCriticalError =
          e.error?.status === 401 ||
          e.error?.message?.includes('401') ||
          (e.error?.message?.includes('Failed to fetch') && !loading) === false ||
          (e.sourceId === undefined && e.error); // Style loading errors have no sourceId

        if (isCriticalError) {
          setLoading(false);

          // Authentication errors (401)
          if (e.error?.message?.includes('401') || e.error?.status === 401) {
            setError('❌ Invalid API token. Get a valid token from https://portal.mapmetrics.org/');
          }
          // Network/style URL errors during initial load
          else if (e.error?.message?.includes('Failed to fetch') && loading) {
            setError('❌ Cannot load map style. Check your style URL from https://portal.mapmetrics.org/');
          }
          // Generic critical error before map loads
          else if (loading) {
            setError('❌ Map failed to load. Check your style URL and token at https://portal.mapmetrics.org/');
          }
          // If map already loaded, just log the error (don't hide the map)
          else {
            console.warn('Map error after load (non-critical):', e);
          }
        } else {
          // Non-critical errors (tile loading, etc.) - just log them
          console.warn('Non-critical map error:', e);
        }
      });

      mapRef.current = map;

      return () => {
        if (map) {
          map.remove();
          mapRef.current = null;
        }
      };
    } catch (err) {
      console.error('Map initialization error:', err);
      setError('❌ Failed to initialize map. Check your style URL from https://portal.mapmetrics.org/');
      setLoading(false);
    }
  }, []);

  // Display error message if something went wrong
  if (error) {
    return (
      <div style={{
        height: '500px',
        width: '100%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#f8d7da',
        color: '#721c24',
        padding: '20px',
        textAlign: 'center',
        fontSize: '16px',
        border: '2px solid #f5c6cb',
        borderRadius: '8px',
        fontFamily: 'system-ui, -apple-system, sans-serif'
      }}>
        <div>
          <strong>{error}</strong>
          <div style={{ marginTop: '10px', fontSize: '14px' }}>
            Need help? Check the <a href="https://discord.com/invite/uRXQRfbb7d" style={{ color: '#721c24' }}>Discord</a>
          </div>
        </div>
      </div>
    );
  }

  // Optional: Show loading state
  if (loading) {
    return (
      <div style={{
        height: '500px',
        width: '100%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#f0f0f0'
      }}>
        Loading map...
      </div>
    );
  }

  return <div ref={mapContainerRef} style={{ height: '500px', width: '100%' }} />;
}

export default MapComponent;

Pattern 3: Add Marker ​

javascript
// Vanilla JS
const marker = new mapmetricsgl.Marker()
  .setLngLat([longitude, latitude])
  .addTo(map);

// With popup
const marker = new mapmetricsgl.Marker()
  .setLngLat([longitude, latitude])
  .setPopup(new mapmetricsgl.Popup().setHTML('<h3>Location Name</h3>'))
  .addTo(map);

// Draggable marker
const marker = new mapmetricsgl.Marker({ draggable: true })
  .setLngLat([longitude, latitude])
  .addTo(map);

Pattern 4: REST API Call (Directions) ​

javascript
// POST request to Directions API
const response = await fetch('https://gateway.mapmetrics-atlas.net/directions/?token=YOUR_TOKEN', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    locations: [
      { lat: 40.744014, lon: -73.990508 },
      { lat: 40.752522, lon: -73.985015 }
    ],
    costing: 'auto'
  })
});

const data = await response.json();
// data.trip contains route information

Pattern 5: Geocoding (Forward) ​

javascript
// GET request to Forward Geocode API
const address = encodeURIComponent('1600 Pennsylvania Avenue NW, Washington, DC');
const response = await fetch(
  `https://gateway.mapmetrics-atlas.net/forward-geocode/?token=YOUR_TOKEN&text=${address}`
);

const data = await response.json();
// data.features[0].geometry.coordinates contains [lon, lat]

Pattern 6: Matrix API ​

javascript
// POST request to Matrix API
const response = await fetch('https://gateway.mapmetrics-atlas.net/matrix/?token=YOUR_TOKEN', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    sources: [
      { lat: 40.744014, lon: -73.990508 },
      { lat: 40.739735, lon: -73.979713 }
    ],
    targets: [
      { lat: 40.752522, lon: -73.985015 },
      { lat: 40.750117, lon: -73.983704 }
    ],
    costing: 'pedestrian'
  })
});

const data = await response.json();
// data.sources_to_targets contains time/distance matrix

📊 API Response Structures ​

Directions Response ​

json
{
  "trip": {
    "locations": [...],
    "legs": [
      {
        "maneuvers": [
          {
            "instruction": "Turn right onto Main Street",
            "time": 45.2,
            "length": 0.542
          }
        ],
        "summary": {
          "time": 18734.567,
          "length": 453.211
        }
      }
    ],
    "summary": {
      "time": 18734.567,
      "length": 453.211
    },
    "units": "kilometers"
  }
}

Geocoding Response ​

json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [longitude, latitude]
      },
      "properties": {
        "name": "Location Name",
        "label": "Full Address",
        "country": "Country Name"
      }
    }
  ]
}

Matrix Response (Verbose) ​

json
{
  "sources_to_targets": [
    [
      {
        "distance": 1.342,
        "time": 294,
        "from_index": 0,
        "to_index": 0
      }
    ]
  ],
  "units": "kilometers"
}

🔑 Important Notes for Code Generation ​

1. Token Handling - CRITICAL ⚠️ ​

EVERY generated code example MUST include token instructions!

What Happens Without a Token: ​

  • ❌ Map fails to load (shows blank/error)
  • ❌ API returns 401 Unauthorized
  • ❌ Console errors: "Token required"
  • ❌ User gets frustrated and blames MapMetrics

Required Token Instructions Template: ​

ALWAYS include this in your response:

⚠️ IMPORTANT: You need an API key to use MapMetrics!

1. Get your free API key at: https://portal.mapmetrics.org/
2. Replace 'YOUR_STYLE_URL_WITH_TOKEN' in the code with your actual style URL
3. The style URL from the portal already includes your token

Without a valid token, the map will not load!

Token Best Practices: ​

  • NEVER expose real tokens in generated code
  • ALWAYS use placeholder: YOUR_STYLE_URL_WITH_TOKEN or YOUR_API_TOKEN
  • ALWAYS include the portal link: https://portal.mapmetrics.org/
  • ALWAYS explain that the style URL includes the token
  • ALWAYS add a warning comment in the code

Example Code Comment: ​

javascript
// ⚠️ REPLACE THIS with your actual style URL from https://portal.mapmetrics.org/
style: 'YOUR_STYLE_URL_WITH_TOKEN'

2. NPM Package Name - CRITICAL ⚠️ ​

ALWAYS use the correct package name!

bash
# ✅ CORRECT
npm install @mapmetrics/mapmetrics-gl

# ❌ WRONG - DO NOT USE!
npm install maplibre-gl  # This will NOT work with MapMetrics!
javascript
// ✅ CORRECT imports
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

// ❌ WRONG - DO NOT USE!
import maplibregl from 'maplibre-gl';  // Wrong package!

3. React Patterns ​

  • ALWAYS use useRef for map container
  • ALWAYS use useRef for map instance
  • ALWAYS cleanup in useEffect return
  • NEVER create map if it already exists
  • ALWAYS use @mapmetrics/mapmetrics-gl, NEVER maplibre-gl

4. Coordinate Format ​

  • MapMetrics uses: [longitude, latitude] (NOT lat/lon)
  • API requests use: {lat: number, lon: number} objects
  • Be consistent based on context

5. Error Handling ​

javascript
// Always include error handling
try {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  const data = await response.json();
} catch (error) {
  console.error('API request failed:', error);
}

6. Costing Models ​

Valid values: auto, bicycle, pedestrian, bus, truck, taxi, motor_scooter

7. Units ​

  • Distance: km (default) or miles / mi
  • Time: always in seconds
  • Coordinates: decimal degrees

📚 Machine-Readable References ​

AI Navigation File ​

llms.txt with all URLs, code templates, and common mistakes: https://docs.mapatlas.xyz/llms.txt

Example Files ​

All examples include:

  • YAML frontmatter with metadata
  • Complete JavaScript implementation
  • Complete React implementation
  • Copy-paste ready code

🎓 Example Code Generation Flow ​

User Request: "Create a map with a draggable marker and popup in React"

AI Response Steps:

  1. Identify capabilities: React integration + Markers + Popups
  2. Reference examples: /sdk/examples/react-map-example, /sdk/examples/add-a-marker, /sdk/examples/add-a-popup
  3. Combine patterns into working code
  4. Include proper imports, refs, cleanup
  5. Add placeholder for style URL
  6. Remind user to get API key

⚡ Quick Command Reference ​

bash
# NPM Installation - ALWAYS USE THIS PACKAGE NAME!
npm install @mapmetrics/mapmetrics-gl

# ❌ WRONG - DO NOT USE
npm install maplibre-gl  # This is NOT MapMetrics!

# Import in JavaScript
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

# ❌ WRONG imports
import maplibregl from 'maplibre-gl';  # Wrong!

# CDN Links
<link href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>

🚨 Common Pitfalls to Avoid ​

  1. ❌ Don't use maplibre-gl package - ALWAYS use @mapmetrics/mapmetrics-gl
  2. ❌ Don't create multiple map instances in React
  3. ❌ Don't forget to cleanup map in useEffect return
  4. ❌ Don't mix up [lon, lat] vs {lat, lon} formats
  5. ❌ Don't forget token in API requests
  6. ❌ Don't hardcode real tokens in examples
  7. ❌ Don't generate code without clear token instructions
  8. ❌ Don't call new mapmetricsgl.Map() before the CDN script has finished loading — causes Uncaught ReferenceError: mapmetricsgl is not defined

3. CDN Script Loading Order - CRITICAL ⚠️ ​

The most common AI-generated bug: mapmetricsgl is not defined — this happens when map code runs before the CDN script has finished loading.

❌ WRONG — causes "mapmetricsgl is not defined": ​

html
<script>
  // Script added dynamically but map created immediately after — WRONG!
  const script = document.createElement('script');
  script.src = 'https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js';
  document.body.appendChild(script);

  // ❌ This runs BEFORE the script above has loaded!
  const map = new mapmetricsgl.Map({ container: 'map', style: '...' });
</script>

✅ CORRECT Option A — static <script> tag in <head> (simplest for plain HTML): ​

html
<head>
  <link href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" rel="stylesheet" />
  <script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
</head>
<body>
  <div id="map"></div>
  <script>
    // ✅ Safe: <head> script is already loaded before body scripts run
    const map = new mapmetricsgl.Map({ container: 'map', style: 'YOUR_STYLE_URL' });
  </script>
</body>

✅ CORRECT Option B — dynamic script with script.onload (required when loading script dynamically): ​

html
<body>
  <div id="map"></div>
  <script>
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
    document.head.appendChild(link);

    const script = document.createElement('script');
    script.src = 'https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js';
    document.body.appendChild(script);

    // ✅ ALL map code must go inside script.onload
    script.onload = () => {
      const map = new mapmetricsgl.Map({ container: 'map', style: 'YOUR_STYLE_URL' });
      map.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
    };
  </script>
</body>

Rule for AI agents: When generating HTML with CDN, ALWAYS use Option A (static <script> in <head>) OR Option B (script.onload). Never call new mapmetricsgl.Map() outside of script.onload when the script is dynamically inserted.

🔧 Troubleshooting Guide ​

Problem: Map doesn't load (blank screen) ​

Most Common Cause: Missing or Invalid Token

javascript
// ❌ WRONG - Will not work
const map = new mapmetricsgl.Map({
  container: 'map',
  style: 'YOUR_STYLE_URL_WITH_TOKEN',  // Placeholder not replaced!
  center: [0, 0],
  zoom: 10
});

Solution:

javascript
// ✅ CORRECT - User must replace with actual URL from portal
const map = new mapmetricsgl.Map({
  container: 'map',
  // Get this URL from https://portal.mapmetrics.org/
  style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_ID/style.json&token=YOUR_TOKEN',
  center: [0, 0],
  zoom: 10
});

Error Messages to Watch For:

  • 401 Unauthorized → Missing or invalid token
  • Token required → Token not provided in request
  • Map container blank → Check console for token errors

Problem: Uncaught ReferenceError: mapmetricsgl is not defined ​

Cause: Map code runs before the CDN script has finished loading.

This is the most common error when AI tools generate HTML. The fix is to either:

  1. Put the <script src="...mapmetrics-gl.js"> tag in <head> (not added dynamically), OR
  2. Wrap ALL map code inside script.onload = () => { ... }
html
<!-- ✅ CORRECT: Static script in <head> -->
<head>
  <link href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" rel="stylesheet" />
  <script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
</head>
<body>
  <div id="map"></div>
  <script>
    const map = new mapmetricsgl.Map({ container: 'map', style: 'YOUR_STYLE_URL' });
  </script>
</body>

<!-- ✅ CORRECT: Dynamic script with onload -->
<script>
  const script = document.createElement('script');
  script.src = 'https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js';
  document.body.appendChild(script);
  script.onload = () => {
    const map = new mapmetricsgl.Map({ container: 'map', style: 'YOUR_STYLE_URL' });
  };
</script>

Problem: REST API returns 401 ​

Cause: Missing token parameter

javascript
// ❌ WRONG
fetch('https://gateway.mapmetrics-atlas.net/directions/', {
  method: 'POST',
  body: JSON.stringify({...})
});

// ✅ CORRECT
fetch('https://gateway.mapmetrics-atlas.net/directions/?token=YOUR_TOKEN', {
  method: 'POST',
  body: JSON.stringify({...})
});

User Doesn't Have a Token Yet ​

When user says "create a map" without mentioning a token:

Your response MUST include:

  1. Working code with placeholder
  2. Clear instructions to get token
  3. Link to portal
  4. Explanation that map won't work without it

Example Response Template:

Here's the code to create a map:

[code with placeholder]

⚠️ IMPORTANT: Before this works, you need to:
1. Sign up at https://portal.mapmetrics.org/ (free)
2. Get your map style URL (includes API token)
3. Replace 'YOUR_STYLE_URL_WITH_TOKEN' in the code above

The map will NOT load without a valid token!

✅ Best Practices ​

  1. ✅ Always include complete, working examples
  2. ✅ Always include both JavaScript and React versions when applicable
  3. ✅ Always remind users about API key requirement
  4. ✅ Always include error handling
  5. ✅ Always use proper React patterns (refs, cleanup)
  6. ✅ Always include CSS for map container
  7. ✅ Always link to relevant documentation sections

📖 Additional Resources ​


Last Updated: 2026-03-03 Documentation Version: 1.1.0 Optimized for: Claude, ChatGPT, Gemini, and other AI coding assistants