diff --git a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts index 2a1e50feaae98..754aa495a5750 100644 --- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts +++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts @@ -76,6 +76,7 @@ export type DrawState = { filterLabel?: string; // point radius filter alias geometryLabel?: string; relation?: ES_SPATIAL_RELATIONS; + center?: MapCenter; }; export type EditState = { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx index a93fc3b0b2b36..7de50ef8e8046 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx @@ -13,12 +13,13 @@ import MapboxDraw from '@mapbox/mapbox-gl-draw'; import mapboxDrawStyles from '@mapbox/mapbox-gl-draw/src/lib/theme'; // @ts-expect-error import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; -import type { Map as MbMap } from '@kbn/mapbox-gl'; +import { Map as MbMap } from '@kbn/mapbox-gl'; import { Feature } from 'geojson'; import { MapMouseEvent } from '@kbn/mapbox-gl'; import { DRAW_SHAPE } from '../../../../common/constants'; import { DrawCircle, DRAW_CIRCLE_RADIUS_LABEL_STYLE } from './draw_circle'; import { DrawTooltip } from './draw_tooltip'; +import { DrawState } from '@kbn/maps-plugin/common/descriptor_types'; const DRAW_RECTANGLE = 'draw_rectangle'; const DRAW_CIRCLE = 'draw_circle'; @@ -28,6 +29,7 @@ mbDrawModes[DRAW_CIRCLE] = DrawCircle; export interface Props { drawShape?: DRAW_SHAPE; + drawState?:DrawState; onDraw: (event: { features: Feature[] }, drawControl?: MapboxDraw) => void; onClick?: (event: MapMouseEvent, drawControl?: MapboxDraw) => void; mbMap: MbMap; @@ -124,6 +126,15 @@ export class DrawControl extends Component { this._mbDrawControl.changeMode(DRAW_RECTANGLE); } else if (drawMode !== DRAW_CIRCLE && this.props.drawShape === DRAW_SHAPE.DISTANCE) { this._mbDrawControl.changeMode(DRAW_CIRCLE); + + if(this.props.drawState?.center){ + let {lat,lon} = this.props.drawState?.center + + let f = this._mbDrawControl.getAll() + f.features[0].properties.center = [lon,lat] + this._mbDrawControl.set(f) + + } } else if (drawMode !== DRAW_POLYGON && this.props.drawShape === DRAW_SHAPE.POLYGON) { this._mbDrawControl.changeMode(DRAW_POLYGON); } else if (drawMode !== DRAW_LINE_STRING && this.props.drawShape === DRAW_SHAPE.LINE) { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx index 9e6a138390c6e..b0d6c3bd65d4e 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx @@ -105,6 +105,7 @@ export class DrawFilterControl extends Component { ? this.props.drawState.drawShape : undefined } + drawState={this.props.drawState} onDraw={this._onDraw} mbMap={this.props.mbMap} enable={this.props.filterModeActive} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/paste_location_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/paste_location_form.tsx new file mode 100644 index 0000000000000..1bcd79acb61c4 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/paste_location_form.tsx @@ -0,0 +1,182 @@ + +import React, { ChangeEvent, Component, Fragment } from 'react'; +import { EuiButton, EuiFieldText, EuiFormRow, EuiSpacer, EuiTextAlign } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MapCenter } from '../../../../common/descriptor_types'; + +import { mgrsToDD, withinRange } from './utils'; + +const LOCATION_PATTERNS:any = { + dd: /^\s*?(-?\d{1,2}(?:\.\d+)?)\s*[,\s|]\s*(-?\d{1,3}(?:\.\d+)?)(?:\b|\D)$/g, + dms:/(^\d{1,6}(?:N|S))\s*[,\s|]\s*(\d{1,7}(?:E|W))/gi, + wkt:/^(?:\s?|\s+)point\s*\((-?\d+(?:\.\d+)?) (-?\d+(?:\.\d+)?)\)/gi, + mgrs:/^\d{1,2}\s?[^ABIOYZabioyz]\s?[A-Za-z]{2}\s?(?:[0-9]\s?[0-9]\s?)+$/, + json:/\{(?:\s+)?".+"[.\S\s]*}/ //Very crude JSON test but should work + } + + + interface Props { + onSubmit: (lat: number, lon: number) => void; + } + + interface State { + locationParsingError:string; + location: string ; + isLocationInvalid: boolean | undefined; + center: MapCenter + } + + export class PasteLocationForm extends Component { + state: State = { + locationParsingError: "", + location: "", + isLocationInvalid: undefined, + center: { + lat: 0, + lon: 0 + } + }; + _onCenterChange = (center:MapCenter)=>{ + this.setState({center}) + } + _onLocationChange = (evt: ChangeEvent) =>{ + + let loc_string = evt.target.value + let locationParsingError = "Location doesn't match DD,DMS,WKT,GEOJSON patterns" + let matched:boolean|string = false + for(let locationType in LOCATION_PATTERNS){ + let pattern = LOCATION_PATTERNS[locationType]; + let matches = loc_string.match(pattern); + if(matches?.length){ + matched = locationType + } + } + + if(matched){ + //parse the location and set the center + let center = {} as MapCenter; + let pattern,matches; + switch (matched) { + case "mgrs": + let loc = mgrsToDD(loc_string) + center.lat = (loc.north +loc.south)/2 + center.lon = (loc.west+loc.east)/2 + break; + case "wkt"://point(23 23) + pattern = LOCATION_PATTERNS[matched] + matches = pattern.exec(loc_string) + pattern.lastIndex = 0 //reset regex object for reuse + if(matches){ + center.lat = parseFloat(matches[2]) + center.lon = parseFloat(matches[1]) + } + break + case "dd": //123,123 + pattern = LOCATION_PATTERNS[matched] + matches = pattern.exec(loc_string) + pattern.lastIndex = 0 //reset regex object for reuse + if(matches){ + center.lat = parseFloat(matches[1]) + center.lon = parseFloat(matches[2]) + } + break + case "dms"://350724N 950724W Oklahoma ish + pattern = LOCATION_PATTERNS[matched] + matches = pattern.exec(loc_string) + pattern.lastIndex = 0 //reset regex object for reuse + if(matches){ + var lat = matches[1] + var lon = matches[2] + lat = lat.padStart(7,"0") + lon = lon.padStart(8,"0") + let southing = lat[6].toUpperCase() ==="S"; + let westing = lon[7].toUpperCase() === "W" + lat = lat.substr(0,6) + lon = lon.substr(0,7) + lat = parseInt(lat.substr(0,2))+parseInt(lat.substr(2,2))/60 + parseInt(lat.substr(4,2))/3600//degrees,minutes,seconds + lon = parseInt(lon.substr(0,3))+parseInt(lon.substr(3,2))/60 + parseInt(lon.substr(5,2))/3600//degrees,minutes,seconds + if(southing){ + lat *= -1 + } + if(westing){ + lon *= -1 + } + center.lat = lat + center.lon = lon + } + break + case "json": + try { + let json = JSON.parse(loc_string); + if(json.type === "Point"){//try geojson {"type": "Point","coordinates": [125.6, 10.1]} + center.lat = json.coordinates[1] + center.lon = json.coordinates[0] + }else if(json.lat){//try legacy es point {lat:123,lon:456} + center.lat = json.lat + center.lon = json.lon + }//TODO add an array with lat,lon?? even though it is already covered by DD + //TODO should we handle a feature group with a point? + } catch (error) { + this.setState({isLocationInvalid:true,location:loc_string}) + return + } + default: + break; + } + const { isInvalid: isLatInvalid, error: latError } = withinRange(center.lat, -90, 90); + const { isInvalid: isLonInvalid, error: lonError } = withinRange(center.lon, -180, 180); + if(!isLatInvalid && !isLonInvalid){ + this._onCenterChange(center) + }else{ + matched = false; + locationParsingError = latError || lonError || "" + } + + } + this.setState({locationParsingError,isLocationInvalid:!matched,location:evt.target.value}) + } + + render(): React.ReactNode { + return ( + + + + + + + + + { + let {lat,lon} = this.state.center + this.props.onSubmit(lat,lon) + }} + data-test-subj="submitViewButton" + > + + + + + ) + } +} \ No newline at end of file diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx index 28fe6073d7646..31d9b8664774e 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_form.tsx @@ -13,7 +13,7 @@ import { MapCenter, MapSettings } from '../../../../common/descriptor_types'; import { DecimalDegreesForm } from './decimal_degrees_form'; import { MgrsForm } from './mgrs_form'; import { UtmForm } from './utm_form'; - +import { PasteLocationForm } from './paste_location_form'; const DEGREES_DECIMAL = 'dd'; const MGRS = 'mgrs'; const UTM = 'utm'; @@ -43,6 +43,7 @@ interface Props { } interface State { + isLocationPopoverOpen: boolean | undefined; isPopoverOpen: boolean; coordinateSystem: string; } @@ -51,6 +52,7 @@ export class SetViewForm extends Component { state: State = { coordinateSystem: DEGREES_DECIMAL, isPopoverOpen: false, + isLocationPopoverOpen: false }; _togglePopover = () => { @@ -64,7 +66,19 @@ export class SetViewForm extends Component { isPopoverOpen: false, }); }; + + _toggleLocationPopover = () => { + this.setState((prevState) => ({ + isLocationPopoverOpen: !prevState.isLocationPopoverOpen, + })); + }; + _closeLocationPopover = () => { + this.setState({ + isLocationPopoverOpen: false, + }); + }; + _onCoordinateSystemChange = (optionId: string) => { this._closePopover(); this.setState({ @@ -127,6 +141,25 @@ export class SetViewForm extends Component { onChange={this._onCoordinateSystemChange} /> + + + + } + > + { + this.props.onSubmit(lat,lon,this.props.zoom) + }}/> + + + {this._renderForm()} ); diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts index 9b330b22721db..8f577d86653a2 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts @@ -14,11 +14,15 @@ import { MapStoreState } from '../../../reducers/store'; import { DrawState } from '../../../../common/descriptor_types'; import { DRAW_MODE } from '../../../../common/constants'; import { getDrawMode } from '../../../selectors/ui_selectors'; +import { getMapZoom } from '../../../selectors/map_selectors'; +import { setGotoWithCenter } from '../../../actions'; + function mapStateToProps(state: MapStoreState) { const drawMode = getDrawMode(state); return { filterModeActive: drawMode === DRAW_MODE.DRAW_FILTERS, + zoom: getMapZoom(state), }; } @@ -32,6 +36,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch { + dispatch(setGotoWithCenter({ lat, lon, zoom })); + } }; } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx index 443dd7b47a069..5104e00b2281e 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx @@ -16,6 +16,8 @@ const defaultProps = { activateDrawFilterMode: () => {}, deactivateDrawMode: () => {}, disableToolsControl: false, + zoom:1, + centerMap: ()=>{} }; test('renders', async () => { diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index 26f14b45cbe9f..bc5de13017a05 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { EuiButtonIcon, EuiPopover, @@ -21,7 +21,9 @@ import { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public'; import { DRAW_SHAPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/draw_forms/geometry_filter_form/geometry_filter_form'; import { DistanceFilterForm } from '../../../components/draw_forms/distance_filter_form'; -import { DrawState } from '../../../../common/descriptor_types'; +import { DrawState, MapCenter } from '../../../../common/descriptor_types'; +import { PasteLocationForm } from '../set_view_control/paste_location_form'; +import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; const DRAW_SHAPE_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', { defaultMessage: 'Draw shape to filter data', @@ -57,6 +59,8 @@ export interface Props { getActionContext?: () => ActionExecutionContext; initiateDraw: (drawState: DrawState) => void; disableToolsControl: boolean; + zoom:number; + centerMap: (lat: number, lon: number, zoom: number) => void; } interface State { @@ -111,14 +115,21 @@ export class ToolsControl extends Component { this._closePopover(); }; - _initiateDistanceDraw = (options: { actionId: string; filterLabel: string }) => { + _initiateDistanceDraw = (options: { actionId: string; filterLabel: string, center?:MapCenter|undefined }) => { this.props.initiateDraw({ drawShape: DRAW_SHAPE.DISTANCE, ...options, }); this._closePopover(); }; + _filterFromLocation = (lat:number,lon:number) =>{ + //Goto Center point on the map + this.props.centerMap(lat,lon,this.props.zoom) + //initDistanceDraw + this._initiateDistanceDraw({actionId:ACTION_GLOBAL_APPLY_FILTER,filterLabel:"Filter From Location",center:{lat,lon}}) //TODO allow setting teh label + + } _getDrawPanels() { const tools = [ { @@ -185,13 +196,16 @@ export class ToolsControl extends Component { id: 3, title: DRAW_DISTANCE_LABEL_SHORT, content: ( - + + + + ), }, ];