Source: Map/LeafletDragPoints.js

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

/**
 * 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;