Skip to content

Commit

Permalink
Move bound estimation to model (#227) (#253)
Browse files Browse the repository at this point in the history
move map bounds to model and added unit test.

Signed-off-by: Vijayan Balasubramanian <balasvij@amazon.com>
  • Loading branch information
opensearch-trigger-bot[bot] authored Feb 17, 2023
1 parent c662b14 commit 7ce84a2
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 59 deletions.
42 changes: 42 additions & 0 deletions public/model/geo/filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { LngLat } from 'maplibre-gl';
import { GeoBounds } from '../map/boundary';
import { FilterMeta, GeoBoundingBoxFilter } from '../../../../../src/plugins/data/common';
import { buildBBoxFilter } from './filter';

describe('test bounding box filter', function () {
it('should return valid bounding box', function () {
const mockBounds: GeoBounds = {
bottomRight: new LngLat(-2.340000000000032, 27.67),
topLeft: new LngLat(-135.18, 71.01),
};
const mockFilterMeta: FilterMeta = {
alias: null,
disabled: true,
negate: false,
};
const actualGeoBoundingBoxFilter: GeoBoundingBoxFilter = buildBBoxFilter(
'field-name',
mockBounds,
mockFilterMeta
);
const expectedBounds = {
bottom_right: {
lat: mockBounds.bottomRight.lat,
lon: mockBounds.bottomRight.lng,
},
top_left: {
lat: mockBounds.topLeft.lat,
lon: mockBounds.topLeft.lng,
},
};
expect(actualGeoBoundingBoxFilter.geo_bounding_box).toEqual({
['field-name']: expectedBounds,
});
expect(actualGeoBoundingBoxFilter.meta.params).toEqual(expectedBounds);
});
});
38 changes: 38 additions & 0 deletions public/model/geo/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { LatLon } from '@opensearch-project/opensearch/api/types';
import { FilterMeta, GeoBoundingBoxFilter } from '../../../../../src/plugins/data/common';
import { GeoBounds } from '../map/boundary';

export const buildBBoxFilter = (
fieldName: string,
mapBounds: GeoBounds,
filterMeta: FilterMeta
): GeoBoundingBoxFilter => {
const bottomRight: LatLon = {
lon: mapBounds.bottomRight.lng,
lat: mapBounds.bottomRight.lat,
};

const topLeft: LatLon = {
lon: mapBounds.topLeft.lng,
lat: mapBounds.topLeft.lat,
};

const boundingBox = {
bottom_right: bottomRight,
top_left: topLeft,
};
return {
meta: {
...filterMeta,
params: boundingBox,
},
geo_bounding_box: {
[fieldName]: boundingBox,
},
};
};
68 changes: 13 additions & 55 deletions public/model/layerRenderController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { LngLatBounds, Map as Maplibre } from 'maplibre-gl';
import { Map as Maplibre } from 'maplibre-gl';
import { DocumentLayerSpecification, MapLayerSpecification } from './mapLayerType';
import { DASHBOARDS_MAPS_LAYER_TYPE, MAX_LONGITUDE, MIN_LONGITUDE } from '../../common';
import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../common';
import {
buildOpenSearchQuery,
Filter,
FilterMeta,
GeoBoundingBoxFilter,
getTime,
IOpenSearchDashboardsSearchResponse,
Expand All @@ -17,34 +18,13 @@ import {
import { layersFunctionMap } from './layersFunctions';
import { MapServices } from '../types';
import { MapState } from './mapState';
import {GeoBounds, getBounds} from './map/boundary';
import { buildBBoxFilter } from './geo/filter';

interface MaplibreRef {
current: Maplibre | null;
}

// calculate lng limits based on map bounds
// maps can render more than 1 copies of map at lower zoom level and displays
// one side from 1 copy and other side from other copy at higher zoom level if
// screen crosses internation dateline
function calculateBoundingBoxLngLimit(bounds: LngLatBounds) {
const boundsMinLng = bounds.getNorthWest().lng;
const boundsMaxLng = bounds.getSouthEast().lng;
// if bounds expands more than 360 then, consider complete globe is visible
if (boundsMaxLng - boundsMinLng >= MAX_LONGITUDE - MIN_LONGITUDE) {
return {
right: MAX_LONGITUDE,
left: MIN_LONGITUDE,
};
}
// wrap bounds if only portion of globe is visible
// wrap() returns a new LngLat object whose longitude is
// wrapped to the range (-180, 180).
return {
right: bounds.getSouthEast().wrap().lng,
left: bounds.getNorthWest().wrap().lng,
};
}

export const prepareDataLayerSource = (
layer: MapLayerSpecification,
mapState: MapState,
Expand Down Expand Up @@ -117,36 +97,14 @@ export const handleDataLayerRender = (
const geoFieldType = mapLayer.source.geoFieldType;

// geo bounding box query supports geo_point fields
if (
geoFieldType === 'geo_point' &&
mapLayer.source.useGeoBoundingBoxFilter &&
maplibreRef.current
) {
const mapBounds = maplibreRef.current.getBounds();
const lngLimit = calculateBoundingBoxLngLimit(mapBounds);
const filterBoundingBox = {
bottom_right: {
lon: lngLimit.right,
lat: mapBounds.getSouthEast().lat,
},
top_left: {
lon: lngLimit.left,
lat: mapBounds.getNorthWest().lat,
},
};
const geoBoundingBoxFilter: GeoBoundingBoxFilter = {
meta: {
disabled: false,
negate: false,
alias: null,
params: filterBoundingBox,
},
geo_bounding_box: {
[geoField]: filterBoundingBox,
},
};
filters.push(geoBoundingBoxFilter);
}
const mapBounds: GeoBounds = getBounds(maplibreRef.current!);
const meta: FilterMeta = {
alias: null,
disabled: !mapLayer.source.useGeoBoundingBoxFilter || geoFieldType !== 'geo_point',
negate: false,
};
const geoBoundingBoxFilter: GeoBoundingBoxFilter = buildBBoxFilter(geoField, mapBounds, meta);
filters.push(geoBoundingBoxFilter);

return prepareDataLayerSource(mapLayer, mapState, services, filters).then((result) => {
const { layer, dataSource } = result;
Expand Down
17 changes: 15 additions & 2 deletions public/model/map/__mocks__/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { LayerSpecification } from 'maplibre-gl';
import { LayerSpecification, LngLatBounds } from 'maplibre-gl';
import { MockLayer } from './layer';

export type Source = any;
Expand All @@ -14,11 +14,16 @@ export class MockMaplibreMap {
sources: Map<string, Source>;
};

constructor(layers: MockLayer[]) {
private _bounds: LngLatBounds;

constructor(layers: MockLayer[], bounds?: LngLatBounds) {
this._styles = {
layers: new Array<MockLayer>(...layers),
sources: new Map<string, Source>(),
};
if (bounds) {
this._bounds = bounds;
}
}

public addSource(sourceId: string, source: Source) {
Expand Down Expand Up @@ -109,4 +114,12 @@ export class MockMaplibreMap {
(layer) => !(layer.getProperty('id') as string).includes(layerId)
);
}

setBounds = (bounds: LngLatBounds) => {
this._bounds = bounds;
};

getBounds = (): LngLatBounds => {
return this._bounds;
};
}
42 changes: 42 additions & 0 deletions public/model/map/bondary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { LngLat, LngLatBounds, Map as Maplibre } from 'maplibre-gl';
import { MockMaplibreMap } from './__mocks__/map';
import { GeoBounds, getBounds } from './boundary';

describe('verify get bounds', function () {
it('should cover complete map if more than on copy is visible', function () {
const ne: LngLat = new LngLat(333.811, 82.8);
const sw: LngLat = new LngLat(-248.8, -79.75);
const mockMap = new MockMaplibreMap([], new LngLatBounds(sw, ne));
const expectedBounds: GeoBounds = {
bottomRight: new LngLat(180, -79.75),
topLeft: new LngLat(-180, 82.8),
};
expect(getBounds((mockMap as unknown) as Maplibre)).toEqual(expectedBounds);
});

it('should wrap if map crosses international date line', function () {
const ne: LngLat = new LngLat(11.56, 80.85);
const sw: LngLat = new LngLat(-220.77, 21.52);
const mockMap = new MockMaplibreMap([], new LngLatBounds(sw, ne));
const expectedBounds: GeoBounds = {
bottomRight: new LngLat(11.559999999999945, 21.52),
topLeft: new LngLat(139.23000000000002, 80.85),
};
expect(getBounds((mockMap as unknown) as Maplibre)).toEqual(expectedBounds);
});
it('should give same value as map bounds for other cases', function () {
const sw: LngLat = new LngLat(-135.18, 27.67);
const ne: LngLat = new LngLat(-2.34, 71.01);
const mockMap = new MockMaplibreMap([], new LngLatBounds(sw, ne));
const expectedBounds: GeoBounds = {
bottomRight: new LngLat(-2.340000000000032, 27.67),
topLeft: new LngLat(-135.18, 71.01),
};
expect(getBounds((mockMap as unknown) as Maplibre)).toEqual(expectedBounds);
});
});
40 changes: 40 additions & 0 deletions public/model/map/boundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { LngLat, LngLatBounds, Map as Maplibre } from 'maplibre-gl';
import { MAX_LONGITUDE, MIN_LONGITUDE } from '../../../common';

export interface GeoBounds {
bottomRight: LngLat;
topLeft: LngLat;
}

// calculate lng limits based on map bounds
// maps can render more than 1 copies of map at lower zoom level and displays
// one side from 1 copy and other side from other copy at higher zoom level if
// screen crosses internation dateline
export function getBounds(map: Maplibre): GeoBounds {
const mapBounds: LngLatBounds = map.getBounds();
const boundsMinLng: number = mapBounds.getNorthWest().lng;
const boundsMaxLng: number = mapBounds.getSouthEast().lng;

let right: number;
let left: number;
// if bounds expands more than 360 then, consider complete globe is visible
if (boundsMaxLng - boundsMinLng >= MAX_LONGITUDE - MIN_LONGITUDE) {
right = MAX_LONGITUDE;
left = MIN_LONGITUDE;
} else {
// wrap bounds if only portion of globe is visible
// wrap() returns a new LngLat object whose longitude is
// wrapped to the range (-180, 180).
right = mapBounds.getSouthEast().wrap().lng;
left = mapBounds.getNorthWest().wrap().lng;
}
return {
bottomRight: new LngLat(right, mapBounds.getSouthEast().lat),
topLeft: new LngLat(left, mapBounds.getNorthWest().lat),
};
}
7 changes: 5 additions & 2 deletions public/model/map/layer_operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import {
addLineLayer,
addPolygonLayer,
getLayers,
hasLayer, moveLayers, removeLayers,
hasLayer,
moveLayers,
removeLayers,
updateCircleLayer,
updateLineLayer,
updatePolygonLayer, updateLayerVisibility,
updatePolygonLayer,
updateLayerVisibility,
} from './layer_operations';
import { Map as Maplibre } from 'maplibre-gl';
import { MockMaplibreMap } from './__mocks__/map';
Expand Down
1 change: 1 addition & 0 deletions test/setup.jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ beforeEach(() => {
afterEach(() => {
console.error.mockRestore();
});
window.URL.createObjectURL = function () {};

0 comments on commit 7ce84a2

Please sign in to comment.