Skip to content

Commit

Permalink
Add get bounds to geojson source (#5575)
Browse files Browse the repository at this point in the history
* Added get bounds to geojson source

* Fix lint, add changelog

* Update build test

* Add a test to cover the empty case.

* Make sure this works for more than one feature
  • Loading branch information
HarelM authored Mar 6, 2025
1 parent c9697c5 commit 2f5a47a
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## main

### ✨ Features and improvements

- Added `getBounds` to GeoJSON source to allow getting the boundaries of the data in it ([#5575](https://github.com/maplibre/maplibre-gl-js/pull/5575))
- Add a check for MouseEvent, to avoid errors when bot were crawling on website using Event instance instead of MouseEvent instance for types like mouseover, mouseout etc.. ([#5466](https://github.com/maplibre/maplibre-gl-js/pull/5466)).
- _...Add new stuff here..._

Expand Down
106 changes: 106 additions & 0 deletions src/source/geojson_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,113 @@ describe('GeoJSONSource#getData', () => {
// empty object instead of returning the result of an actual computation.
await expect(source.getData()).resolves.toStrictEqual({});
});
});

describe('GeoJSONSource#getBounds', () => {
const mapStub = {
_requestManager: {
transformRequest: (url) => { return {url}; }
}
} as any;

test('get bounds from empty geometry', async () => {
const source = new GeoJSONSource('id', {data: hawkHill} as GeoJSONSourceOptions, wrapDispatcher({
sendAsync(message) {
expect(message.type).toBe(MessageType.getData);
return Promise.resolve({
type: 'LineString',
coordinates: []
});
}
}), undefined);
source.map = mapStub;
const bounds = await source.getBounds();
expect(bounds.isEmpty()).toBeTruthy();
});

test('get bounds from geomerty collection', async () => {
const source = new GeoJSONSource('id', {data: hawkHill} as GeoJSONSourceOptions, wrapDispatcher({
sendAsync(message) {
expect(message.type).toBe(MessageType.getData);
return Promise.resolve({
type: 'GeometryCollection',
geometries: [{
type: 'LineString',
coordinates: [
[1.1, 1.2],
[1.3, 1.4]
]
}]
});
}
}), undefined);
source.map = mapStub;
const bounds = await source.getBounds();
expect(bounds.getNorthEast().lat).toBe(1.4);
expect(bounds.getNorthEast().lng).toBe(1.3);
expect(bounds.getSouthWest().lat).toBe(1.2);
expect(bounds.getSouthWest().lng).toBe(1.1);
});

test('get bounds from feature', async () => {
const source = new GeoJSONSource('id', {data: hawkHill} as GeoJSONSourceOptions, wrapDispatcher({
sendAsync(message) {
expect(message.type).toBe(MessageType.getData);
return Promise.resolve({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[1.1, 1.2],
[1.3, 1.4]
]
}
});
}
}), undefined);
source.map = mapStub;
const bounds = await source.getBounds();
expect(bounds.getNorthEast().lat).toBe(1.4);
expect(bounds.getNorthEast().lng).toBe(1.3);
expect(bounds.getSouthWest().lat).toBe(1.2);
expect(bounds.getSouthWest().lng).toBe(1.1);
});

test('get bounds from feature collection', async () => {
const source = new GeoJSONSource('id', {data: hawkHill} as GeoJSONSourceOptions, wrapDispatcher({
sendAsync(message) {
expect(message.type).toBe(MessageType.getData);
return Promise.resolve({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[1.1, 1.2],
[1.3, 1.8]
]
}
}, {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[1.5, 1.6],
[1.7, 1.4]
]
}
}]
});
}
}), undefined);
source.map = mapStub;
const bounds = await source.getBounds();
expect(bounds.getNorthEast().lat).toBe(1.8);
expect(bounds.getNorthEast().lng).toBe(1.7);
expect(bounds.getSouthWest().lat).toBe(1.2);
expect(bounds.getSouthWest().lng).toBe(1.1);
});
});

describe('GeoJSONSource#serialize', () => {
Expand Down
39 changes: 38 additions & 1 deletion src/source/geojson_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {extend, warnOnce} from '../util/util';
import {EXTENT} from '../data/extent';
import {ResourceType} from '../util/request_manager';
import {browser} from '../util/browser';
import {LngLatBounds} from '../geo/lng_lat_bounds';

import type {Source} from './source';
import type {Map} from '../ui/map';
Expand All @@ -13,7 +14,7 @@ import type {Actor} from '../util/actor';
import type {GeoJSONSourceSpecification, PromoteIdSpecification} from '@maplibre/maplibre-gl-style-spec';
import type {GeoJSONSourceDiff} from './geojson_source_diff';
import type {GeoJSONWorkerOptions, LoadGeoJSONParameters} from './geojson_worker_source';
import {type WorkerTileParameters} from './worker_source';
import type {WorkerTileParameters} from './worker_source';
import {MessageType} from '../util/actor_messages';

/**
Expand Down Expand Up @@ -249,6 +250,42 @@ export class GeoJSONSource extends Evented implements Source {
return this.actor.sendAsync({type: MessageType.getData, data: options});
}

private getCoordinatesFromGeometry(geometry: GeoJSON.Geometry): number[] {
if (geometry.type === 'GeometryCollection') {
return geometry.geometries.map((g: Exclude<GeoJSON.Geometry, GeoJSON.GeometryCollection>) => g.coordinates).flat(Infinity) as number[];
}
return geometry.coordinates.flat(Infinity) as number[];
}

/**
* Allows getting the source's boundaries.
* If there's a problem with the source's data, it will return an empty {@link LngLatBounds}.
* @returns a promise which resolves to the source's boundaries
*/
async getBounds(): Promise<LngLatBounds> {
const bounds = new LngLatBounds();
const data = await this.getData();
let coordinates: number[];
switch (data.type) {
case 'FeatureCollection':
coordinates = data.features.map(f => this.getCoordinatesFromGeometry(f.geometry)).flat(Infinity) as number[];
break;
case 'Feature':
coordinates = this.getCoordinatesFromGeometry(data.geometry);
break;
default:
coordinates = this.getCoordinatesFromGeometry(data);
break;
}
if (coordinates.length == 0) {
return bounds;
}
for (let i = 0; i < coordinates.length - 1; i += 2) {
bounds.extend([coordinates[i], coordinates[i+1]]);
}
return bounds;
}

/**
* To disable/enable clustering on the source options
* @param options - The options to set
Expand Down
2 changes: 1 addition & 1 deletion test/build/min.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('test min build', () => {
const decreaseQuota = 4096;

// feel free to update this value after you've checked that it has changed on purpose :-)
const expectedBytes = 910913;
const expectedBytes = 911111;

expect(actualBytes).toBeLessThan(expectedBytes + increaseQuota);
expect(actualBytes).toBeGreaterThan(expectedBytes - decreaseQuota);
Expand Down

0 comments on commit 2f5a47a

Please sign in to comment.