OpenSourcePolitics / decidim-ditp

Decidim instance for DITP OGP
1 stars 3 forks source link

refactor, move to CRS #37

Open github-actions[bot] opened 1 year ago

github-actions[bot] commented 1 year ago

@option worldCopyJump: Boolean = false

With this option enabled, the map tracks when you pan to another "copy"

of the world and seamlessly jumps to the original one so that all overlays

like markers and vector layers are still visible.

If maxBounds is set, this option will control how solid the bounds

are when dragging the map around. The default value of 0.0 allows the

user to drag outside the bounds at normal speed, higher values will

slow down map dragging outside bounds, and 1.0 makes the bounds fully

solid, preventing the user from dragging outside the bounds.

https://api.github.com/OpenSourcePolitics/decidim-ditp/blob/cf68cc605e2b45b5d16865ebf4eadd87b89a898f/packages/core/node_modules/leaflet/src/map/handler/Map.Drag.js#L40


import {Map} from '../Map';
import * as Browser from '../../core/Browser';
import {Handler} from '../../core/Handler';
import {Draggable} from '../../dom/Draggable';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import {toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds';
import {toBounds} from '../../geometry/Bounds';

/*
 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
 */

// @namespace Map
// @section Interaction Options
Map.mergeOptions({
    // @option dragging: Boolean = true
    // Whether the map be draggable with mouse/touch or not.
    dragging: true,

    // @section Panning Inertia Options
    // @option inertia: Boolean = *
    // If enabled, panning of the map will have an inertia effect where
    // the map builds momentum while dragging and continues moving in
    // the same direction for some time. Feels especially nice on touch
    // devices. Enabled by default unless running on old Android devices.
    inertia: !Browser.android23,

    // @option inertiaDeceleration: Number = 3000
    // The rate with which the inertial movement slows down, in pixels/second².
    inertiaDeceleration: 3400, // px/s^2

    // @option inertiaMaxSpeed: Number = Infinity
    // Max speed of the inertial movement, in pixels/second.
    inertiaMaxSpeed: Infinity, // px/s

    // @option easeLinearity: Number = 0.2
    easeLinearity: 0.2,

    // TODO refactor, move to CRS
    // @option worldCopyJump: Boolean = false
    // With this option enabled, the map tracks when you pan to another "copy"
    // of the world and seamlessly jumps to the original one so that all overlays
    // like markers and vector layers are still visible.
    worldCopyJump: false,

    // @option maxBoundsViscosity: Number = 0.0
    // If `maxBounds` is set, this option will control how solid the bounds
    // are when dragging the map around. The default value of `0.0` allows the
    // user to drag outside the bounds at normal speed, higher values will
    // slow down map dragging outside bounds, and `1.0` makes the bounds fully
    // solid, preventing the user from dragging outside the bounds.
    maxBoundsViscosity: 0.0
});

export var Drag = Handler.extend({
    addHooks: function () {
        if (!this._draggable) {
            var map = this._map;

            this._draggable = new Draggable(map._mapPane, map._container);

            this._draggable.on({
                dragstart: this._onDragStart,
                drag: this._onDrag,
                dragend: this._onDragEnd
            }, this);

            this._draggable.on('predrag', this._onPreDragLimit, this);
            if (map.options.worldCopyJump) {
                this._draggable.on('predrag', this._onPreDragWrap, this);
                map.on('zoomend', this._onZoomEnd, this);

                map.whenReady(this._onZoomEnd, this);
            }
        }
        DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
        this._draggable.enable();
        this._positions = [];
        this._times = [];
    },

    removeHooks: function () {
        DomUtil.removeClass(this._map._container, 'leaflet-grab');
        DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
        this._draggable.disable();
    },

    moved: function () {
        return this._draggable && this._draggable._moved;
    },

    moving: function () {
        return this._draggable && this._draggable._moving;
    },

    _onDragStart: function () {
        var map = this._map;

        map._stop();
        if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
            var bounds = latLngBounds(this._map.options.maxBounds);

            this._offsetLimit = toBounds(
                this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
                this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
                    .add(this._map.getSize()));

            this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
        } else {
            this._offsetLimit = null;
        }

        map
            .fire('movestart')
            .fire('dragstart');

        if (map.options.inertia) {
            this._positions = [];
            this._times = [];
        }
    },

    _onDrag: function (e) {
        if (this._map.options.inertia) {
            var time = this._lastTime = +new Date(),
                pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;

            this._positions.push(pos);
            this._times.push(time);

            this._prunePositions(time);
        }

        this._map
            .fire('move', e)
            .fire('drag', e);
    },

    _prunePositions: function (time) {
        while (this._positions.length > 1 && time - this._times[0] > 50) {
            this._positions.shift();
            this._times.shift();
        }
    },

    _onZoomEnd: function () {
        var pxCenter = this._map.getSize().divideBy(2),
            pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);

        this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
        this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
    },

    _viscousLimit: function (value, threshold) {
        return value - (value - threshold) * this._viscosity;
    },

    _onPreDragLimit: function () {
        if (!this._viscosity || !this._offsetLimit) { return; }

        var offset = this._draggable._newPos.subtract(this._draggable._startPos);

        var limit = this._offsetLimit;
        if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
        if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
        if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
        if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }

        this._draggable._newPos = this._draggable._startPos.add(offset);
    },

    _onPreDragWrap: function () {
        // TODO refactor to be able to adjust map pane position after zoom
        var worldWidth = this._worldWidth,
            halfWidth = Math.round(worldWidth / 2),
            dx = this._initialWorldOffset,
            x = this._draggable._newPos.x,
            newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
            newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
            newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;

        this._draggable._absPos = this._draggable._newPos.clone();
        this._draggable._newPos.x = newX;
    },

    _onDragEnd: function (e) {
        var map = this._map,
            options = map.options,

            noInertia = !options.inertia || this._times.length < 2;

        map.fire('dragend', e);

        if (noInertia) {
            map.fire('moveend');

        } else {
            this._prunePositions(+new Date());

            var direction = this._lastPos.subtract(this._positions[0]),
                duration = (this._lastTime - this._times[0]) / 1000,
                ease = options.easeLinearity,

                speedVector = direction.multiplyBy(ease / duration),
                speed = speedVector.distanceTo([0, 0]),

                limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
                limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),

                decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
                offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();

            if (!offset.x && !offset.y) {
                map.fire('moveend');

            } else {
                offset = map._limitOffset(offset, map.options.maxBounds);

                Util.requestAnimFrame(function () {
                    map.panBy(offset, {
                        duration: decelerationDuration,
                        easeLinearity: ease,
                        noMoveStart: true,
                        animate: true
                    });
                });
            }
        }
    }
});

// @section Handlers
// @property dragging: Handler
// Map dragging handler (by both mouse and touch).
Map.addInitHook('addHandler', 'dragging', Drag);