Skip to content

OpenLayers

OpenLayers is a high-performance library with excellent WMS/WMTS support and projection handling.

Python Example

1
2
3
4
5
6
7
8
from anymap_ts import OpenLayersMap

m = OpenLayersMap(center=[-122.4, 37.8], zoom=10)
m.add_basemap("OpenStreetMap")
m.add_geojson(geojson, name="cities", style={"fillColor": "rgba(255, 0, 0, 0.8)", "strokeColor": "#ffffff", "radius": 8})
m.add_marker(-122.4194, 37.7749, color="#ff0000")
m.fly_to(-122.4194, 37.7749, zoom=14)
m

TypeScript Implementation

The OpenLayersRenderer wraps the OpenLayers API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import OSM from 'ol/source/OSM';
import XYZ from 'ol/source/XYZ';
import GeoJSON from 'ol/format/GeoJSON';
import { fromLonLat, toLonLat } from 'ol/proj';

export class OpenLayersRenderer extends BaseMapRenderer<Map> {
  private layersMap: Map<string, Layer> = new Map();

  protected createMap(): Map {
    const center = this.model.get('center') as [number, number];
    const zoom = this.model.get('zoom');

    return new Map({
      target: this.mapContainer!,
      view: new View({
        center: fromLonLat(center), // Convert to EPSG:3857
        zoom,
      }),
    });
  }
}

Key Methods

Adding GeoJSON

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private handleAddGeoJSON(args: unknown[], kwargs: Record<string, unknown>): void {
  const geojson = kwargs.data as FeatureCollection;
  const name = kwargs.name as string;
  const style = kwargs.style as Record<string, unknown>;

  // Parse GeoJSON
  const features = new GeoJSON().readFeatures(geojson, {
    featureProjection: 'EPSG:3857', // Reproject to map projection
  });

  // Create vector source and layer
  const source = new VectorSource({ features });
  const layer = new VectorLayer({
    source,
    style: this.createStyle(style),
  });

  this.map.addLayer(layer);
  this.layersMap.set(name, layer);

  // Fit to extent
  if (kwargs.fitBounds !== false) {
    this.map.getView().fit(source.getExtent(), { padding: [50, 50, 50, 50] });
  }
}

Styling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private createStyle(options?: Record<string, unknown>): Style {
  return new Style({
    fill: new Fill({
      color: options?.fillColor || 'rgba(51, 136, 255, 0.5)',
    }),
    stroke: new Stroke({
      color: options?.strokeColor || '#3388ff',
      width: options?.strokeWidth || 2,
    }),
    image: new Circle({
      radius: options?.radius || 8,
      fill: new Fill({ color: options?.fillColor || '#3388ff' }),
      stroke: new Stroke({ color: options?.strokeColor || '#ffffff', width: 2 }),
    }),
  });
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private handleFlyTo(args: unknown[], kwargs: Record<string, unknown>): void {
  const [lng, lat] = args as [number, number];
  const zoom = kwargs.zoom as number;
  const duration = kwargs.duration as number || 2000;

  this.map.getView().animate({
    center: fromLonLat([lng, lat]),
    zoom: zoom || this.map.getView().getZoom(),
    duration,
  });
}

Markers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private handleAddMarker(args: unknown[], kwargs: Record<string, unknown>): void {
  const [lng, lat] = args as [number, number];
  const id = kwargs.id as string || `marker-${Date.now()}`;
  const color = kwargs.color as string || '#3388ff';

  const feature = new Feature({
    geometry: new Point(fromLonLat([lng, lat])),
  });

  feature.setStyle(new Style({
    image: new Circle({
      radius: 8,
      fill: new Fill({ color }),
      stroke: new Stroke({ color: '#ffffff', width: 2 }),
    }),
  }));

  const source = new VectorSource({ features: [feature] });
  const layer = new VectorLayer({ source });

  this.map.addLayer(layer);
  this.markersMap.set(id, layer);
}

WMS Layers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private handleAddWMSLayer(args: unknown[], kwargs: Record<string, unknown>): void {
  const url = kwargs.url as string;
  const layers = kwargs.layers as string;
  const name = kwargs.name as string;

  const layer = new TileLayer({
    source: new TileWMS({
      url,
      params: {
        LAYERS: layers,
        TILED: true,
      },
    }),
  });

  this.map.addLayer(layer);
  this.layersMap.set(name, layer);
}

Coordinate System

OpenLayers uses EPSG:3857 (Web Mercator) internally. Convert coordinates:

1
2
3
4
5
6
7
import { fromLonLat, toLonLat } from 'ol/proj';

// To OpenLayers (EPSG:4326 -> EPSG:3857)
const olCoord = fromLonLat([-122.4, 37.8]);

// From OpenLayers (EPSG:3857 -> EPSG:4326)
const lngLat = toLonLat(olCoord);

Source Files

  • Renderer: src/openlayers/OpenLayersRenderer.ts
  • Types: src/types/openlayers.ts

See also: Python notebook example