Skip to content

Commit 06d6abc

Browse files
[Maps] auto-fit to data bounds (#72129)
* [Maps] auto-fit to data bounds * update jest snapshot * add buffer to fit to bounds * sync join layers prior to fitting to bounds * clean-up comment * better names * fix tslint errors * update functional test expect * add functional tests * clean-up * change test run location * fix test expect Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 1559268 commit 06d6abc

File tree

17 files changed

+249
-22
lines changed

17 files changed

+249
-22
lines changed

x-pack/plugins/maps/public/actions/data_request_actions.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ import {
3737
UPDATE_SOURCE_DATA_REQUEST,
3838
} from './map_action_constants';
3939
import { ILayer } from '../classes/layers/layer';
40+
import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer';
4041
import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types';
4142
import { DataRequestAbortError } from '../classes/util/data_request';
43+
import { scaleBounds } from '../elasticsearch_geo_utils';
44+
45+
const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1;
4246

4347
export type DataRequestContext = {
4448
startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void;
@@ -122,13 +126,26 @@ function getDataRequestContext(
122126

123127
export function syncDataForAllLayers() {
124128
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
125-
const syncPromises = getLayerList(getState()).map(async (layer) => {
129+
const syncPromises = getLayerList(getState()).map((layer) => {
126130
return dispatch<any>(syncDataForLayer(layer));
127131
});
128132
await Promise.all(syncPromises);
129133
};
130134
}
131135

136+
export function syncDataForAllJoinLayers() {
137+
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
138+
const syncPromises = getLayerList(getState())
139+
.filter((layer) => {
140+
return 'hasJoins' in layer ? (layer as IVectorLayer).hasJoins() : false;
141+
})
142+
.map((layer) => {
143+
return dispatch<any>(syncDataForLayer(layer));
144+
});
145+
await Promise.all(syncPromises);
146+
};
147+
}
148+
132149
export function syncDataForLayer(layer: ILayer) {
133150
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
134151
const dataRequestContext = getDataRequestContext(dispatch, getState, layer.getId());
@@ -284,7 +301,7 @@ export function fitToLayerExtent(layerId: string) {
284301
getDataRequestContext(dispatch, getState, layerId)
285302
);
286303
if (bounds) {
287-
await dispatch(setGotoWithBounds(bounds));
304+
await dispatch(setGotoWithBounds(scaleBounds(bounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
288305
}
289306
} catch (error) {
290307
if (!(error instanceof DataRequestAbortError)) {
@@ -359,7 +376,7 @@ export function fitToDataBounds() {
359376
maxLat: turfUnionBbox[3],
360377
};
361378

362-
dispatch(setGotoWithBounds(dataBounds));
379+
dispatch(setGotoWithBounds(scaleBounds(dataBounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
363380
};
364381
}
365382

x-pack/plugins/maps/public/actions/map_actions.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import { Dispatch } from 'redux';
99
// @ts-ignore
1010
import turf from 'turf';
11+
import uuid from 'uuid/v4';
1112
import turfBooleanContains from '@turf/boolean-contains';
1213
import { Filter, Query, TimeRange } from 'src/plugins/data/public';
1314
import { MapStoreState } from '../reducers/store';
1415
import {
1516
getDataFilters,
17+
getMapSettings,
1618
getWaitingForMapReadyLayerListRaw,
1719
getQuery,
1820
} from '../selectors/map_selectors';
@@ -42,7 +44,11 @@ import {
4244
UPDATE_DRAW_STATE,
4345
UPDATE_MAP_SETTING,
4446
} from './map_action_constants';
45-
import { syncDataForAllLayers } from './data_request_actions';
47+
import {
48+
fitToDataBounds,
49+
syncDataForAllJoinLayers,
50+
syncDataForAllLayers,
51+
} from './data_request_actions';
4652
import { addLayer } from './layer_actions';
4753
import { MapSettings } from '../reducers/map';
4854
import {
@@ -51,6 +57,7 @@ import {
5157
MapExtent,
5258
MapRefreshConfig,
5359
} from '../../common/descriptor_types';
60+
import { scaleBounds } from '../elasticsearch_geo_utils';
5461

5562
export function setMapInitError(errorMessage: string) {
5663
return {
@@ -134,15 +141,7 @@ export function mapExtentChanged(newMapConstants: { zoom: number; extent: MapExt
134141
}
135142

136143
if (!doesBufferContainExtent || currentZoom !== newZoom) {
137-
const scaleFactor = 0.5; // TODO put scale factor in store and fetch with selector
138-
const width = extent.maxLon - extent.minLon;
139-
const height = extent.maxLat - extent.minLat;
140-
dataFilters.buffer = {
141-
minLon: extent.minLon - width * scaleFactor,
142-
minLat: extent.minLat - height * scaleFactor,
143-
maxLon: extent.maxLon + width * scaleFactor,
144-
maxLat: extent.maxLat + height * scaleFactor,
145-
};
144+
dataFilters.buffer = scaleBounds(extent, 0.5);
146145
}
147146
}
148147

@@ -197,6 +196,7 @@ function generateQueryTimestamp() {
197196
return new Date().toISOString();
198197
}
199198

199+
let lastSetQueryCallId: string = '';
200200
export function setQuery({
201201
query,
202202
timeFilters,
@@ -226,7 +226,22 @@ export function setQuery({
226226
filters,
227227
});
228228

229-
await dispatch<any>(syncDataForAllLayers());
229+
if (getMapSettings(getState()).autoFitToDataBounds) {
230+
// Joins are performed on the client.
231+
// As a result, bounds for join layers must also be performed on the client.
232+
// Therefore join layers need to fetch data prior to auto fitting bounds.
233+
const localSetQueryCallId = uuid();
234+
lastSetQueryCallId = localSetQueryCallId;
235+
await dispatch<any>(syncDataForAllJoinLayers());
236+
237+
// setQuery can be triggered before async data fetching completes
238+
// Only continue execution path if setQuery has not been re-triggered.
239+
if (localSetQueryCallId === lastSetQueryCallId) {
240+
dispatch<any>(fitToDataBounds());
241+
}
242+
} else {
243+
await dispatch<any>(syncDataForAllLayers());
244+
}
230245
};
231246
}
232247

x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
237237
return [];
238238
}
239239

240+
hasJoins() {
241+
return false;
242+
}
243+
240244
getSource() {
241245
return this._isClustered ? this._clusterSource : this._documentSource;
242246
}

x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface IVectorLayer extends ILayer {
3535
getStyle(): IVectorStyle;
3636
getFeatureById(id: string | number): Feature | null;
3737
getPropertiesForTooltip(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
38+
hasJoins(): boolean;
3839
}
3940

4041
export class VectorLayer extends AbstractLayer implements IVectorLayer {
@@ -81,4 +82,5 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer {
8182
getStyle(): IVectorStyle;
8283
getFeatureById(id: string | number): Feature | null;
8384
getPropertiesForTooltip(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
85+
hasJoins(): boolean;
8486
}

x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class VectorLayer extends AbstractLayer {
8585
});
8686
}
8787

88-
_hasJoins() {
88+
hasJoins() {
8989
return this.getValidJoins().length > 0;
9090
}
9191

@@ -159,7 +159,7 @@ export class VectorLayer extends AbstractLayer {
159159
async getBounds({ startLoading, stopLoading, registerCancelCallback, dataFilters }) {
160160
const isStaticLayer = !this.getSource().isBoundsAware();
161161
if (isStaticLayer) {
162-
return getFeatureCollectionBounds(this._getSourceFeatureCollection(), this._hasJoins());
162+
return getFeatureCollectionBounds(this._getSourceFeatureCollection(), this.hasJoins());
163163
}
164164

165165
const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`);
@@ -193,6 +193,11 @@ export class VectorLayer extends AbstractLayer {
193193
return bounds;
194194
}
195195

196+
isLoadingBounds() {
197+
const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID);
198+
return !!boundsDataRequest && boundsDataRequest.isLoading();
199+
}
200+
196201
async getLeftJoinFields() {
197202
return await this.getSource().getLeftJoinFields();
198203
}
@@ -583,7 +588,7 @@ export class VectorLayer extends AbstractLayer {
583588
}
584589

585590
async syncData(syncContext) {
586-
this._syncData(syncContext, this.getSource(), this.getCurrentStyle());
591+
await this._syncData(syncContext, this.getSource(), this.getCurrentStyle());
587592
}
588593

589594
// TLDR: Do not call getSource or getCurrentStyle in syncData flow. Use 'source' and 'style' arguments instead.
@@ -597,13 +602,16 @@ export class VectorLayer extends AbstractLayer {
597602
// Given 2 above, which source/style to use can not be pulled from data request state.
598603
// Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle.
599604
async _syncData(syncContext, source, style) {
605+
if (this.isLoadingBounds()) {
606+
return;
607+
}
600608
await this._syncSourceStyleMeta(syncContext, source, style);
601609
await this._syncSourceFormatters(syncContext, source, style);
602610
const sourceResult = await this._syncSource(syncContext, source, style);
603611
if (
604612
!sourceResult.featureCollection ||
605613
!sourceResult.featureCollection.features.length ||
606-
!this._hasJoins()
614+
!this.hasJoins()
607615
) {
608616
return;
609617
}
@@ -711,7 +719,7 @@ export class VectorLayer extends AbstractLayer {
711719
mbMap.addLayer(mbLayer);
712720
}
713721

714-
const filterExpr = getPointFilterExpression(this._hasJoins());
722+
const filterExpr = getPointFilterExpression(this.hasJoins());
715723
if (filterExpr !== mbMap.getFilter(pointLayerId)) {
716724
mbMap.setFilter(pointLayerId, filterExpr);
717725
mbMap.setFilter(textLayerId, filterExpr);
@@ -747,7 +755,7 @@ export class VectorLayer extends AbstractLayer {
747755
mbMap.addLayer(mbLayer);
748756
}
749757

750-
const filterExpr = getPointFilterExpression(this._hasJoins());
758+
const filterExpr = getPointFilterExpression(this.hasJoins());
751759
if (filterExpr !== mbMap.getFilter(symbolLayerId)) {
752760
mbMap.setFilter(symbolLayerId, filterExpr);
753761
}
@@ -769,7 +777,7 @@ export class VectorLayer extends AbstractLayer {
769777
const sourceId = this.getId();
770778
const fillLayerId = this._getMbPolygonLayerId();
771779
const lineLayerId = this._getMbLineLayerId();
772-
const hasJoins = this._hasJoins();
780+
const hasJoins = this.hasJoins();
773781
if (!mbMap.getLayer(fillLayerId)) {
774782
const mbLayer = {
775783
id: fillLayerId,

x-pack/plugins/maps/public/connected_components/map_settings_panel/__snapshots__/navigation_panel.test.tsx.snap

Lines changed: 66 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export function MapSettingsPanel({
9696
iconType="check"
9797
onClick={keepChanges}
9898
fill
99+
data-test-subj="mapSettingSubmitButton"
99100
>
100101
<FormattedMessage
101102
id="xpack.maps.mapSettingsPanel.keepChangesButtonLabel"

0 commit comments

Comments
 (0)