mirror of
https://github.com/visgl/react-map-gl.git
synced 2026-01-18 15:54:22 +00:00
652 lines
21 KiB
JavaScript
652 lines
21 KiB
JavaScript
// Copyright (c) 2015 Uber Technologies, Inc.
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var React = require('react');
|
|
var debounce = require('debounce');
|
|
var r = require('r-dom');
|
|
var d3 = require('d3');
|
|
var noop = require('./noop');
|
|
var assign = require('object-assign');
|
|
var Immutable = require('immutable');
|
|
var MapboxGL = require('mapbox-gl');
|
|
var LngLatBounds = MapboxGL.LngLatBounds;
|
|
var Point = MapboxGL.Point;
|
|
// NOTE: Transform is not a public API so we should be careful to always lock
|
|
// down mapbox-gl to a specific major, minor, and patch version.
|
|
var Transform = require('mapbox-gl/js/geo/transform');
|
|
var vec4 = require('gl-matrix').vec4;
|
|
|
|
var config = require('./config');
|
|
var MapInteractions = require('./map-interactions.react');
|
|
|
|
function mod(value, divisor) {
|
|
var modulus = value % divisor;
|
|
return modulus < 0 ? divisor + modulus : modulus;
|
|
}
|
|
|
|
function unproject(transform, point) {
|
|
return transform.pointLocation(MapboxGL.Point.convert(point));
|
|
}
|
|
|
|
function getBBoxFromTransform(transform, width, height) {
|
|
return [unproject(transform, [0, 0]), unproject(transform, [width, height])];
|
|
}
|
|
|
|
function cloneTransform(original) {
|
|
var transform = new Transform(original._minZoom, original._maxZoom);
|
|
transform.latRange = original.latRange;
|
|
transform.width = original.width;
|
|
transform.height = original.height;
|
|
transform.zoom = original.zoom;
|
|
transform.center = original.center;
|
|
transform.angle = original.angle;
|
|
transform.altitude = original.altitude;
|
|
transform.pitch = original.pitch;
|
|
return transform;
|
|
}
|
|
|
|
var MapGL = React.createClass({
|
|
|
|
displayName: 'MapGL',
|
|
|
|
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
|
|
var allTheSame = Object.keys(nextProps).reduce(function reduce(all, prop) {
|
|
var same = nextProps[prop] === this.props[prop];
|
|
return all && same;
|
|
}.bind(this), true);
|
|
|
|
if (!allTheSame) {
|
|
return true;
|
|
}
|
|
|
|
allTheSame = Object.keys(nextState).reduce(function reduce(all, prop) {
|
|
var same = nextState[prop] === this.state[prop];
|
|
return all && same;
|
|
}.bind(this), true);
|
|
|
|
return !allTheSame;
|
|
},
|
|
|
|
propTypes: {
|
|
/**
|
|
* The latitude of the center of the map.
|
|
*/
|
|
latitude: React.PropTypes.number.isRequired,
|
|
/**
|
|
* The longitude of the center of the map.
|
|
*/
|
|
longitude: React.PropTypes.number.isRequired,
|
|
/**
|
|
* The tile zoom level of the map.
|
|
*/
|
|
zoom: React.PropTypes.number.isRequired,
|
|
/**
|
|
* The Mapbox style the component should use. Can either be a string url
|
|
* or a MapboxGL style Immutable.Map object.
|
|
*/
|
|
mapStyle: React.PropTypes.oneOfType([
|
|
React.PropTypes.string,
|
|
React.PropTypes.instanceOf(Immutable.Map)
|
|
]),
|
|
/**
|
|
* The Mapbox API access token to provide to mapbox-gl-js. This is required
|
|
* when using Mapbox provided vector tiles and styles.
|
|
*/
|
|
mapboxApiAccessToken: React.PropTypes.string,
|
|
/**
|
|
* `onChangeViewport` callback is fired when the user interacted with the
|
|
* map. The object passed to the callback containers `latitude`,
|
|
* `longitude`, `zoom` and `bbox`. information.
|
|
*/
|
|
onChangeViewport: React.PropTypes.func,
|
|
/**
|
|
* The width of the map.
|
|
*/
|
|
width: React.PropTypes.number.isRequired,
|
|
/**
|
|
* The height of the map.
|
|
*/
|
|
height: React.PropTypes.number.isRequired,
|
|
/**
|
|
* Is the component currently being dragged. This is used to show/hide the
|
|
* drag cursor. Also used as an optimization in some overlays by preventing
|
|
* rendering while dragging.
|
|
*/
|
|
isDragging: React.PropTypes.bool,
|
|
/**
|
|
* Required to calculate the mouse projection after the first click event
|
|
* during dragging. Where the map is depends on where you first clicked on
|
|
* the map.
|
|
*/
|
|
startDragLatLng: React.PropTypes.array,
|
|
/**
|
|
* Called when a feature is hovered over. Features must set the
|
|
* `interactive` property to `true` for this to work properly. see the
|
|
* Mapbox example: https://www.mapbox.com/mapbox-gl-js/example/featuresat/
|
|
* The first argument of the callback will be the array of feature the
|
|
* mouse is over. This is the same response returned from `featuresAt`.
|
|
*/
|
|
onHoverFeatures: React.PropTypes.func,
|
|
|
|
/**
|
|
* Show attribution control or not.
|
|
*/
|
|
attributionControl: React.PropTypes.bool,
|
|
|
|
/**
|
|
* Called when a feature is clicked on. Features must set the
|
|
* `interactive` property to `true` for this to work properly. see the
|
|
* Mapbox example: https://www.mapbox.com/mapbox-gl-js/example/featuresat/
|
|
* The first argument of the callback will be the array of feature the
|
|
* mouse is over. This is the same response returned from `featuresAt`.
|
|
*/
|
|
onClickFeatures: React.PropTypes.func
|
|
},
|
|
|
|
getDefaultProps: function getDefaultProps() {
|
|
return {
|
|
mapStyle: 'mapbox://styles/mapbox/light-v8',
|
|
onChangeViewport: noop,
|
|
mapboxApiAccessToken: config.DEFAULTS.MAPBOX_API_ACCESS_TOKEN,
|
|
attributionControl: true
|
|
};
|
|
},
|
|
|
|
getInitialState: function getInitialState() {
|
|
var defaultState = {};
|
|
var stateChanges = this._updateStateFromProps(defaultState, this.props);
|
|
var state = assign({}, defaultState, stateChanges);
|
|
return state;
|
|
},
|
|
|
|
// New props are comin' round the corner!
|
|
componentWillReceiveProps: function componentWillReceiveProps(newProps) {
|
|
var stateChanges = this._updateStateFromProps(this.state, newProps);
|
|
this.setState(stateChanges);
|
|
},
|
|
|
|
// Use props to create an object of state changes.
|
|
_updateStateFromProps: function _updateStateFromProps(state, props) {
|
|
var stateChanges = {
|
|
latitude: props.latitude,
|
|
longitude: props.longitude,
|
|
zoom: props.zoom,
|
|
width: props.width,
|
|
height: props.height,
|
|
mapStyle: props.mapStyle,
|
|
startLatLng: props.startDragLatLng &&
|
|
new MapboxGL.LngLat(props.startDragLatLng[1], props.startDragLatLng[0])
|
|
};
|
|
|
|
assign(stateChanges, {
|
|
prevLatitude: state.latitude,
|
|
prevLongitude: state.longitude,
|
|
prevZoom: state.zoom,
|
|
prevWidth: state.width,
|
|
prevHeight: state.height,
|
|
prevMapStyle: state.mapStyle
|
|
});
|
|
|
|
MapboxGL.accessToken = props.mapboxApiAccessToken;
|
|
|
|
return stateChanges;
|
|
},
|
|
|
|
_onChangeViewport: function _onChangeViewport(_changes) {
|
|
var map = this._getMap();
|
|
var width = this.props.width;
|
|
var height = this.props.height;
|
|
var bbox = getBBoxFromTransform(map.transform, width, height);
|
|
var center = map.getCenter();
|
|
var startLatLng = this.state.startLatLng;
|
|
var changes = assign({
|
|
latitude: center.lat,
|
|
longitude: center.lng,
|
|
zoom: map.getZoom(),
|
|
bbox: bbox,
|
|
isDragging: this.props.isDragging,
|
|
startDragLatLng: startLatLng && [startLatLng.lat, startLatLng.lng]
|
|
}, _changes);
|
|
changes.longitude = mod(changes.longitude + 180, 360) - 180;
|
|
this.props.onChangeViewport(changes);
|
|
},
|
|
|
|
_getMap: function _getMap() {
|
|
return this._map;
|
|
},
|
|
|
|
componentDidMount: function componentDidMount() {
|
|
var mapStyle;
|
|
if (this.props.mapStyle instanceof Immutable.Map) {
|
|
mapStyle = this.props.mapStyle.toJS();
|
|
} else {
|
|
mapStyle = this.props.mapStyle;
|
|
}
|
|
var map = new MapboxGL.Map({
|
|
container: this.refs.mapboxMap.getDOMNode(),
|
|
center: [this.state.longitude, this.state.latitude],
|
|
zoom: this.state.zoom,
|
|
style: mapStyle,
|
|
interactive: false
|
|
// ,
|
|
// attributionControl: this.props.attributionControl
|
|
});
|
|
|
|
d3.select(map.getCanvas()).style('outline', 'none');
|
|
|
|
this._map = map;
|
|
this._updateMapViewport();
|
|
this._onChangeViewport();
|
|
},
|
|
|
|
_updateMapViewport: function _updateMapViewport() {
|
|
var state = this.state;
|
|
if (state.latitude !== state.prevLatitude ||
|
|
state.longitude !== state.prevLongitude ||
|
|
state.zoom !== state.prevZoom
|
|
) {
|
|
this._getMap().jumpTo({
|
|
center: [state.longitude, state.latitude],
|
|
zoom: state.zoom,
|
|
bearing: 0,
|
|
pitch: 0
|
|
});
|
|
}
|
|
if (state.width !== state.prevWidth || state.height !== state.prevHeight) {
|
|
this._resizeMap();
|
|
}
|
|
},
|
|
|
|
_resizeMap: debounce(function _resizeMap() {
|
|
var map = this._getMap();
|
|
map.resize();
|
|
}, 100),
|
|
|
|
_diffSources: function _diffSources(prevStyle, nextStyle) {
|
|
var prevSources = prevStyle.get('sources');
|
|
var nextSources = nextStyle.get('sources');
|
|
var enter = [];
|
|
var update = [];
|
|
var exit = [];
|
|
var prevIds = prevSources.keySeq().toArray();
|
|
var nextIds = nextSources.keySeq().toArray();
|
|
prevIds.forEach(function each(id) {
|
|
var nextSource = nextSources.get(id);
|
|
if (nextSource) {
|
|
if (!nextSource.equals(prevSources.get(id))) {
|
|
update.push({id: id, source: nextSources.get(id)});
|
|
}
|
|
} else {
|
|
exit.push({id: id, source: prevSources.get(id)});
|
|
}
|
|
});
|
|
nextIds.forEach(function each(id) {
|
|
var prevSource = prevSources.get(id);
|
|
if (!prevSource) {
|
|
enter.push({id: id, source: nextSources.get(id)});
|
|
}
|
|
});
|
|
return {enter: enter, update: update, exit: exit};
|
|
},
|
|
|
|
_diffLayers: function _diffLayers(prevStyle, nextStyle) {
|
|
var prevLayers = prevStyle.get('layers');
|
|
var nextLayers = nextStyle.get('layers');
|
|
var updates = [];
|
|
var exiting = [];
|
|
var prevMap = {};
|
|
var nextMap = {};
|
|
nextLayers.forEach(function map(layer, index) {
|
|
var id = layer.get('id');
|
|
var layerImBehind = nextLayers.get(index + 1);
|
|
nextMap[id] = {
|
|
layer: layer,
|
|
id: id,
|
|
// The `id` of the layer before this one.
|
|
before: layerImBehind ? layerImBehind.get('id') : null,
|
|
enter: true
|
|
};
|
|
});
|
|
prevLayers.forEach(function map(layer, index) {
|
|
var id = layer.get('id');
|
|
var layerImBehind = prevLayers.get(index + 1);
|
|
prevMap[id] = {
|
|
layer: layer,
|
|
id: id,
|
|
before: layerImBehind ? layerImBehind.get('id') : null
|
|
};
|
|
if (nextMap[id]) {
|
|
// Not a new layer.
|
|
nextMap[id].enter = false;
|
|
} else {
|
|
// This layer is being removed.
|
|
exiting.push(prevMap[id]);
|
|
}
|
|
});
|
|
nextLayers.reverse().forEach(function map(layer) {
|
|
var id = layer.get('id');
|
|
if (
|
|
!prevMap[id] ||
|
|
!prevMap[id].layer.equals(nextMap[id].layer) ||
|
|
prevMap[id].before !== nextMap[id].before
|
|
) {
|
|
// This layer is being changed.
|
|
updates.push(nextMap[id]);
|
|
}
|
|
});
|
|
return {updates: updates, exiting: exiting};
|
|
},
|
|
|
|
// Individually update the maps source and layers that have changed if all
|
|
// other style props haven't changed. This prevents flicking of the map when
|
|
// styles only change sources or layers.
|
|
_setDiffStyle: function _setDiffStyle(prevStyle, nextStyle) {
|
|
var map = this._getMap();
|
|
var prevKeysMap = prevStyle && styleKeysMap(prevStyle) || {};
|
|
var nextKeysMap = styleKeysMap(nextStyle);
|
|
function styleKeysMap(style) {
|
|
return style.map(function _map() {
|
|
return true;
|
|
}).delete('layers').delete('sources').toJS();
|
|
}
|
|
function propsOtherThanLayersOrSourcesDiffer() {
|
|
var prevKeysList = Object.keys(prevKeysMap);
|
|
var nextKeysList = Object.keys(nextKeysMap);
|
|
if (prevKeysList.length !== nextKeysList.length) {
|
|
return true;
|
|
}
|
|
// `nextStyle` and `prevStyle` should not have the same set of props.
|
|
if (nextKeysList.some(function forEach(key) {
|
|
// But the value of one of those props is different.
|
|
return prevStyle.get(key) !== nextStyle.get(key);
|
|
})) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!prevStyle || propsOtherThanLayersOrSourcesDiffer()) {
|
|
map.setStyle(nextStyle.toJS());
|
|
return;
|
|
}
|
|
|
|
var sourcesDiff = this._diffSources(prevStyle, nextStyle);
|
|
var layersDiff = this._diffLayers(prevStyle, nextStyle);
|
|
|
|
// TODO: It's rather difficult to determine style diffing in the presence
|
|
// of refs. For now, if any style update has a ref, fallback to no diffing.
|
|
// We can come back to this case if there's a solid usecase.
|
|
if (layersDiff.updates.some(function updatedNodeHasRef(node) {
|
|
return node.layer.get('ref');
|
|
})) {
|
|
map.setStyle(nextStyle.toJS());
|
|
return;
|
|
}
|
|
|
|
map.batch(function batchStyleUpdates() {
|
|
sourcesDiff.enter.forEach(function each(enter) {
|
|
map.addSource(enter.id, enter.source.toJS());
|
|
});
|
|
sourcesDiff.update.forEach(function each(update) {
|
|
map.removeSource(update.id);
|
|
map.addSource(update.id, update.source.toJS());
|
|
});
|
|
sourcesDiff.exit.forEach(function each(exit) {
|
|
map.removeSource(exit.id);
|
|
});
|
|
layersDiff.exiting.forEach(function forEach(exit) {
|
|
if (map.style.getLayer(exit.id)) {
|
|
map.removeLayer(exit.id);
|
|
}
|
|
});
|
|
layersDiff.updates.forEach(function forEach(update) {
|
|
if (!update.enter) {
|
|
// This is an old layer that needs to be updated. Remove the old layer
|
|
// with the same id and add it back again.
|
|
map.removeLayer(update.id);
|
|
}
|
|
map.addLayer(update.layer.toJS(), update.before);
|
|
});
|
|
});
|
|
},
|
|
|
|
_updateMapStyle: function _updateMapStyle() {
|
|
var mapStyle = this.state.mapStyle;
|
|
if (mapStyle !== this.state.prevMapStyle) {
|
|
if (mapStyle instanceof Immutable.Map) {
|
|
this._setDiffStyle(this.state.prevMapStyle, mapStyle);
|
|
} else {
|
|
this._getMap().setStyle(mapStyle);
|
|
}
|
|
}
|
|
},
|
|
|
|
componentDidUpdate: function componentDidUpdate() {
|
|
this._updateMapViewport();
|
|
this._updateMapStyle();
|
|
},
|
|
|
|
_onMouseDown: function _onMouseDown(opt) {
|
|
var map = this._getMap();
|
|
var startLatLng = unproject(map.transform, opt.pos);
|
|
this._onChangeViewport({
|
|
isDragging: true,
|
|
startDragLatLng: [startLatLng.lat, startLatLng.lng]
|
|
});
|
|
},
|
|
|
|
_onMouseDrag: function _onMouseDrag(opt) {
|
|
var p2 = opt.pos;
|
|
var map = this._getMap();
|
|
var width = this.props.width;
|
|
var height = this.props.height;
|
|
// take the start latlng and put it where the mouse is down.
|
|
var transform = cloneTransform(map.transform);
|
|
assert(this.state.startLatLng, '`startDragLatLng` prop is required for ' +
|
|
'mouse drag behavior.');
|
|
transform.setLocationAtPoint(this.state.startLatLng, p2);
|
|
var bbox = getBBoxFromTransform(transform, width, height);
|
|
this._onChangeViewport({
|
|
latitude: transform.center.lat,
|
|
longitude: transform.center.lng,
|
|
zoom: transform.zoom,
|
|
bbox: bbox,
|
|
isDragging: true
|
|
});
|
|
},
|
|
|
|
_onMouseMove: function _onMouseMove(opt) {
|
|
var map = this._getMap();
|
|
var pos = opt.pos;
|
|
if (!this.props.onHoverFeatures) {
|
|
return;
|
|
}
|
|
map.featuresAt([pos.x, pos.y], {}, function callback(error, features) {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
if (!features.length) {
|
|
return;
|
|
}
|
|
this.props.onHoverFeatures(features);
|
|
}.bind(this));
|
|
},
|
|
|
|
_onMouseUp: function _onMouseUp(opt) {
|
|
var map = this._getMap();
|
|
var width = this.props.width;
|
|
var height = this.props.height;
|
|
var transform = cloneTransform(map.transform);
|
|
|
|
this._onChangeViewport({
|
|
latitude: transform.center.lat,
|
|
longitude: transform.center.lng,
|
|
zoom: transform.zoom,
|
|
isDragging: false,
|
|
bbox: getBBoxFromTransform(transform, width, height)
|
|
});
|
|
|
|
if (!this.props.onClickFeatures) {
|
|
return;
|
|
}
|
|
|
|
var pos = opt.pos;
|
|
|
|
// Radius enables point features, like marker symbols, to be clicked.
|
|
map.featuresAt([pos.x, pos.y], {
|
|
radius: 15
|
|
}, function callback(error, features) {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
if (!features.length) {
|
|
return;
|
|
}
|
|
this.props.onClickFeatures(features);
|
|
}.bind(this));
|
|
},
|
|
|
|
_onZoom: function _onZoom(opt) {
|
|
var map = this._getMap();
|
|
var props = this.props;
|
|
var transform = cloneTransform(map.transform);
|
|
var around = unproject(transform, opt.pos);
|
|
transform.zoom = transform.scaleZoom(map.transform.scale * opt.scale);
|
|
transform.setLocationAtPoint(around, opt.pos);
|
|
this._onChangeViewport({
|
|
latitude: transform.center.lat,
|
|
longitude: transform.center.lng,
|
|
zoom: transform.zoom,
|
|
isDragging: true,
|
|
bbox: getBBoxFromTransform(transform, props.width, props.height)
|
|
});
|
|
},
|
|
|
|
_onZoomEnd: function _onZoomEnd() {
|
|
this._onChangeViewport({isDragging: false});
|
|
},
|
|
|
|
_renderOverlays: function _renderOverlays(transform) {
|
|
var children = [];
|
|
|
|
// Calculate the transformation matrix once for a given render cycle
|
|
// instead of for each point.
|
|
// from: mapbox-gl-js/js/geo/transform.js
|
|
var tileZoom = transform.tileZoom;
|
|
var coordinatePointMatrix = transform.coordinatePointMatrix(tileZoom);
|
|
function coordinatePoint(coord) {
|
|
var matrix = coordinatePointMatrix;
|
|
var p = vec4.transformMat4([], [coord.column, coord.row, 0, 1], matrix);
|
|
return new Point(p[0] / p[3], p[1] / p[3]);
|
|
}
|
|
|
|
function locationPoint(latlng) {
|
|
return coordinatePoint(transform.locationCoordinate(latlng));
|
|
}
|
|
|
|
function fastProject(latlng) {
|
|
return locationPoint(new MapboxGL.LngLat(latlng[1], latlng[0]));
|
|
}
|
|
|
|
React.Children.forEach(this.props.children, function _map(child) {
|
|
if (!child) {
|
|
return;
|
|
}
|
|
children.push(React.cloneElement(child, {
|
|
width: this.props.width,
|
|
height: this.props.height,
|
|
isDragging: this.props.isDragging,
|
|
project: fastProject,
|
|
unproject: unproject.bind(null, transform)
|
|
}));
|
|
}, this);
|
|
return r.div({
|
|
className: 'overlays',
|
|
style: {position: 'absolute', left: 0, top: 0}
|
|
}, children);
|
|
},
|
|
|
|
render: function render() {
|
|
var props = this.props;
|
|
var style = assign({}, props.style, {
|
|
width: props.width,
|
|
height: props.height,
|
|
cursor: this.props.isDragging ?
|
|
config.CURSOR.GRABBING : config.CURSOR.GRAB
|
|
});
|
|
|
|
var transform = new Transform();
|
|
transform.width = props.width;
|
|
transform.height = props.height;
|
|
transform.zoom = this.props.zoom;
|
|
transform.center.lat = this.props.latitude;
|
|
transform.center.lng = this.props.longitude;
|
|
|
|
return r.div({
|
|
style: assign({}, this.props.style, {
|
|
width: this.props.width,
|
|
height: this.props.height
|
|
})
|
|
}, [
|
|
r(MapInteractions, {
|
|
onMouseDown: this._onMouseDown,
|
|
onMouseDrag: this._onMouseDrag,
|
|
onMouseUp: this._onMouseUp,
|
|
onMouseMove: this._onMouseMove,
|
|
onZoom: this._onZoom,
|
|
onZoomEnd: this._onZoomEnd,
|
|
width: this.props.width,
|
|
height: this.props.height
|
|
}, [
|
|
r.div({ref: 'mapboxMap', style: style, className: props.className}),
|
|
this._renderOverlays(transform)
|
|
])
|
|
]
|
|
);
|
|
}
|
|
});
|
|
|
|
MapGL.fitBounds = function fitBounds(width, height, _bounds, options) {
|
|
var bounds = new LngLatBounds([_bounds[0].reverse(), _bounds[1].reverse()]);
|
|
options = options || {};
|
|
var padding = typeof options.padding === 'undefined' ? 0 : options.padding;
|
|
var offset = Point.convert([0, 0]);
|
|
var tr = new Transform();
|
|
tr.width = width;
|
|
tr.height = height;
|
|
var nw = tr.project(bounds.getNorthWest());
|
|
var se = tr.project(bounds.getSouthEast());
|
|
var size = se.sub(nw);
|
|
var scaleX = (tr.width - padding * 2 - Math.abs(offset.x) * 2) / size.x;
|
|
var scaleY = (tr.height - padding * 2 - Math.abs(offset.y) * 2) / size.y;
|
|
|
|
var center = tr.unproject(nw.add(se).div(2));
|
|
var zoom = tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY));
|
|
return {
|
|
latitude: center.lat,
|
|
longitude: center.lng,
|
|
zoom: zoom
|
|
};
|
|
};
|
|
|
|
module.exports = MapGL;
|