Skip to content
3 changes: 1 addition & 2 deletions src/plots/map/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,5 @@ module.exports = {


mapOnErrorMsg: 'Map error.',


fitBoundsPadding: 20,
};
11 changes: 11 additions & 0 deletions src/plots/map/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ var attrs = module.exports = overrideAll({
'(in degrees, where *0* means perpendicular to the surface of the map) (map.pitch).'
].join(' ')
},
disableDynamicCentering: {
valType: 'boolean',
dflt: false,
description: [
'By default the map camera will be dynamically centered on lon/lat points.',
'This can be slow when the number of points are above 1e7.',
'It is recommended to disable this feature for large datasets.',
'Note: This feature is also disabled if center or zoom attributes are set',
'regardless of disableDynamicCentering\'s setting.'
].join(' ')
},

bounds: {
west: {
Expand Down
73 changes: 71 additions & 2 deletions src/plots/map/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,36 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
type: 'map',
attributes: layoutAttributes,
handleDefaults: handleDefaults,
partition: 'y'
partition: 'y',
fullData
});
};

function handleDefaults(containerIn, containerOut, coerce) {
function handleDefaults(containerIn, containerOut, coerce, opts) {
coerce('style');
coerce('center.lon');
coerce('center.lat');
coerce('zoom');
coerce('bearing');
coerce('pitch');
coerce('disableDynamicCentering');

// dynamically set center/zoom if neither param provided
if (!containerIn?.disableDynamicCentering && !containerIn?.center && !containerIn?.zoom) {
var [{ lon, lat }] = opts.fullData;
var { minLon, maxLon } = getLonBounds(lon);
var { minLat, maxLat } = getLatBounds(lat);
// this param is called bounds in mapLibre ctor
// not to be confused with maxBounds aliased below
containerOut._fitBounds = {
west: minLon,
east: maxLon,
south: minLat,
north: maxLat,
};
}

// bounds is really for setting maxBounds in mapLibre ctor
var west = coerce('bounds.west');
var east = coerce('bounds.east');
var south = coerce('bounds.south');
Expand All @@ -46,6 +64,57 @@ function handleDefaults(containerIn, containerOut, coerce) {
containerOut._input = containerIn;
}

function getLonBounds(lon) {
if (!lon.length) return { minLon: 0, maxLon: 0 };

// normalize to [0, 360)
const norm = lon.map(to360).sort((a, b) => a - b);

let maxGap = -1;
let gapIndex = 0;

// find largest gap
for (let i = 0; i < norm.length; i++) {
const curr = norm[i];
const next = norm[(i + 1) % norm.length];
const gap = (next - curr + 360) % 360;

if (gap > maxGap) {
maxGap = gap;
gapIndex = i;
}
}

// take complement of largest gap
let minLon = norm[(gapIndex + 1) % norm.length];
let maxLon = norm[gapIndex];
minLon = to180(minLon)
maxLon = to180(maxLon)

return { minLon, maxLon };

// https://gis.stackexchange.com/questions/201789/verifying-formula-that-will-convert-longitude-0-360-to-180-to-180
function to180(deg) {
return ((deg + 180) % 360) - 180
}
function to360(deg) {
return ((deg % 360) + 360) % 360;
}
}

function getLatBounds(lat) {
let minLat=lat[0]
let maxLat=lat[0]
for(let i = 1; i < lat.length; i++){
minLat=Math.min(minLat,lat[i])
maxLat=Math.max(maxLat,lat[i])
}
return {
minLat,
maxLat
};
}

function handleLayerDefaults(layerIn, layerOut) {
function coerce(attr, dflt) {
return Lib.coerce(layerIn, layerOut, layoutAttributes.layers, attr, dflt);
Expand Down
12 changes: 12 additions & 0 deletions src/plots/map/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {

var bounds = opts.bounds;
var maxBounds = bounds ? [[bounds.west, bounds.south], [bounds.east, bounds.north]] : null;
var _fitBounds = opts._fitBounds ? [
[opts._fitBounds.west, opts._fitBounds.south],
[opts._fitBounds.east, opts._fitBounds.north],
] : null;

// create the map!
var map = self.map = new maplibregl.Map({
Expand All @@ -90,6 +94,10 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
zoom: opts.zoom,
bearing: opts.bearing,
pitch: opts.pitch,
bounds: _fitBounds,
fitBoundsOptions: {
padding: constants.fitBoundsPadding,
},
maxBounds: maxBounds,

interactive: !self.isStatic,
Expand Down Expand Up @@ -334,6 +342,10 @@ proto.updateLayout = function(fullLayout) {
if(!this.dragging && !this.wheeling) {
map.setCenter(convertCenter(opts.center));
map.setZoom(opts.zoom);
if (opts._fitBounds) {
var { west, south, east, north } = opts._fitBounds
map.fitBounds([[west, south], [east, north]], { padding: constants.fitBoundsPadding })
}
map.setBearing(opts.bearing);
map.setPitch(opts.pitch);
}
Expand Down
15 changes: 15 additions & 0 deletions test/image/mocks/map_dynamic_defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"data": [
{
"hovertext": ["San Marino", "Cairo", "Istanbul", "Trondheim"],
"lat": [43.9360958, 30.06263, 41.01384, 63.43049],
"legendgroup": "",
"lon": [12.4417702, 31.24967, 28.94966, 10.39506],
"marker": {
"color": "#636efa"
},
"mode": "markers",
"type": "scattermap"
}
]
}
15 changes: 15 additions & 0 deletions test/image/mocks/map_dynamic_defaults_anti_meridian.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"data": [
{
"hovertext": ["P1", "P2", "P3", "P4"],
"lat": [43.9360958, 30.06263, 41.01384, 63.43049],
"legendgroup": "",
"lon": [170, 180, -180, -170],
"marker": {
"color": "#636efa"
},
"mode": "markers",
"type": "scattermap"
}
]
}
15 changes: 15 additions & 0 deletions test/image/mocks/map_dynamic_defaults_anti_meridian_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"data": [
{
"hovertext": ["P1", "P2", "P3", "P4"],
"lat": [43.9360958, 30.06263, 41.01384, 43.43049],
"legendgroup": "",
"lon": [100, 180, -180, -170],
"marker": {
"color": "#636efa"
},
"mode": "markers",
"type": "scattermap"
}
]
}
23 changes: 23 additions & 0 deletions test/image/mocks/map_dynamic_defaults_center_set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"data": [
{
"hovertext": ["San Marino", "Cairo", "Istanbul", "Trondheim"],
"lat": [43.9360958, 30.06263, 41.01384, 63.43049],
"legendgroup": "",
"lon": [12.4417702, 31.24967, 28.94966, 10.39506],
"marker": {
"color": "#636efa"
},
"mode": "markers",
"type": "scattermap"
}
],
"layout": {
"map": {
"center": {
"lat": 90,
"lon": 90
}
}
}
}
20 changes: 20 additions & 0 deletions test/image/mocks/map_dynamic_defaults_zoom_set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"data": [
{
"hovertext": ["San Marino", "Cairo", "Istanbul", "Trondheim"],
"lat": [43.9360958, 30.06263, 41.01384, 63.43049],
"legendgroup": "",
"lon": [12.4417702, 31.24967, 28.94966, 10.39506],
"marker": {
"color": "#636efa"
},
"mode": "markers",
"type": "scattermap"
}
],
"layout": {
"map": {
"zoom": 2
}
}
}