diff --git a/x-pack/plugins/gis/public/actions/store_actions.js b/x-pack/plugins/gis/public/actions/store_actions.js index dc6a170b6f470..9c06a186e5b46 100644 --- a/x-pack/plugins/gis/public/actions/store_actions.js +++ b/x-pack/plugins/gis/public/actions/store_actions.js @@ -251,12 +251,35 @@ export function clearMouseCoordinates() { return { type: CLEAR_MOUSE_COORDINATES }; } -export function setGoto({ lat, lon, zoom }) { + +export function fitToLayerExtent(layerId) { + return async function (dispatch, getState) { + const targetLayer = getLayerList(getState()).find(layer => { + return layer.getId() === layerId; + }); + + if (targetLayer) { + const dataFilters = getDataFilters(getState()); + const bounds = await targetLayer.getBounds(dataFilters); + if (bounds) { + await dispatch(setGotoWithBounds(bounds)); + } + } + }; +} + +export function setGotoWithBounds(bounds) { return { type: SET_GOTO, - lat, - lon, - zoom, + bounds: bounds + }; +} + + +export function setGotoWithCenter({ lat, lon, zoom }) { + return { + type: SET_GOTO, + center: { lat, lon, zoom } }; } diff --git a/x-pack/plugins/gis/public/angular/map_controller.js b/x-pack/plugins/gis/public/angular/map_controller.js index f6128b429bd0f..2c45fdcd5eae6 100644 --- a/x-pack/plugins/gis/public/angular/map_controller.js +++ b/x-pack/plugins/gis/public/angular/map_controller.js @@ -17,7 +17,7 @@ import { setSelectedLayer, setTimeFilters, setRefreshConfig, - setGoto, + setGotoWithCenter, replaceLayerList, setQuery, } from '../actions/store_actions'; @@ -85,7 +85,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage queryFromSavedObject = mapState.query; const timeFilters = mapState.timeFilters ? mapState.timeFilters : timefilter.getTime(); store.dispatch(setTimeFilters(timeFilters)); - store.dispatch(setGoto({ + store.dispatch(setGotoWithCenter({ lat: mapState.center.lat, lon: mapState.center.lon, zoom: mapState.zoom, diff --git a/x-pack/plugins/gis/public/components/layer_panel/index.js b/x-pack/plugins/gis/public/components/layer_panel/index.js index cd14860b7ccf3..4560660bcd733 100644 --- a/x-pack/plugins/gis/public/components/layer_panel/index.js +++ b/x-pack/plugins/gis/public/components/layer_panel/index.js @@ -7,6 +7,9 @@ import { connect } from 'react-redux'; import { LayerPanel } from './view'; import { getSelectedLayer } from '../../selectors/map_selectors'; +import { + fitToLayerExtent +} from '../../actions/store_actions'; function mapStateToProps(state = {}) { return { @@ -14,8 +17,12 @@ function mapStateToProps(state = {}) { }; } -function mapDispatchToProps(/* dispatch */) { - return {}; +function mapDispatchToProps(dispatch) { + return { + fitToBounds: (layerId) => { + dispatch(fitToLayerExtent(layerId)); + } + }; } const connectedLayerPanel = connect(mapStateToProps, mapDispatchToProps)(LayerPanel); diff --git a/x-pack/plugins/gis/public/components/layer_panel/settings_panel/index.js b/x-pack/plugins/gis/public/components/layer_panel/settings_panel/index.js index 2bbcbe3273e01..6a79f6fcd6bc9 100644 --- a/x-pack/plugins/gis/public/components/layer_panel/settings_panel/index.js +++ b/x-pack/plugins/gis/public/components/layer_panel/settings_panel/index.js @@ -24,7 +24,7 @@ function mapStateToProps(state = {}) { maxZoom: selectedLayer.getMaxZoom(), minZoom: selectedLayer.getMinZoom(), renderSourceDetails: selectedLayer.renderSourceDetails, - renderSourceSettingsEditor: selectedLayer.renderSourceSettingsEditor, + renderSourceSettingsEditor: selectedLayer.renderSourceSettingsEditor }; } diff --git a/x-pack/plugins/gis/public/components/layer_panel/view.js b/x-pack/plugins/gis/public/components/layer_panel/view.js index 1e827f74b598f..6c46c94c75602 100644 --- a/x-pack/plugins/gis/public/components/layer_panel/view.js +++ b/x-pack/plugins/gis/public/components/layer_panel/view.js @@ -12,6 +12,7 @@ import { FlyoutFooter } from './flyout_footer'; import { SettingsPanel } from './settings_panel'; import { + EuiButtonIcon, EuiFlexItem, EuiTitle, EuiPanel, @@ -70,7 +71,9 @@ export class LayerPanel extends React.Component { - {selectedLayer.getIcon()} + + Fit + diff --git a/x-pack/plugins/gis/public/components/map/mb/view.js b/x-pack/plugins/gis/public/components/map/mb/view.js index b8033c7e54ed4..6b5d3db00487f 100644 --- a/x-pack/plugins/gis/public/components/map/mb/view.js +++ b/x-pack/plugins/gis/public/components/map/mb/view.js @@ -10,6 +10,7 @@ import { ResizeChecker } from 'ui/resize_checker'; import { syncLayerOrder, removeOrphanedSourcesAndLayers, createMbMapInstance } from './utils'; import { inspectorAdapters } from '../../../kibana_services'; import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; +import mapboxgl from 'mapbox-gl'; export class MBMapContainer extends React.Component { @@ -61,7 +62,8 @@ export class MBMapContainer extends React.Component { } async _initializeMap() { - this._mbMap = await createMbMapInstance(this.refs.mapContainer, this.props.goto); + + this._mbMap = await createMbMapInstance(this.refs.mapContainer, this.props.goto ? this.props.goto.center : null); // Override mapboxgl.Map "on" and "removeLayer" methods so we can track layer listeners // Tracked layer listerners are used to clean up event handlers @@ -88,6 +90,7 @@ export class MBMapContainer extends React.Component { this.assignSizeWatch(); + // moveend callback is debounced to avoid updating map extent state while map extent is still changing // moveend is fired while the map extent is still changing in the following scenarios // 1) During opening/closing of layer details panel, the EUI animation results in 8 moveend events @@ -169,11 +172,23 @@ export class MBMapContainer extends React.Component { } clearGoto(); - this._mbMap.setZoom(goto.zoom); - this._mbMap.setCenter({ - lng: goto.lon, - lat: goto.lat - }); + + if (goto.bounds) { + //clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90) + const lnLatBounds = new mapboxgl.LngLatBounds( + new mapboxgl.LngLat(clamp(goto.bounds.min_lon, -180, 180), clamp(goto.bounds.min_lat, -89, 89)), + new mapboxgl.LngLat(clamp(goto.bounds.max_lon, -180, 180), clamp(goto.bounds.max_lat, -89, 89)), + ); + this._mbMap.fitBounds(lnLatBounds); + } else if (goto.center) { + this._mbMap.setZoom(goto.center.zoom); + this._mbMap.setCenter({ + lng: goto.center.lon, + lat: goto.center.lat + }); + } + + }; _syncMbMapWithLayerList = () => { @@ -217,3 +232,10 @@ export class MBMapContainer extends React.Component { ); } } + + +function clamp(val, min, max) { + if (val > max) val = max; + else if (val < min) val = min; + return val; +} diff --git a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss index 403c3e89a4391..6f699c5dc5ec7 100644 --- a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss +++ b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss @@ -11,6 +11,10 @@ opacity: 0.5; } +.gisTocEntry__grab { + margin-left: $euiSizeXS; +} + .gisTocEntry__grab:hover { cursor: grab; } diff --git a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/index.js b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/index.js index a8ba35f8aa0be..c774fe7ecd756 100644 --- a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/index.js +++ b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/index.js @@ -6,9 +6,9 @@ import _ from 'lodash'; import { connect } from 'react-redux'; -import { TOCEntry } from './toc_entry'; +import { TOCEntry } from './view'; import { updateFlyout, FLYOUT_STATE } from '../../../../../store/ui'; -import { setSelectedLayer, toggleLayerVisible } from '../../../../../actions/store_actions'; +import { fitToLayerExtent, setSelectedLayer, toggleLayerVisible } from '../../../../../actions/store_actions'; function mapStateToProps(state = {}) { return { @@ -22,7 +22,12 @@ function mapDispatchToProps(dispatch) { dispatch(setSelectedLayer(layerId)); dispatch(updateFlyout(FLYOUT_STATE.LAYER_PANEL)); }, - toggleVisible: layerId => dispatch(toggleLayerVisible(layerId)) + toggleVisible: layerId => { + dispatch(toggleLayerVisible(layerId)); + }, + fitToBounds: (layerId) => { + dispatch(fitToLayerExtent(layerId)); + } }); } diff --git a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.js b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/view.js similarity index 53% rename from x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.js rename to x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index 3789ad2c0df9f..0ad1a9e10fa52 100644 --- a/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.js +++ b/x-pack/plugins/gis/public/components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -9,12 +9,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, - EuiLoadingSpinner, - EuiToolTip, - EuiIconTip, EuiSpacer } from '@elastic/eui'; -import { VisibilityToggle } from '../../../../../shared/components/visibility_toggle'; +import { LayerTocActions } from '../../../../../shared/components/layer_toc_actions'; export class TOCEntry extends React.Component { @@ -26,69 +23,44 @@ export class TOCEntry extends React.Component { componentDidMount() { this._isMounted = true; + this._updateDisplayName(); } componentWillUnmount() { this._isMounted = false; } - _renderVisibilityToggle() { - const { layer, toggleVisible } = this.props; - return ( - toggleVisible(layer.getId())} - size={'l'} - > - {layer.getIcon()} - - ); - } - render() { - - const { layer, openLayerPanel, zoom } = this.props; - - const displayName = layer.getDisplayName(); - Promise.resolve(displayName).then(label => { - if (this._isMounted) { - if (label !== this.state.displayName) { - this.setState({ - displayName: label - }); - } + async _updateDisplayName() { + const label = await this.props.layer.getDisplayName(); + if (this._isMounted) { + if (label !== this.state.displayName) { + this.setState({ + displayName: label + }); } - }); - - let visibilityIndicator; - if (layer.dataHasLoadError()) { - visibilityIndicator = ( - - ); - } else if (layer.isLayerLoading()) { - visibilityIndicator = ; - } else if (!layer.showAtZoomLevel(zoom)) { - const { minZoom, maxZoom } = layer.getZoomConfig(); - visibilityIndicator = ( - - {this._renderVisibilityToggle()} - - ); - } else { - visibilityIndicator = this._renderVisibilityToggle(); } + } + componentDidUpdate() { + this._updateDisplayName(); + } + + render() { + + const { layer, openLayerPanel, zoom, toggleVisible, fitToBounds } = this.props; + const legendIcon = ( + { + fitToBounds(layer.getId()); + }} + zoom={zoom} + toggleVisible={() => { + toggleVisible(layer.getId()); + }} + /> + ); let tocDetails = layer.getTOCDetails(); if (tocDetails) { tocDetails = ( @@ -106,7 +78,7 @@ export class TOCEntry extends React.Component { data-layerid={layer.getId()} > - {visibilityIndicator} + { legendIcon }