Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to create a filter from a lat/long. #144063 #145164

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type DrawState = {
filterLabel?: string; // point radius filter alias
geometryLabel?: string;
relation?: ES_SPATIAL_RELATIONS;
center?: MapCenter;
};

export type EditState = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -124,6 +126,15 @@ export class DrawControl extends Component<Props> {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class DrawFilterControl extends Component<Props, {}> {
? this.props.drawState.drawShape
: undefined
}
drawState={this.props.drawState}
onDraw={this._onDraw}
mbMap={this.props.mbMap}
enable={this.props.filterModeActive}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Props, State> {
state: State = {
locationParsingError: "",
location: "",
isLocationInvalid: undefined,
center: {
lat: 0,
lon: 0
}
};
_onCenterChange = (center:MapCenter)=>{
this.setState({center})
}
_onLocationChange = (evt: ChangeEvent<HTMLInputElement>) =>{

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 (
<Fragment>
<EuiFormRow
label={i18n.translate('xpack.maps.setViewControl.pasteLocation', {
defaultMessage: 'Paste Location',
})}
isInvalid={this.state.isLocationInvalid}
error={this.state.locationParsingError}
display="columnCompressed"
>
<EuiFieldText
compressed
value={this.state.location}
onChange={this._onLocationChange}
isInvalid={this.state.isLocationInvalid}
data-test-subj="pasteLocationInput"
/>

</EuiFormRow>
<EuiSpacer size="s" />

<EuiTextAlign textAlign="right">
<EuiButton
size="s"
fill
disabled={this.state.isLocationInvalid}
onClick={()=>{
let {lat,lon} = this.state.center
this.props.onSubmit(lat,lon)
}}
data-test-subj="submitViewButton"
>
<FormattedMessage
id="xpack.maps.setViewControl.submitButtonLabel"
defaultMessage="Go"
/>
</EuiButton>
</EuiTextAlign>
</Fragment>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -43,6 +43,7 @@ interface Props {
}

interface State {
isLocationPopoverOpen: boolean | undefined;
isPopoverOpen: boolean;
coordinateSystem: string;
}
Expand All @@ -51,6 +52,7 @@ export class SetViewForm extends Component<Props, State> {
state: State = {
coordinateSystem: DEGREES_DECIMAL,
isPopoverOpen: false,
isLocationPopoverOpen: false
};

_togglePopover = () => {
Expand All @@ -64,7 +66,19 @@ export class SetViewForm extends Component<Props, State> {
isPopoverOpen: false,
});
};

_toggleLocationPopover = () => {
this.setState((prevState) => ({
isLocationPopoverOpen: !prevState.isLocationPopoverOpen,
}));
};

_closeLocationPopover = () => {
this.setState({
isLocationPopoverOpen: false,
});
};

_onCoordinateSystemChange = (optionId: string) => {
this._closePopover();
this.setState({
Expand Down Expand Up @@ -127,6 +141,25 @@ export class SetViewForm extends Component<Props, State> {
onChange={this._onCoordinateSystemChange}
/>
</EuiPopover>
<EuiPopover
panelPaddingSize="s"
isOpen={this.state.isLocationPopoverOpen}
closePopover={this._closeLocationPopover}
button={
<EuiButtonEmpty iconType="console" size="xs" onClick={this._toggleLocationPopover}>
<FormattedMessage
id="xpack.maps.setViewControl.pasteLocationSystemButtonLabel"
defaultMessage="Paste Location"
/>
</EuiButtonEmpty>
}
>
<PasteLocationForm onSubmit={(lat:number,lon:number)=>{
this.props.onSubmit(lat,lon,this.props.zoom)
}}/>
</EuiPopover>


{this._renderForm()}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}

Expand All @@ -32,6 +36,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyActi
dispatch(setDrawMode(DRAW_MODE.DRAW_FILTERS));
dispatch(updateDrawState(drawState));
},
centerMap: (lat:number, lon:number, zoom:number) => {
dispatch(setGotoWithCenter({ lat, lon, zoom }));
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const defaultProps = {
activateDrawFilterMode: () => {},
deactivateDrawMode: () => {},
disableToolsControl: false,
zoom:1,
centerMap: ()=>{}
};

test('renders', async () => {
Expand Down
Loading