Source: control/rotate.js

import Point from "ol/geom/Point";
import Pointer from "ol/interaction/Pointer";
import Vector from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { Icon, Style } from "ol/style";

import rotateMapSVG from "../../img/rotate_map.svg";
import rotateSVG from "../../img/rotate.svg";
import Control from "./control";

/**
 * Tool with for rotating geometries.
 * @extends {Control}
 * @alias ole.RotateControl
 */
class RotateControl extends Control {
  /**
   * @inheritdoc
   * @param {Object} [options] Control options.
   * @param {string} [options.rotateAttribute] Name of a feature attribute
   *   that is used for storing the rotation in rad.
   * @param {ol.style.Style.StyleLike} [options.style] Style used for the rotation layer.
   */
  constructor(options) {
    super({
      className: "icon-rotate",
      image: rotateSVG,
      title: "Rotate",
      ...options,
    });

    /**
     * @type {ol.interaction.Pointer}
     * @private
     */
    this.pointerInteraction = new Pointer({
      handleDownEvent: this.onDown.bind(this),
      handleDragEvent: this.onDrag.bind(this),
      handleUpEvent: this.onUp.bind(this),
    });

    /**
     * @type {string}
     * @private
     */
    this.rotateAttribute = options.rotateAttribute || "ole_rotation";

    /**
     * Layer for rotation feature.
     * @type {ol.layer.Vector}
     * @private
     */
    this.rotateLayer = new Vector({
      source: new VectorSource(),
      style:
        options.style ||
        ((f) => {
          const rotation = f.get(this.rotateAttribute);
          return [
            new Style({
              geometry: new Point(this.center),
              image: new Icon({
                rotation,
                src: rotateMapSVG,
              }),
            }),
          ];
        }),
    });
  }

  /**
   * @inheritdoc
   */
  activate() {
    this.map?.addInteraction(this.pointerInteraction);
    this.rotateLayer.setMap(this.map);
    super.activate();
  }

  /**
   * @inheritdoc
   */
  deactivate(silent) {
    this.rotateLayer.getSource().clear();
    this.rotateLayer.setMap(null);
    this.map?.removeInteraction(this.pointerInteraction);
    super.deactivate(silent);
  }

  /**
   * Handle a pointer down event.
   * @param {ol.MapBrowserEvent} event Down event
   * @private
   */
  onDown(evt) {
    this.dragging = false;
    this.feature = this.map.forEachFeatureAtPixel(evt.pixel, (f) => {
      if (this.source.getFeatures().indexOf(f) > -1) {
        return f;
      }

      return null;
    });

    if (this.center && this.feature) {
      this.feature.set(
        this.rotateAttribute,
        this.feature.get(this.rotateAttribute) || 0,
      );

      // rotation between clicked coordinate and feature center
      this.initialRotation =
        Math.atan2(
          evt.coordinate[1] - this.center[1],
          evt.coordinate[0] - this.center[0],
        ) + this.feature.get(this.rotateAttribute);
    }

    if (this.feature) {
      return true;
    }

    return false;
  }

  /**
   * Handle a pointer drag event.
   * @param {ol.MapBrowserEvent} event Down event
   * @private
   */
  onDrag(evt) {
    this.dragging = true;

    if (this.feature && this.center) {
      const rotation = Math.atan2(
        evt.coordinate[1] - this.center[1],
        evt.coordinate[0] - this.center[0],
      );

      const rotationDiff = this.initialRotation - rotation;
      const geomRotation =
        rotationDiff - this.feature.get(this.rotateAttribute);

      this.feature.getGeometry().rotate(-geomRotation, this.center);
      this.rotateFeature.getGeometry().rotate(-geomRotation, this.center);

      this.feature.set(this.rotateAttribute, rotationDiff);
      this.rotateFeature.set(this.rotateAttribute, rotationDiff);
    }
  }

  /**
   * Handle a pointer up event.
   * @param {ol.MapBrowserEvent} event Down event
   * @private
   */
  onUp(evt) {
    if (!this.dragging) {
      if (this.feature) {
        this.rotateFeature = this.feature;
        this.center = evt.coordinate;
        this.rotateLayer.getSource().clear();
        this.rotateLayer.getSource().addFeature(this.rotateFeature);
      } else {
        this.rotateLayer.getSource().clear();
      }
    }
  }
}

export default RotateControl;