Skip to content

Commit 93ff941

Browse files
committed
Move getFitboundsLonRange to new location
1 parent 02073d3 commit 93ff941

5 files changed

Lines changed: 81 additions & 85 deletions

File tree

src/lib/geo_location_utils.js

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,60 @@ function computeBbox(d) {
381381
return turfBbox(d);
382382
}
383383

384+
/**
385+
* Pick a compact longitude range for `fitbounds`-style auto-framing when the
386+
* data straddles the antimeridian (±180°).
387+
*
388+
* Longitude is cyclic, so the naive [min, max] range used by the autorange
389+
* machinery can include a large empty span when points sit on both sides of
390+
* ±180° (e.g. lon = [131.8855, -179] spans ~311° the long way round, when the
391+
* compact view spans ~49° across the antimeridian). This finds the largest gap
392+
* between consecutive longitudes and, when that gap is wider than the gap across
393+
* the antimeridian, returns the complementary range so the map shows the dense
394+
* cluster of points rather than the empty ocean between them.
395+
*
396+
* The returned upper bound may exceed 180°; downstream `makeRangeBox` (and
397+
* MapLibre's `LngLatBounds`) handle ranges that cross the antimeridian without
398+
* ambiguity.
399+
*
400+
* @param {Array} lons - longitude values (may contain non-finite entries)
401+
* @return {Array|null} [lonStart, lonEnd] when an antimeridian-crossing range is
402+
* more compact, otherwise null (caller keeps the autorange result).
403+
*/
404+
function getFitboundsLonRange(lons) {
405+
const sorted = lons.filter(isFinite).sort((a, b) => a - b);
406+
if (sorted.length < 2) return null;
407+
408+
const n = sorted.length;
409+
const naiveSpan = sorted[n - 1] - sorted[0];
410+
// Data already wraps the whole globe; there is nothing to compact.
411+
if (naiveSpan >= 360) return null;
412+
413+
// Widest gap between consecutive longitudes.
414+
let maxGap = -Infinity;
415+
let gapStart = -1;
416+
for (let i = 0; i < n - 1; i++) {
417+
const gap = sorted[i + 1] - sorted[i];
418+
if (gap > maxGap) {
419+
maxGap = gap;
420+
gapStart = i;
421+
}
422+
}
423+
424+
// Only worth wrapping when an interior gap is wider than the gap that the
425+
// naive [min, max] range already leaves open across the antimeridian.
426+
const antimeridianGap = 360 - naiveSpan;
427+
if (maxGap <= antimeridianGap) return null;
428+
429+
return [sorted[gapStart + 1], sorted[gapStart] + 360];
430+
}
431+
384432
module.exports = {
385-
locationToFeature: locationToFeature,
386-
feature2polygons: feature2polygons,
387-
getTraceGeojson: getTraceGeojson,
388-
extractTraceFeature: extractTraceFeature,
389-
fetchTraceGeoData: fetchTraceGeoData,
390-
computeBbox: computeBbox
433+
locationToFeature,
434+
feature2polygons,
435+
getTraceGeojson,
436+
extractTraceFeature,
437+
fetchTraceGeoData,
438+
computeBbox,
439+
getFitboundsLonRange
391440
};

src/plots/geo/geo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ var selectOnClick = require('../../components/selections').selectOnClick;
2424

2525
var createGeoZoom = require('./zoom');
2626
var constants = require('./constants');
27-
var getFitboundsLonRange = require('./get_fitbounds_lon_range');
2827

2928
var geoUtils = require('../../lib/geo_location_utils');
29+
var getFitboundsLonRange = geoUtils.getFitboundsLonRange;
3030
var topojsonUtils = require('../../lib/topojson_utils');
3131
var topojsonFeature = require('topojson-client').feature;
3232

src/plots/geo/get_fitbounds_lon_range.js

Lines changed: 0 additions & 53 deletions
This file was deleted.

test/jasmine/tests/geo_test.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ var Lib = require('../../../src/lib');
44
var Geo = require('../../../src/plots/geo');
55
var GeoAssets = require('../../../src/assets/geo_assets');
66
var constants = require('../../../src/plots/geo/constants');
7-
var getFitboundsLonRange = require('../../../src/plots/geo/get_fitbounds_lon_range');
87
var geoLocationUtils = require('../../../src/lib/geo_location_utils');
98
var topojsonUtils = require('../../../src/lib/topojson_utils');
109

@@ -37,30 +36,6 @@ function move(fromX, fromY, toX, toY, delay) {
3736
});
3837
}
3938

40-
describe('Test geo fitbounds longitude range', function() {
41-
it('returns the compact crossing range when point data straddles the antimeridian', function() {
42-
expect(getFitboundsLonRange([131.8855, -179])).toEqual([131.8855, 181]);
43-
expect(getFitboundsLonRange([170, 175, -170])).toEqual([170, 190]);
44-
});
45-
46-
it('keeps the naive range (null) when the data does not straddle the antimeridian', function() {
47-
expect(getFitboundsLonRange([131.8855, 179])).toBe(null);
48-
expect(getFitboundsLonRange([-10, 0, 20])).toBe(null);
49-
});
50-
51-
it('keeps the naive range (null) when the data spans the whole globe', function() {
52-
var lons = [];
53-
for(var lon = 0; lon <= 360; lon += 2.5) lons.push(lon);
54-
expect(getFitboundsLonRange(lons)).toBe(null);
55-
});
56-
57-
it('returns null when fewer than two finite longitudes are available', function() {
58-
expect(getFitboundsLonRange([10])).toBe(null);
59-
expect(getFitboundsLonRange([NaN, 5])).toBe(null);
60-
expect(getFitboundsLonRange([])).toBe(null);
61-
});
62-
});
63-
6439
describe('Test geo fitbounds with antimeridian-straddling points', function() {
6540
var gd;
6641

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { getFitboundsLonRange } = require('../../../src/lib/geo_location_utils');
2+
3+
describe('Test geo_location_utils.getFitboundsLonRange', () => {
4+
it('returns the compact crossing range when point data straddles the antimeridian', () => {
5+
expect(getFitboundsLonRange([131.8855, -179])).toEqual([131.8855, 181]);
6+
expect(getFitboundsLonRange([170, 175, -170])).toEqual([170, 190]);
7+
});
8+
9+
it('keeps the naive range (null) when the data does not straddle the antimeridian', () => {
10+
expect(getFitboundsLonRange([131.8855, 179])).toBe(null);
11+
expect(getFitboundsLonRange([-10, 0, 20])).toBe(null);
12+
});
13+
14+
it('keeps the naive range (null) when the data spans the whole globe', () => {
15+
const lons = [];
16+
for (let lon = 0; lon <= 360; lon += 2.5) lons.push(lon);
17+
expect(getFitboundsLonRange(lons)).toBe(null);
18+
});
19+
20+
it('returns null when fewer than two finite longitudes are available', () => {
21+
expect(getFitboundsLonRange([10])).toBe(null);
22+
expect(getFitboundsLonRange([NaN, 5])).toBe(null);
23+
expect(getFitboundsLonRange([])).toBe(null);
24+
});
25+
});

0 commit comments

Comments
 (0)