Skip to content

Commit

Permalink
[Maps] auto-fit to data bounds (#72129) (#72536)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
nreese and elasticmachine authored Jul 20, 2020
1 parent e0dec6e commit 700a0c7
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 22 deletions.
23 changes: 20 additions & 3 deletions x-pack/plugins/maps/public/actions/data_request_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ import {
UPDATE_SOURCE_DATA_REQUEST,
} from './map_action_constants';
import { ILayer } from '../classes/layers/layer';
import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer';
import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types';
import { DataRequestAbortError } from '../classes/util/data_request';
import { scaleBounds } from '../elasticsearch_geo_utils';

const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1;

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

export function syncDataForAllLayers() {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const syncPromises = getLayerList(getState()).map(async (layer) => {
const syncPromises = getLayerList(getState()).map((layer) => {
return dispatch<any>(syncDataForLayer(layer));
});
await Promise.all(syncPromises);
};
}

export function syncDataForAllJoinLayers() {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const syncPromises = getLayerList(getState())
.filter((layer) => {
return 'hasJoins' in layer ? (layer as IVectorLayer).hasJoins() : false;
})
.map((layer) => {
return dispatch<any>(syncDataForLayer(layer));
});
await Promise.all(syncPromises);
};
}

export function syncDataForLayer(layer: ILayer) {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const dataRequestContext = getDataRequestContext(dispatch, getState, layer.getId());
Expand Down Expand Up @@ -284,7 +301,7 @@ export function fitToLayerExtent(layerId: string) {
getDataRequestContext(dispatch, getState, layerId)
);
if (bounds) {
await dispatch(setGotoWithBounds(bounds));
await dispatch(setGotoWithBounds(scaleBounds(bounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
}
} catch (error) {
if (!(error instanceof DataRequestAbortError)) {
Expand Down Expand Up @@ -359,7 +376,7 @@ export function fitToDataBounds() {
maxLat: turfUnionBbox[3],
};

dispatch(setGotoWithBounds(dataBounds));
dispatch(setGotoWithBounds(scaleBounds(dataBounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
};
}

Expand Down
37 changes: 26 additions & 11 deletions x-pack/plugins/maps/public/actions/map_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import { Dispatch } from 'redux';
// @ts-ignore
import turf from 'turf';
import uuid from 'uuid/v4';
import turfBooleanContains from '@turf/boolean-contains';
import { Filter, Query, TimeRange } from 'src/plugins/data/public';
import { MapStoreState } from '../reducers/store';
import {
getDataFilters,
getMapSettings,
getWaitingForMapReadyLayerListRaw,
getQuery,
} from '../selectors/map_selectors';
Expand Down Expand Up @@ -42,7 +44,11 @@ import {
UPDATE_DRAW_STATE,
UPDATE_MAP_SETTING,
} from './map_action_constants';
import { syncDataForAllLayers } from './data_request_actions';
import {
fitToDataBounds,
syncDataForAllJoinLayers,
syncDataForAllLayers,
} from './data_request_actions';
import { addLayer } from './layer_actions';
import { MapSettings } from '../reducers/map';
import {
Expand All @@ -51,6 +57,7 @@ import {
MapExtent,
MapRefreshConfig,
} from '../../common/descriptor_types';
import { scaleBounds } from '../elasticsearch_geo_utils';

export function setMapInitError(errorMessage: string) {
return {
Expand Down Expand Up @@ -134,15 +141,7 @@ export function mapExtentChanged(newMapConstants: { zoom: number; extent: MapExt
}

if (!doesBufferContainExtent || currentZoom !== newZoom) {
const scaleFactor = 0.5; // TODO put scale factor in store and fetch with selector
const width = extent.maxLon - extent.minLon;
const height = extent.maxLat - extent.minLat;
dataFilters.buffer = {
minLon: extent.minLon - width * scaleFactor,
minLat: extent.minLat - height * scaleFactor,
maxLon: extent.maxLon + width * scaleFactor,
maxLat: extent.maxLat + height * scaleFactor,
};
dataFilters.buffer = scaleBounds(extent, 0.5);
}
}

Expand Down Expand Up @@ -197,6 +196,7 @@ function generateQueryTimestamp() {
return new Date().toISOString();
}

let lastSetQueryCallId: string = '';
export function setQuery({
query,
timeFilters,
Expand Down Expand Up @@ -226,7 +226,22 @@ export function setQuery({
filters,
});

await dispatch<any>(syncDataForAllLayers());
if (getMapSettings(getState()).autoFitToDataBounds) {
// Joins are performed on the client.
// As a result, bounds for join layers must also be performed on the client.
// Therefore join layers need to fetch data prior to auto fitting bounds.
const localSetQueryCallId = uuid();
lastSetQueryCallId = localSetQueryCallId;
await dispatch<any>(syncDataForAllJoinLayers());

// setQuery can be triggered before async data fetching completes
// Only continue execution path if setQuery has not been re-triggered.
if (localSetQueryCallId === lastSetQueryCallId) {
dispatch<any>(fitToDataBounds());
}
} else {
await dispatch<any>(syncDataForAllLayers());
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
return [];
}

hasJoins() {
return false;
}

getSource() {
return this._isClustered ? this._clusterSource : this._documentSource;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface IVectorLayer extends ILayer {
getStyle(): IVectorStyle;
getFeatureById(id: string | number): Feature | null;
getPropertiesForTooltip(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
hasJoins(): boolean;
}

export class VectorLayer extends AbstractLayer implements IVectorLayer {
Expand Down Expand Up @@ -81,4 +82,5 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer {
getStyle(): IVectorStyle;
getFeatureById(id: string | number): Feature | null;
getPropertiesForTooltip(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
hasJoins(): boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class VectorLayer extends AbstractLayer {
});
}

_hasJoins() {
hasJoins() {
return this.getValidJoins().length > 0;
}

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

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

isLoadingBounds() {
const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID);
return !!boundsDataRequest && boundsDataRequest.isLoading();
}

async getLeftJoinFields() {
return await this.getSource().getLeftJoinFields();
}
Expand Down Expand Up @@ -583,7 +588,7 @@ export class VectorLayer extends AbstractLayer {
}

async syncData(syncContext) {
this._syncData(syncContext, this.getSource(), this.getCurrentStyle());
await this._syncData(syncContext, this.getSource(), this.getCurrentStyle());
}

// TLDR: Do not call getSource or getCurrentStyle in syncData flow. Use 'source' and 'style' arguments instead.
Expand All @@ -597,13 +602,16 @@ export class VectorLayer extends AbstractLayer {
// Given 2 above, which source/style to use can not be pulled from data request state.
// Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle.
async _syncData(syncContext, source, style) {
if (this.isLoadingBounds()) {
return;
}
await this._syncSourceStyleMeta(syncContext, source, style);
await this._syncSourceFormatters(syncContext, source, style);
const sourceResult = await this._syncSource(syncContext, source, style);
if (
!sourceResult.featureCollection ||
!sourceResult.featureCollection.features.length ||
!this._hasJoins()
!this.hasJoins()
) {
return;
}
Expand Down Expand Up @@ -711,7 +719,7 @@ export class VectorLayer extends AbstractLayer {
mbMap.addLayer(mbLayer);
}

const filterExpr = getPointFilterExpression(this._hasJoins());
const filterExpr = getPointFilterExpression(this.hasJoins());
if (filterExpr !== mbMap.getFilter(pointLayerId)) {
mbMap.setFilter(pointLayerId, filterExpr);
mbMap.setFilter(textLayerId, filterExpr);
Expand Down Expand Up @@ -747,7 +755,7 @@ export class VectorLayer extends AbstractLayer {
mbMap.addLayer(mbLayer);
}

const filterExpr = getPointFilterExpression(this._hasJoins());
const filterExpr = getPointFilterExpression(this.hasJoins());
if (filterExpr !== mbMap.getFilter(symbolLayerId)) {
mbMap.setFilter(symbolLayerId, filterExpr);
}
Expand All @@ -769,7 +777,7 @@ export class VectorLayer extends AbstractLayer {
const sourceId = this.getId();
const fillLayerId = this._getMbPolygonLayerId();
const lineLayerId = this._getMbLineLayerId();
const hasJoins = this._hasJoins();
const hasJoins = this.hasJoins();
if (!mbMap.getLayer(fillLayerId)) {
const mbLayer = {
id: fillLayerId,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export function MapSettingsPanel({
iconType="check"
onClick={keepChanges}
fill
data-test-subj="mapSettingSubmitButton"
>
<FormattedMessage
id="xpack.maps.mapSettingsPanel.keepChangesButtonLabel"
Expand Down
Loading

0 comments on commit 700a0c7

Please sign in to comment.