Skip to main content
Technology & EngineeringMaps Geolocation Services236 lines

OpenLayers

OpenLayers: high-performance open-source map library with vector tiles, projections, OGC services (WMS/WFS), drawing interactions, and GeoJSON/KML support

Quick Summary14 lines
You are an expert in building map applications with OpenLayers, the open-source JavaScript library for interactive maps.

## Key Points

- Dispose of the map with `map.setTarget(null)` in single-page applications when the component unmounts, to avoid memory leaks from orphaned event listeners and canvas elements.
- Forgetting to import `ol/ol.css` causes controls, popups, and attribution to render without styling, making the map appear broken.

## Quick Example

```bash
npm install ol
```
skilldb get maps-geolocation-services-skills/OpenLayersFull skill: 236 lines
Paste into your CLAUDE.md or agent config

OpenLayers — Maps & Geolocation

You are an expert in building map applications with OpenLayers, the open-source JavaScript library for interactive maps.

Core Philosophy

OpenLayers is the power tool of web mapping. Where Leaflet optimizes for simplicity and Mapbox for design, OpenLayers optimizes for completeness and standards compliance. It supports every major geospatial format (GeoJSON, KML, GML, WMS, WFS, WCS, WMTS, MVT), arbitrary coordinate projections (not just Web Mercator), and fine-grained control over rendering, interactions, and layer composition. Choose OpenLayers when your application needs GIS-grade capabilities -- custom projections, OGC service integration, complex vector editing, or multi-CRS support.

Coordinate projections are the single most common source of bugs. OpenLayers defaults to EPSG:3857 (Web Mercator, measured in meters), but geographic data from APIs and files typically uses EPSG:4326 (longitude/latitude in degrees). Always use fromLonLat() to convert geographic coordinates to the map's projection and toLonLat() for the reverse. Passing raw latitude/longitude values directly to the View center places the map thousands of kilometers from the intended location.

OpenLayers is a library, not a platform. It does not include a tile server, a geocoding API, or a routing engine. You bring your own data sources and combine them using OpenLayers' layer system. This means OpenLayers works with any tile provider (OSM, Mapbox, custom WMS/WMTS servers) and any data format, but it also means you are responsible for assembling the full stack yourself.

Anti-Patterns

  • Passing raw lat/lng values to View.center without projection conversion -- OpenLayers uses EPSG:3857 by default. Raw geographic coordinates place the map at the wrong location. Always use fromLonLat([lng, lat]).
  • Confusing coordinate order -- OpenLayers uses [longitude, latitude] (x, y) order for fromLonLat. Swapping to [latitude, longitude] places markers in the wrong hemisphere. The order matches GeoJSON convention, not the common "lat, lng" verbal convention.
  • Loading entire large GeoJSON datasets at once -- For datasets with thousands of features, load only the visible viewport using strategy: bbox on the VectorSource. Loading everything upfront causes memory issues and rendering lag.
  • Not disposing the map in single-page applications -- Failing to call map.setTarget(null) when the component unmounts leaks event listeners and canvas elements, degrading performance over time.
  • Forgetting to import ol/ol.css -- Without the CSS, controls, popups, and attribution render without styling, making the map appear visually broken even though the JavaScript is working correctly.

Overview

OpenLayers is a full-featured, open-source map library that supports raster tiles, vector tiles, WMS, WFS, GeoJSON, KML, and other OGC standards. It provides fine-grained control over projections, coordinate systems, map interactions, and rendering. OpenLayers is commonly used in GIS applications, government/municipal mapping portals, and projects that need advanced cartographic features beyond what simpler libraries offer.

Setup & Configuration

Installation

npm install ol

Basic Map

import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import OSM from "ol/source/OSM";
import "ol/ol.css";

const map = new Map({
  target: "map",
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
  ],
  view: new View({
    center: [0, 0], // EPSG:3857 (Web Mercator) coordinates
    zoom: 2,
  }),
});

Setting Center with Longitude/Latitude

OpenLayers uses EPSG:3857 (meters) by default. Use fromLonLat to convert from geographic coordinates.

import { fromLonLat, toLonLat } from "ol/proj";

const map = new Map({
  target: "map",
  layers: [new TileLayer({ source: new OSM() })],
  view: new View({
    center: fromLonLat([13.405, 52.52]), // Berlin
    zoom: 12,
  }),
});

Core Patterns

Vector Layer with GeoJSON

import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import GeoJSON from "ol/format/GeoJSON";
import { Style, Fill, Stroke, Circle } from "ol/style";

const geojsonSource = new VectorSource({
  url: "/data/locations.geojson",
  format: new GeoJSON(),
});

const vectorLayer = new VectorLayer({
  source: geojsonSource,
  style: new Style({
    image: new Circle({
      radius: 7,
      fill: new Fill({ color: "#3399CC" }),
      stroke: new Stroke({ color: "#fff", width: 2 }),
    }),
    stroke: new Stroke({ color: "#3399CC", width: 3 }),
    fill: new Fill({ color: "rgba(51, 153, 204, 0.2)" }),
  }),
});

map.addLayer(vectorLayer);

Click Interaction and Popups

import Overlay from "ol/Overlay";

const popupEl = document.getElementById("popup");
const popup = new Overlay({
  element: popupEl,
  positioning: "bottom-center",
  offset: [0, -15],
  autoPan: true,
});
map.addOverlay(popup);

map.on("click", (evt) => {
  const feature = map.forEachFeatureAtPixel(evt.pixel, (f) => f);
  if (feature) {
    const coords = feature.getGeometry().getCoordinates();
    popupEl.innerHTML = `<strong>${feature.get("name")}</strong>`;
    popup.setPosition(coords);
  } else {
    popup.setPosition(undefined);
  }
});

Drawing and Editing Features

import Draw from "ol/interaction/Draw";
import Modify from "ol/interaction/Modify";

const drawSource = new VectorSource();
const drawLayer = new VectorLayer({ source: drawSource });
map.addLayer(drawLayer);

// Enable polygon drawing
const drawInteraction = new Draw({
  source: drawSource,
  type: "Polygon",
});
map.addInteraction(drawInteraction);

drawInteraction.on("drawend", (event) => {
  const geojsonFormat = new GeoJSON();
  const geojson = geojsonFormat.writeFeature(event.feature, {
    featureProjection: "EPSG:3857",
    dataProjection: "EPSG:4326",
  });
  console.log("Drawn polygon:", geojson);
});

// Enable feature modification
const modifyInteraction = new Modify({ source: drawSource });
map.addInteraction(modifyInteraction);

WMS Layer (Web Map Service)

import TileWMS from "ol/source/TileWMS";

const wmsLayer = new TileLayer({
  source: new TileWMS({
    url: "https://example.com/geoserver/wms",
    params: {
      LAYERS: "my_workspace:my_layer",
      TILED: true,
      FORMAT: "image/png",
    },
    serverType: "geoserver",
  }),
});

map.addLayer(wmsLayer);

Vector Tiles

import VectorTileLayer from "ol/layer/VectorTile";
import VectorTileSource from "ol/source/VectorTile";
import MVT from "ol/format/MVT";

const vtLayer = new VectorTileLayer({
  source: new VectorTileSource({
    format: new MVT(),
    url: "https://tiles.example.com/{z}/{x}/{y}.pbf",
    maxZoom: 14,
  }),
});

map.addLayer(vtLayer);

Coordinate Projection

import { register } from "ol/proj/proj4";
import proj4 from "proj4";

// Register a custom projection (e.g., EPSG:25832 UTM zone 32N)
proj4.defs(
  "EPSG:25832",
  "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
);
register(proj4);

// Now you can use EPSG:25832 in OpenLayers
const view = new View({
  projection: "EPSG:25832",
  center: [500000, 5800000],
  zoom: 10,
});

Best Practices

  • Always use fromLonLat() and toLonLat() to convert between geographic coordinates (EPSG:4326) and the map's native projection (EPSG:3857) — passing raw lat/lng values directly to View.center places the map at the wrong location.
  • When loading large GeoJSON datasets, use a VectorSource with a url loader and the strategy: bbox loading strategy to fetch only features within the current viewport, rather than loading everything at once.
  • Dispose of the map with map.setTarget(null) in single-page applications when the component unmounts, to avoid memory leaks from orphaned event listeners and canvas elements.

Common Pitfalls

  • Confusing coordinate order: OpenLayers uses [longitude, latitude] (x, y) order for fromLonLat, but [latitude, longitude] is common in other contexts. Swapping them puts the map in the wrong hemisphere.
  • Forgetting to import ol/ol.css causes controls, popups, and attribution to render without styling, making the map appear broken.

Install this skill directly: skilldb add maps-geolocation-services-skills

Get CLI access →