@mapbox/react-map-gl
Version:
A React wrapper for MapboxGL-js and overlay API.
372 lines (318 loc) • 11.4 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import WebMercatorViewport, { normalizeViewportProps } from 'viewport-mercator-project';
import { TransitionInterpolator } from './transition';
import { clamp } from './math-utils';
import assert from './assert'; // MAPBOX LIMITS
export const MAPBOX_LIMITS = {
minZoom: 0,
maxZoom: 24,
minPitch: 0,
maxPitch: 60
};
const DEFAULT_STATE = {
pitch: 0,
bearing: 0,
altitude: 1.5
};
export default class MapState {
constructor(_ref) {
let width = _ref.width,
height = _ref.height,
latitude = _ref.latitude,
longitude = _ref.longitude,
zoom = _ref.zoom,
_ref$bearing = _ref.bearing,
bearing = _ref$bearing === void 0 ? DEFAULT_STATE.bearing : _ref$bearing,
_ref$pitch = _ref.pitch,
pitch = _ref$pitch === void 0 ? DEFAULT_STATE.pitch : _ref$pitch,
_ref$altitude = _ref.altitude,
altitude = _ref$altitude === void 0 ? DEFAULT_STATE.altitude : _ref$altitude,
_ref$maxZoom = _ref.maxZoom,
maxZoom = _ref$maxZoom === void 0 ? MAPBOX_LIMITS.maxZoom : _ref$maxZoom,
_ref$minZoom = _ref.minZoom,
minZoom = _ref$minZoom === void 0 ? MAPBOX_LIMITS.minZoom : _ref$minZoom,
_ref$maxPitch = _ref.maxPitch,
maxPitch = _ref$maxPitch === void 0 ? MAPBOX_LIMITS.maxPitch : _ref$maxPitch,
_ref$minPitch = _ref.minPitch,
minPitch = _ref$minPitch === void 0 ? MAPBOX_LIMITS.minPitch : _ref$minPitch,
transitionDuration = _ref.transitionDuration,
transitionEasing = _ref.transitionEasing,
transitionInterpolator = _ref.transitionInterpolator,
transitionInterruption = _ref.transitionInterruption,
startPanLngLat = _ref.startPanLngLat,
startZoomLngLat = _ref.startZoomLngLat,
startBearing = _ref.startBearing,
startPitch = _ref.startPitch,
startZoom = _ref.startZoom;
_defineProperty(this, "_viewportProps", void 0);
_defineProperty(this, "_interactiveState", void 0);
assert(Number.isFinite(width), '`width` must be supplied');
assert(Number.isFinite(height), '`height` must be supplied');
assert(Number.isFinite(longitude), '`longitude` must be supplied');
assert(Number.isFinite(latitude), '`latitude` must be supplied');
assert(Number.isFinite(zoom), '`zoom` must be supplied');
this._viewportProps = this._applyConstraints({
width,
height,
latitude,
longitude,
zoom,
bearing,
pitch,
altitude,
maxZoom,
minZoom,
maxPitch,
minPitch,
transitionDuration,
transitionEasing,
transitionInterpolator,
transitionInterruption
});
this._interactiveState = {
startPanLngLat,
startZoomLngLat,
startBearing,
startPitch,
startZoom
};
}
/* Public API */
getViewportProps() {
return this._viewportProps;
}
getInteractiveState() {
return this._interactiveState;
}
/**
* Start panning
* @param {[Number, Number]} pos - position on screen where the pointer grabs
*/
panStart(_ref2) {
let pos = _ref2.pos;
return this._getUpdatedMapState({
startPanLngLat: this._unproject(pos)
});
}
/**
* Pan
* @param {[Number, Number]} pos - position on screen where the pointer is
* @param {[Number, Number], optional} startPos - where the pointer grabbed at
* the start of the operation. Must be supplied of `panStart()` was not called
*/
pan(_ref3) {
let pos = _ref3.pos,
startPos = _ref3.startPos;
const startPanLngLat = this._interactiveState.startPanLngLat || this._unproject(startPos);
if (!startPanLngLat) {
return this;
}
const _this$_calculateNewLn = this._calculateNewLngLat({
startPanLngLat,
pos
}),
_this$_calculateNewLn2 = _slicedToArray(_this$_calculateNewLn, 2),
longitude = _this$_calculateNewLn2[0],
latitude = _this$_calculateNewLn2[1];
return this._getUpdatedMapState({
longitude,
latitude
});
}
/**
* End panning
* Must call if `panStart()` was called
*/
panEnd() {
return this._getUpdatedMapState({
startPanLngLat: null
});
}
/**
* Start rotating
* @param {[Number, Number]} pos - position on screen where the center is
*/
rotateStart(_ref4) {
let pos = _ref4.pos;
return this._getUpdatedMapState({
startBearing: this._viewportProps.bearing,
startPitch: this._viewportProps.pitch
});
}
/**
* Rotate
* @param {Number} deltaScaleX - a number between [-1, 1] specifying the
* change to bearing.
* @param {Number} deltaScaleY - a number between [-1, 1] specifying the
* change to pitch. -1 sets to minPitch and 1 sets to maxPitch.
*/
rotate(_ref5) {
let _ref5$deltaScaleX = _ref5.deltaScaleX,
deltaScaleX = _ref5$deltaScaleX === void 0 ? 0 : _ref5$deltaScaleX,
_ref5$deltaScaleY = _ref5.deltaScaleY,
deltaScaleY = _ref5$deltaScaleY === void 0 ? 0 : _ref5$deltaScaleY;
const _this$_interactiveSta = this._interactiveState,
startBearing = _this$_interactiveSta.startBearing,
startPitch = _this$_interactiveSta.startPitch;
if (!Number.isFinite(startBearing) || !Number.isFinite(startPitch)) {
return this;
}
const _this$_calculateNewPi = this._calculateNewPitchAndBearing({
deltaScaleX,
deltaScaleY,
startBearing: startBearing || 0,
startPitch: startPitch || 0
}),
pitch = _this$_calculateNewPi.pitch,
bearing = _this$_calculateNewPi.bearing;
return this._getUpdatedMapState({
bearing,
pitch
});
}
/**
* End rotating
* Must call if `rotateStart()` was called
*/
rotateEnd() {
return this._getUpdatedMapState({
startBearing: null,
startPitch: null
});
}
/**
* Start zooming
* @param {[Number, Number]} pos - position on screen where the center is
*/
zoomStart(_ref6) {
let pos = _ref6.pos;
return this._getUpdatedMapState({
startZoomLngLat: this._unproject(pos),
startZoom: this._viewportProps.zoom
});
}
/**
* Zoom
* @param {[Number, Number]} pos - position on screen where the current center is
* @param {[Number, Number]} startPos - the center position at
* the start of the operation. Must be supplied of `zoomStart()` was not called
* @param {Number} scale - a number between [0, 1] specifying the accumulated
* relative scale.
*/
zoom(_ref7) {
let pos = _ref7.pos,
startPos = _ref7.startPos,
scale = _ref7.scale;
assert(scale > 0, '`scale` must be a positive number'); // Make sure we zoom around the current mouse position rather than map center
let _this$_interactiveSta2 = this._interactiveState,
startZoom = _this$_interactiveSta2.startZoom,
startZoomLngLat = _this$_interactiveSta2.startZoomLngLat;
if (!Number.isFinite(startZoom)) {
// We have two modes of zoom:
// scroll zoom that are discrete events (transform from the current zoom level),
// and pinch zoom that are continuous events (transform from the zoom level when
// pinch started).
// If startZoom state is defined, then use the startZoom state;
// otherwise assume discrete zooming
startZoom = this._viewportProps.zoom;
startZoomLngLat = this._unproject(startPos) || this._unproject(pos);
} // take the start lnglat and put it where the mouse is down.
assert(startZoomLngLat, '`startZoomLngLat` prop is required ' + 'for zoom behavior to calculate where to position the map.');
const zoom = this._calculateNewZoom({
scale,
startZoom: startZoom || 0
});
const zoomedViewport = new WebMercatorViewport(Object.assign({}, this._viewportProps, {
zoom
})); // $FlowFixMe
const _zoomedViewport$getMa = zoomedViewport.getMapCenterByLngLatPosition({
lngLat: startZoomLngLat,
pos
}),
_zoomedViewport$getMa2 = _slicedToArray(_zoomedViewport$getMa, 2),
longitude = _zoomedViewport$getMa2[0],
latitude = _zoomedViewport$getMa2[1];
return this._getUpdatedMapState({
zoom,
longitude,
latitude
});
}
/**
* End zooming
* Must call if `zoomStart()` was called
*/
zoomEnd() {
return this._getUpdatedMapState({
startZoomLngLat: null,
startZoom: null
});
}
/* Private methods */
_getUpdatedMapState(newProps) {
// Update _viewportProps
return new MapState(Object.assign({}, this._viewportProps, this._interactiveState, newProps));
} // Apply any constraints (mathematical or defined by _viewportProps) to map state
_applyConstraints(props) {
// Ensure zoom is within specified range
const maxZoom = props.maxZoom,
minZoom = props.minZoom,
zoom = props.zoom;
props.zoom = clamp(zoom, minZoom, maxZoom); // Ensure pitch is within specified range
const maxPitch = props.maxPitch,
minPitch = props.minPitch,
pitch = props.pitch;
props.pitch = clamp(pitch, minPitch, maxPitch);
Object.assign(props, normalizeViewportProps(props));
return props;
}
_unproject(pos) {
const viewport = new WebMercatorViewport(this._viewportProps);
return pos && viewport.unproject(pos);
} // Calculate a new lnglat based on pixel dragging position
_calculateNewLngLat(_ref8) {
let startPanLngLat = _ref8.startPanLngLat,
pos = _ref8.pos;
const viewport = new WebMercatorViewport(this._viewportProps);
return viewport.getMapCenterByLngLatPosition({
lngLat: startPanLngLat,
pos
});
} // Calculates new zoom
_calculateNewZoom(_ref9) {
let scale = _ref9.scale,
startZoom = _ref9.startZoom;
const _this$_viewportProps = this._viewportProps,
maxZoom = _this$_viewportProps.maxZoom,
minZoom = _this$_viewportProps.minZoom;
const zoom = startZoom + Math.log2(scale);
return clamp(zoom, minZoom, maxZoom);
} // Calculates a new pitch and bearing from a position (coming from an event)
_calculateNewPitchAndBearing(_ref10) {
let deltaScaleX = _ref10.deltaScaleX,
deltaScaleY = _ref10.deltaScaleY,
startBearing = _ref10.startBearing,
startPitch = _ref10.startPitch;
// clamp deltaScaleY to [-1, 1] so that rotation is constrained between minPitch and maxPitch.
// deltaScaleX does not need to be clamped as bearing does not have constraints.
deltaScaleY = clamp(deltaScaleY, -1, 1);
const _this$_viewportProps2 = this._viewportProps,
minPitch = _this$_viewportProps2.minPitch,
maxPitch = _this$_viewportProps2.maxPitch;
const bearing = startBearing + 180 * deltaScaleX;
let pitch = startPitch;
if (deltaScaleY > 0) {
// Gradually increase pitch
pitch = startPitch + deltaScaleY * (maxPitch - startPitch);
} else if (deltaScaleY < 0) {
// Gradually decrease pitch
pitch = startPitch - deltaScaleY * (minPitch - startPitch);
}
return {
pitch,
bearing
};
}
}
//# sourceMappingURL=map-state.js.map