Map/LeafletDragPoints.js

/*global require*/
"use strict";
var defined = require("terriajs-cesium/Source/Core/defined").default;
var Cartesian3 = require("terriajs-cesium/Source/Core/Cartesian3").default;
var CustomDataSource = require("terriajs-cesium/Source/DataSources/CustomDataSource")
  .default;

/**
 * Callback for when a point is moved.
 * @callback PointMovedCallback
 * @param {CustomDataSource} customDataSource Contains all point entities that user has selected so far
 */

/**
 * For letting user drag existing points in Leaflet ViewerMode only.
 *
 * @alias LeafletDragPoints
 * @constructor
 *
 * @param {Terria} terria The Terria instance.
 * @param {PointMovedCallback} pointMovedCallback A function that is called when a point is moved.
 */
var LeafletDragPoints = function(terria, pointMovedCallback) {
  this._terria = terria;
  this._setUp = false;
  this.type = "Leaflet";

  /**
   * Callback that occurs when point is moved. Function takes a CustomDataSource which is a list of PointEntities.
   * @type {PointMovedCallback}
   * @default undefined
   */
  this._pointMovedCallback = pointMovedCallback;

  /**
   * List of entities that can be dragged, which is populated with user-created points only.
   * @type {CustomDataSource}
   */
  this._draggableObjects = new CustomDataSource();

  /**
   * Whether user is currently dragging point.
   * @type {Bool}
   */
  this._dragInProgress = false;

  /**
   * For determining whether a drag has just occurred, to avoid deleting a point at the end of the drag.
   * @type {Number}
   */
  this.dragCount = 0;
};

/**
 * Set up the drag point helper so that attempting to drag a point will move the point.
 */
LeafletDragPoints.prototype.setUp = function() {
  if (this._setUp) {
    return;
  }
  if (!defined(this._terria.leaflet) || !defined(this._terria.leaflet.map)) {
    // Test context or something has gone *so* badly wrong
    return;
  }
  this._terria.leaflet.scene.featureMousedown.addEventListener(
    this._onMouseDownOnPoint,
    this
  );
  this._setUp = true;
};

/**
 * Function that is called when the user clicks and holds on a point that was previously drawn.
 *
 * @param {Entity} entity The entity that user mouse downs on.
 */
LeafletDragPoints.prototype._onMouseDownOnPoint = function(entity) {
  if (
    !defined(this._draggableObjects.entities) ||
    this._draggableObjects.entities.length === 0
  ) {
    return;
  }

  var dragEntity = this._draggableObjects.entities.values.filter(function(
    dragObjEntity
  ) {
    // Not necessarily same entity, but will have same id.
    return dragObjEntity.id === entity.id;
  })[0];
  if (defined(dragEntity)) {
    // The touch events below don't actually work because Leaflet doesn't
    // expose these events.  See here for a possible workaround:
    // https://github.com/Leaflet/Leaflet/issues/1542
    this._terria.leaflet.map.on("mousemove", this._onMouseMove, this);
    this._terria.leaflet.map.on("touchmove", this._onMouseMove, this);
    this._terria.leaflet.map.on("mouseup", this._onMouseUp, this);
    this._terria.leaflet.map.on("touchend", this._onMouseUp, this);

    this._dragInProgress = true;
    this._entityDragged = dragEntity;

    this._terria.currentViewer.pauseMapInteraction();
    this._originalPosition = dragEntity.position;
  }
};

/**
 * Function that is called when the mouse moves.
 *
 * @param {Leaflet.MouseEvent} move Information about the move such as the final position of the mouse.
 */
LeafletDragPoints.prototype._onMouseMove = function(move) {
  if (!this._dragInProgress) {
    return;
  }
  this.dragCount = this.dragCount + 1;
  if (defined(this._entityDragged)) {
    this._entityDragged.position = Cartesian3.fromDegrees(
      move.latlng.lng,
      move.latlng.lat
    );
  }
};

/**
 * Function that is called when the user releases the mousedown click.
 *
 * @param {Leaflet.MouseEvent} e Information about where the event occurred.
 */
LeafletDragPoints.prototype._onMouseUp = function(e) {
  if (
    this._dragInProgress &&
    Cartesian3.fromDegrees(e.latlng.lng, e.latlng.lat) !==
      this._originalPosition
  ) {
    this._pointMovedCallback(this._draggableObjects);
  }
  this._terria.leaflet.map.off("mousemove", this._onMouseMove, this);
  this._terria.leaflet.map.off("touchmove", this._onMouseMove, this);
  this._terria.leaflet.map.off("mouseup", this._onMouseUp, this);
  this._terria.leaflet.map.off("touchend", this._onMouseUp, this);
  this._dragInProgress = false;
  this._terria.currentViewer.resumeMapInteraction();
};

/**
 * Update the list of draggable objects with a new list of entities that are able to be dragged. We are only interested
 * in entities that the user has drawn.
 *
 * @param {CustomDataSource} entities Entities that user has drawn on the map.
 */
LeafletDragPoints.prototype.updateDraggableObjects = function(entities) {
  this._draggableObjects = entities;
};

/**
 * A clean up function to call when destroying the object.
 */
LeafletDragPoints.prototype.destroy = function() {
  this._setUp = false;
};

module.exports = LeafletDragPoints;