Using Vector Tiles with Mapping Libraries

Learn how to integrate Geog vector tiles into popular mapping libraries including Mapbox GL JS, MapLibre GL JS, and Leaflet.

Mapbox GL JS

import mapboxgl from "mapbox-gl";

const map = new mapboxgl.Map({
  container: "map",
  style: {
    version: 8,
    sources: {
      "geog-tiles": {
        type: "vector",
        tiles: [
          "https://tiles.geog.dev/{z}/{x}/{y}.mvt?key=your-api-key",
        ],
        maxzoom: 14,
      },
    },
    layers: [
      {
        id: "places",
        type: "circle",
        source: "geog-tiles",
        "source-layer": "places",
        paint: {
          "circle-radius": 6,
          "circle-color": "#FF6B6B",
        },
      },
      {
        id: "roads",
        type: "line",
        source: "geog-tiles",
        "source-layer": "roads",
        paint: {
          "line-color": "#333",
          "line-width": 2,
        },
      },
    ],
  },
});

MapLibre GL JS

import maplibregl from "maplibre-gl";

const map = new maplibregl.Map({
  container: "map",
  style: {
    version: 8,
    sources: {
      "geog-vector": {
        type: "vector",
        tiles: [
          "https://tiles.geog.dev/{z}/{x}/{y}.mvt?key=your-api-key",
        ],
      },
    },
    layers: [
      {
        id: "boundaries",
        type: "line",
        source: "geog-vector",
        "source-layer": "boundaries",
        paint: {
          "line-color": "#666",
          "line-width": 1,
        },
      },
    ],
  },
});

Leaflet with Vector Grid

import L from "leaflet";
import "leaflet.vectorgrid";

const map = L.map("map").setView([40.7128, -74.006], 10);

const vectorTileOptions = {
  vectorTileLayerStyles: {
    places: {
      radius: 6,
      fillColor: "#FF6B6B",
      color: "#333",
      weight: 1,
      opacity: 1,
      fillOpacity: 0.8,
    },
    roads: {
      color: "#333",
      weight: 2,
      opacity: 0.7,
    },
  },
  subdomains: [],
  // key passed via URL query parameter
};

const vectorGrid = L.vectorGrid
  .protobuf(
    "https://tiles.geog.dev/{z}/{x}/{y}.mvt?key=your-api-key",
    vectorTileOptions,
  )
  .addTo(map);

Styling Examples

Places Styling

// Restaurant categories with different colors
const placesPaint = {
  "circle-radius": ["interpolate", ["linear"], ["zoom"], 8, 4, 14, 8],
  "circle-color": [
    "match",
    ["get", "category"],
    "restaurant",
    "#FF6B6B",
    "retail",
    "#4ECDC4",
    "accommodation",
    "#45B7D1",
    "entertainment",
    "#96CEB4",
    "#DDD",
  ],
  "circle-opacity": 0.8,
  "circle-stroke-color": "#FFF",
  "circle-stroke-width": 1,
};

Road Classification

// Different road types with varying widths
const roadsPaint = {
  "line-color": "#333",
  "line-width": [
    "match",
    ["get", "class"],
    "highway",
    4,
    "primary",
    3,
    "secondary",
    2,
    "tertiary",
    1.5,
    1,
  ],
  "line-opacity": ["interpolate", ["linear"], ["zoom"], 8, 0.6, 12, 1],
};

Performance Optimization

Caching Strategy

  • CDN Caching: Tiles cached at Cloudflare edge locations
  • Browser Caching: Cache-Control: max-age=86400 for stable tiles
  • Conditional Requests: ETag support for efficient updates

Request Optimization

// Batch tile requests
const tilePromises = [];
for (let x = startX; x <= endX; x++) {
  for (let y = startY; y <= endY; y++) {
    tilePromises.push(
      fetch(`https://tiles.geog.dev/${z}/${x}/${y}.mvt?key=${apiKey}`),
    );
  }
}

const tiles = await Promise.all(tilePromises);

Memory Management

// Implement tile cleanup for long-running applications
map.on("sourcedata", (e) => {
  if (e.isSourceLoaded && e.sourceId === "geog-tiles") {
    // Clean up old tiles beyond viewport
    cleanupTileCache();
  }
});

Usage Analytics

Tile Request Tracking

// Track tile usage in your application
function trackTileUsage(z, x, y, loadTime) {
  analytics.track("tile_loaded", {
    zoom: z,
    tile_x: x,
    tile_y: y,
    load_time_ms: loadTime,
    user_agent: navigator.userAgent,
  });
}

// Monitor tile loading performance
const startTime = performance.now();
fetch(tileUrl).then((response) => {
  const loadTime = performance.now() - startTime;
  trackTileUsage(z, x, y, loadTime);
  return response;
});

Best Practices

Client Implementation

  1. Implement proper error handling for missing tiles
  2. Use appropriate zoom levels based on data availability
  3. Cache tiles client-side when possible
  4. Implement progressive loading for better UX
  5. Monitor rate limits and implement backoff strategies

Performance Tips

  1. Limit concurrent requests to avoid rate limiting
  2. Use tile pyramids efficiently (don't skip zoom levels)
  3. Implement viewport-based loading to minimize requests
  4. Use compression (gzip) for faster downloads
  5. Monitor tile load times and optimize accordingly

Styling Guidelines

  1. Progressive enhancement - style for different zoom levels
  2. Consistent symbology across zoom levels
  3. Readable labels with appropriate font sizing
  4. Color accessibility for different user needs
  5. Performance-conscious styling to maintain smooth interaction

See Also