Edit

Custom Controls

custom6 control3 rotate4 north1 svg3

Shows how to create custom controls.

This example creates a "rotate to north" control with an inline SVG north arrow. The control's render method rotates the arrow together with the map, so it always points north, and hides the control with the ol-hidden class when the map is not rotated. The control replaces the default ol/control/Rotate control, which is left out of the map's controls. Use Alt+Shift+Drag to rotate the map.

main.js
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import Control from 'ol/control/Control.js';
import {defaults as defaultControls} from 'ol/control/defaults.js';
import TileLayer from 'ol/layer/Tile.js';
import OSM from 'ol/source/OSM.js';

//
// Define rotate to north control.
//

const northArrow =
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">' +
  '<path d="M12 2 5 21l7-4z"/>' +
  '<path d="M12 2l7 19-7-4z" opacity=".5"/>' +
  '</svg>';

class RotateNorthControl extends Control {
  /**
   * @param {Object} [opt_options] Control options.
   */
  constructor(opt_options) {
    const options = opt_options || {};

    const button = document.createElement('button');
    button.type = 'button';
    button.title = 'Rotate to north';
    button.innerHTML = northArrow;

    const element = document.createElement('div');
    element.className = 'rotate-north ol-unselectable ol-control';
    element.appendChild(button);

    super({
      element: element,
      target: options.target,
    });

    /**
     * The north arrow, rotated together with the map.
     * @type {SVGElement}
     * @private
     */
    this.arrow_ = button.querySelector('svg');

    /**
     * The rotation the arrow was last rendered with.
     * @type {number|undefined}
     * @private
     */
    this.rotation_ = undefined;

    button.addEventListener('click', this.handleRotateNorth.bind(this), false);
  }

  handleRotateNorth() {
    this.getMap().getView().animate({rotation: 0, duration: 250});
  }

  /**
   * Keep the arrow pointing north, and hide the control when the map is
   * already rotated to north.
   * @param {import('ol/MapEvent.js').default} mapEvent Map event.
   * @override
   */
  render(mapEvent) {
    const frameState = mapEvent.frameState;
    if (!frameState) {
      return;
    }
    const rotation = frameState.viewState.rotation;
    if (rotation !== this.rotation_) {
      this.arrow_.style.transform = 'rotate(' + rotation + 'rad)';
      this.element.classList.toggle('ol-hidden', rotation === 0);
      this.rotation_ = rotation;
    }
  }
}

//
// Create map, giving it a rotate to north control instead of the default
// rotate control.
//

const map = new Map({
  controls: defaultControls({rotate: false}).extend([new RotateNorthControl()]),
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 3,
    rotation: 1,
  }),
});
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Custom Controls</title>
    <link rel="stylesheet" href="node_modules/ol/ol.css">
    <style>
      .map {
        width: 100%;
        height: 400px;
      }
      .rotate-north {
        top: 65px;
        left: .5em;
      }
      .ol-touch .rotate-north {
        top: 80px;
      }
      .rotate-north svg {
        width: 1em;
        height: 1em;
        vertical-align: middle;
      }
      .rotate-north.ol-hidden {
        opacity: 0;
        visibility: hidden;
        transition: opacity .25s linear, visibility 0s linear .25s;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>

    <script type="module" src="main.js"></script>
  </body>
</html>
package.json
{
  "name": "custom-controls",
  "dependencies": {
    "ol": "10.11.0"
  },
  "devDependencies": {
    "vite": "^8.1.5"
  },
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}