Skip to content

Commit

Permalink
[Backport 2024.01.xx]: #10136: Search for Map CRS coordinates (#10220,
Browse files Browse the repository at this point in the history
…#10305) (#10317)

* #10136: Search for Map CRS coordinates (#10220)

* #10136: Search for Map CRS coordinates
Description:
- handle current map CRS coordinate search
- Add new component for current map CRS coordinates search
- create a util function for getting extent based on extent to validate the mapCRS extent in case of seach by mapCRS coords
- write some unit tests accroding the new added code + changes

* #10136: Search for Map CRS coordinates
Description:
- add translations

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments
- handle projection bounds range

* #10136: Search for Map CRS coordinates
Description:
- fix FE failure by creating a custom component for DecimalCoordinateEditorSearch

* #10136: Search for Map CRS coordinates
Description:
- revert change in DecimalCoordinateEditor file to keep it as it is in MS

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments
- fix issue of not zooming to 0,0 for map crs option
- don't allow to change coords inputs beyond the allowable crs extent

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments

* #10136: Search for Map CRS coordinates
Description:
- fix clearing marker in switch to different crs

* #10136: Search for Map CRS coordinates
Description:
- fix issue in switch to aeronautical inputs then switch to map crs coord search

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments

* #10136: Search for Map CRS coordinates
Description:
- resolve review comments

* #10136: Search for Map CRS coordinates
Description:
- resolve jumping cursor to last number in input number in change
- Rename component to CRSCoordinateEditor

* #10136: Search for Map CRS coordinates
Description:
- handle localization into onFocus event in CRS coordinate editor

* #10136: Search for Map CRS coordinates
Description:
- remove util function and its test and add its logic to onFocus function directly to fix FE failure

* #10136: Search for Map CRS coordinates
Description:
- revert changes of onFocus, onBlur for IntlNumberFormControl

* #10136: Search for Map CRS coordinates
Description:
- fix issue in lon field

* #10136: Search for Map CRS coordinates (#10305)

* #10136: Search for Map CRS coordinates
Description:
- resolve a threshold in CRS coordinate in switch

* #10136: Search for Map CRS coordinates
Description:
- resolve not update the X/Y coods in case switch between map crs by storing the currentMapCRS into coordinate object
  • Loading branch information
mahmoudadel54 committed May 14, 2024
1 parent 9193a2d commit 694861c
Show file tree
Hide file tree
Showing 12 changed files with 538 additions and 22 deletions.
22 changes: 18 additions & 4 deletions web/client/components/mapcontrols/search/SearchBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import SearchBarToolbar from '../../search/SearchBarToolbar';
import { defaultSearchWrapper } from '../../search/SearchBarUtils';
import BookmarkSelect, {BookmarkOptions} from "../searchbookmarkconfig/BookmarkSelect";
import CoordinatesSearch, {CoordinateOptions} from "../searchcoordinates/CoordinatesSearch";
import CurrentMapCRSCoordSearch from '../searchcoordinates/CurrentMapCRSCoordSearch';
import tooltip from '../../misc/enhancers/tooltip';

const TMenuItem = tooltip(MenuItem);
Expand Down Expand Up @@ -101,6 +102,7 @@ export default ({
onZoomToPoint = () => {},
onClearBookmarkSearch = () => {},
onPurgeResults,
currentMapCRS = 'EPSG:4326',
items = [],
...props
}) => {
Expand All @@ -112,6 +114,13 @@ export default ({
}
}, [searchOptions?.services]);
useEffect(() => {
// Switch back to coordinate search when map CRS is EPSG:4326 and active tool is Map CRS coordinate search
if (currentMapCRS === 'EPSG:4326' && activeTool === 'mapCRSCoordinatesSearch') {
onChangeActiveSearchTool('coordinatesSearch');
}
}, [currentMapCRS]);

const selectedServices = searchOptions?.services?.filter((_, index) => selectedSearchService >= 0 ? selectedSearchService === index : true) ?? [];
const search = defaultSearchWrapper({
searchText,
Expand Down Expand Up @@ -160,6 +169,8 @@ export default ({
clearSearch={clearSearch}
onChangeActiveSearchTool={onChangeActiveSearchTool}
onClearBookmarkSearch={onClearBookmarkSearch}
currentMapCRS={currentMapCRS}
onChangeFormat={onChangeFormat}
/>);
}

Expand Down Expand Up @@ -224,7 +235,10 @@ export default ({
onCancelSelectedItem={onCancelSelectedItem}
onPurgeResults={onPurgeResults}/>
{activeTool === "coordinatesSearch" && showCoordinatesSearchOption &&
<CoordinatesSearch format={format} defaultZoomLevel={defaultZoomLevel} onClearCoordinatesSearch={onClearCoordinatesSearch} />
<CoordinatesSearch currentMapCRS={currentMapCRS} format={format} defaultZoomLevel={defaultZoomLevel} onClearCoordinatesSearch={onClearCoordinatesSearch} />
}
{activeTool === "mapCRSCoordinatesSearch" && showCoordinatesSearchOption && currentMapCRS &&
<CurrentMapCRSCoordSearch currentMapCRS={currentMapCRS} format={format} defaultZoomLevel={defaultZoomLevel} onClearCoordinatesSearch={onClearCoordinatesSearch} />
}
{
activeTool === "bookmarkSearch" && showBookMarkSearchOption &&
Expand Down Expand Up @@ -253,7 +267,7 @@ export default ({
clearSearch();
}
},
...(activeTool === "coordinatesSearch" &&
...(["coordinatesSearch", "mapCRSCoordinatesSearch"].includes(activeTool) &&
CoordinateOptions.removeIcon(activeTool, coordinate, onClearCoordinatesSearch, onChangeCoord))
}, {
glyph: searchIcon,
Expand All @@ -265,8 +279,8 @@ export default ({
visible: activeTool === "addressSearch" &&
(!(searchText !== "" || selectedItems && selectedItems.length > 0) || !splitTools),
onClick: () => isSearchClickable && search(),
...(activeTool === "coordinatesSearch" &&
CoordinateOptions.searchIcon(activeTool, coordinate, onZoomToPoint, defaultZoomLevel)),
...(["coordinatesSearch", "mapCRSCoordinatesSearch"].includes(activeTool) &&
CoordinateOptions.searchIcon(activeTool, coordinate, onZoomToPoint, defaultZoomLevel, currentMapCRS)),
...(activeTool === "bookmarkSearch" &&
BookmarkOptions.searchIcon(activeTool, props))
}, {
Expand Down
112 changes: 112 additions & 0 deletions web/client/components/mapcontrols/search/__tests__/SearchBar-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,17 @@ describe("test the SearchBar", () => {
expect(cog.length).toBe(1);
});

it('test zoomToPoint, with search, with decimal, with reset if mapCRS different than default 4326', () => {
const store = {dispatch: () => {}, subscribe: () => {}, getState: () => ({search: {coordinate: {lat: 0.05, lon: 0.05, yCoord: 2, xCoord: 2}}})};
ReactDOM.render(<Provider store={store}><SearchBar format="decimal" coordinate={{"lat": 0.05, "lon": 0.05, "yCoord": 2, "xCoord": 2}} activeSearchTool="mapCRSCoordinatesSearch" showOptions searchText={"va"} currentMapCRS={"EPSG:900913"} delay={0} typeAhead={false} /></Provider>, document.getElementById("container"));
let reset = document.getElementsByClassName("glyphicon-1-close");
let search = document.getElementsByClassName("glyphicon-search");
let cog = document.getElementsByClassName("glyphicon-cog");
expect(reset.length).toBe(1);
expect(search.length).toBe(1);
expect(cog.length).toBe(0);
});

it('test zoomToPoint, with search, with aeronautical, with reset', () => {
const store = {dispatch: () => {}, subscribe: () => {}, getState: () => ({search: {coordinate: {lat: 2, lon: 2}}})};
ReactDOM.render(<Provider store={store}><SearchBar format="aeronautical" activeSearchTool="coordinatesSearch" showOptions searchText={"va"} delay={0} typeAhead={false} /></Provider>, document.getElementById("container"));
Expand Down Expand Up @@ -342,6 +353,41 @@ describe("test the SearchBar", () => {
}
});
});
it('test calling zoomToPoint with onKeyDown event if mapCRS different than default 4326', (done) => {
const store = {
dispatch: () => {},
subscribe: () => {},
getState: () => ({search: {coordinate: {yCoord: 15, xCoord: 15}}})
};
ReactDOM.render(
<Provider store={store}>
<SearchBar
format="decimal"
activeSearchTool="mapCRSCoordinatesSearch"
showOptions
onZoomToPoint={(point, zoom, crs) => {
expect(point).toEqual({x: 15, y: 15});
expect(zoom).toEqual(12);
expect(crs).toEqual("EPSG:4326");
done();
}}
coordinate={{yCoord: 15, xCoord: 15}}
currentMapCRS={"EPSG:900913"}
typeAhead={false} />
</Provider>
, document.getElementById("container")
);
const container = document.getElementById('container');
const elements = container.querySelectorAll('input');
TestUtils.Simulate.keyDown(elements[0], {
keyCode: 13,
preventDefault: () => {
expect(true).toBe(true);
done();
}
});
});

it('Test SearchBar with not allowed e char for keyDown event', (done) => {
const store = {
dispatch: () => {},
Expand Down Expand Up @@ -374,6 +420,40 @@ describe("test the SearchBar", () => {
}
});
});
it('Test SearchBar with not allowed e char for keyDown event if mapCRS different than default 4326', (done) => {
const store = {
dispatch: () => {},
subscribe: () => {},
getState: () => ({search: {coordinate: {yCoord: 150, xCoord: 150}}})
};
ReactDOM.render(
<Provider store={store}>
<SearchBar
format="decimal"
activeSearchTool="mapCRSCoordinatesSearch"
showOptions
onZoomToPoint={() => {
expect(true).toBe(false);
}}
coordinate={{yCoord: 150, xCoord: 150}}
currentMapCRS={"EPSG:900913"}
typeAhead={false} />
</Provider>, document.getElementById("container")
);
const container = document.getElementById('container');
const elements = container.querySelectorAll('input');
expect(elements.length).toBe(2);
expect(elements[0].value).toBe('150');

TestUtils.Simulate.keyDown(elements[0], {
keyCode: 69, // char e
preventDefault: () => {
expect(true).toBe(true);
done();
}
});
});

it('Test SearchBar with valid onKeyDown event by pressing number 8', () => {
const store = {
dispatch: () => {},
Expand Down Expand Up @@ -405,6 +485,38 @@ describe("test the SearchBar", () => {
}
});
});
it('Test SearchBar with valid onKeyDown event by pressing number 8 if mapCRS different than default 4326', () => {
const store = {
dispatch: () => {},
subscribe: () => {},
getState: () => ({search: {coordinate: {yCoord: 1, xCoord: 1}}})
};
ReactDOM.render(
<Provider store={store}>
<SearchBar
format="decimal"
activeSearchTool="mapCRSCoordinatesSearch"
showOptions
onZoomToPoint={() => {
expect(true).toBe(false);
}}
coordinate={{yCoord: 1, xCoord: 1}}
currentMapCRS={"EPSG:900913"}
typeAhead={false} />
</Provider>, document.getElementById("container")
);
const container = document.getElementById('container');
const elements = container.querySelectorAll('input');
expect(elements.length).toBe(2);
expect(elements[0].value).toBe('1');

TestUtils.Simulate.keyDown(elements[0], {
keyCode: 56,
preventDefault: () => {
expect(true).toBe(false);
}
});
});

it('test showOptions false, only address tool visible', () => {
ReactDOM.render(<SearchBar splitTools showOptions={false} searchText={""} delay={0} typeAhead={false} />, document.getElementById("container"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Message from "../../I18N/Message";
import CoordinateEntry from "../../misc/coordinateeditors/CoordinateEntry";
import DropdownToolbarOptions from "../../misc/toolbar/DropdownToolbarOptions";
import { zoomAndAddPoint, changeCoord } from '../../../actions/search';
import { reproject} from '../../../utils/CoordinatesUtils';

/**
* CoordinateOptions for Search bar
Expand All @@ -26,10 +27,14 @@ import { zoomAndAddPoint, changeCoord } from '../../../actions/search';
export const CoordinateOptions = ({
clearCoordinates: (onClearCoordinatesSearch, onChangeCoord) =>{
onClearCoordinatesSearch({owner: "search"});
onChangeCoord("lat", "");
onChangeCoord("lon", "");
const clearedFields = ["lat", "lon", "xCoord", "yCoord", "currentMapXYCRS"];
const resetVal = '';
clearedFields.forEach(field => onChangeCoord(field, resetVal));
},
areValidCoordinates: (coordinate) => {
if (!coordinate) return false;
return isNumber(coordinate?.lon) && isNumber(coordinate?.lat);
},
areValidCoordinates: (coordinate) => isNumber(coordinate?.lon) && isNumber(coordinate?.lat),
zoomToPoint: (onZoomToPoint, coordinate, defaultZoomLevel = 12) => {
onZoomToPoint({
x: parseFloat(coordinate.lon),
Expand Down Expand Up @@ -63,19 +68,31 @@ export const CoordinateOptions = ({
coordinate,
onClearCoordinatesSearch,
onChangeCoord) =>({
visible: activeTool === "coordinatesSearch" && (isNumber(coordinate.lon) || isNumber(coordinate.lat)),
visible: (['coordinatesSearch', 'mapCRSCoordinatesSearch'].includes(activeTool)) && (isNumber(coordinate.lon) || isNumber(coordinate.lat) || isNumber(coordinate.xCoord) || isNumber(coordinate.yCoord)),
onClick: () => CoordinateOptions.clearCoordinates(onClearCoordinatesSearch, onChangeCoord)
}),
searchIcon: (activeTool, coordinate, onZoomToPoint, defaultZoomLevel) => ({
visible: activeTool === "coordinatesSearch",
visible: ["coordinatesSearch", "mapCRSCoordinatesSearch"].includes(activeTool),
onClick: () => {
if (activeTool === "coordinatesSearch" && CoordinateOptions.areValidCoordinates(coordinate)) {
if ((['coordinatesSearch', 'mapCRSCoordinatesSearch'].includes(activeTool)) && CoordinateOptions.areValidCoordinates(coordinate)) {
CoordinateOptions.zoomToPoint(onZoomToPoint, coordinate, defaultZoomLevel);
}
}
}),
coordinatesMenuItem: ({activeTool, searchText, clearSearch, onChangeActiveSearchTool, onClearBookmarkSearch}) =>(
<MenuItem active={activeTool === "coordinatesSearch"} onClick={() => {
coordinatesMenuItem: ({activeTool, searchText, clearSearch, onChangeActiveSearchTool, onClearBookmarkSearch, currentMapCRS, onChangeFormat}) =>{
if (currentMapCRS === 'EPSG:4326') {
return (<MenuItem active={activeTool === "coordinatesSearch"} onClick={() => {
if (searchText !== undefined && searchText !== "") {
clearSearch();
}
onClearBookmarkSearch("selected");
onChangeActiveSearchTool("coordinatesSearch");
document.dispatchEvent(new MouseEvent('click'));
}}>
<Glyphicon glyph={"search-coords"}/> <Message msgId="search.coordinatesSearch"/>
</MenuItem>);
}
return (<><MenuItem active={activeTool === "coordinatesSearch"} onClick={() => {
if (searchText !== undefined && searchText !== "") {
clearSearch();
}
Expand All @@ -85,7 +102,21 @@ export const CoordinateOptions = ({
}}>
<Glyphicon glyph={"search-coords"}/> <Message msgId="search.coordinatesSearch"/>
</MenuItem>
)
<MenuItem active={activeTool === "mapCRSCoordinatesSearch"} onClick={() => {
if (searchText !== undefined && searchText !== "") {
clearSearch();
}
onClearBookmarkSearch("selected");
onChangeActiveSearchTool("mapCRSCoordinatesSearch");
onChangeFormat("decimal");
document.dispatchEvent(new MouseEvent('click'));
}}>
<span style={{marginLeft: 20}}>
<Glyphicon glyph={"search-coords"}/> <Message msgId="search.currentMapCRS"/>
</span>
</MenuItem>
</>);
}
});


Expand All @@ -108,6 +139,7 @@ const CoordinatesSearch = ({
onZoomToPoint,
onChangeCoord,
defaultZoomLevel,
currentMapCRS,
aeronauticalOptions = {
seconds: {
decimals: 4,
Expand All @@ -131,9 +163,30 @@ const CoordinatesSearch = ({

const changeCoordinates = (coord, value) => {
onChangeCoord(coord, parseFloat(value));
// set current map crs to coordinate object
if (coordinate?.currentMapXYCRS !== currentMapCRS && currentMapCRS !== "EPSG:4326") onChangeCoord('currentMapXYCRS', currentMapCRS);
if (!areValidCoordinates()) {
onClearCoordinatesSearch({owner: "search"});
}
// if there is mapCRS available --> calculate X/Y values by reproject to display in case switch to MapCRS
if (currentMapCRS !== 'EPSG:4326') {
// if there are lat, lon values --> reproject the point and get xCoord and yCoord for map CRS
const latNumVal = coord === 'lat' ? parseFloat(value) : coordinate.lat;
const lonNumVal = coord === 'lon' ? parseFloat(value) : coordinate.lon;
const isLatNumberVal = isNumber(latNumVal) && !isNaN(latNumVal);
const isLonNumberVal = isNumber(lonNumVal) && !isNaN(lonNumVal);
if (isLatNumberVal && isLonNumberVal) {
const reprojectedValue = reproject([lonNumVal, latNumVal], 'EPSG:4326', currentMapCRS, true);
const parsedXCoord = parseFloat((reprojectedValue?.x));
const parsedYCoord = parseFloat((reprojectedValue?.y));
onChangeCoord('xCoord', parsedXCoord);
onChangeCoord('yCoord', parsedYCoord);

return;
}
coordinate.xCoord && onChangeCoord('xCoord', '');
coordinate.yCoord && onChangeCoord('yCoord', '');
}
};

const onZoom = () => {
Expand All @@ -147,6 +200,7 @@ const CoordinatesSearch = ({
<InputGroup >
<InputGroup.Addon style={{minWidth: 45}}><Message msgId="search.latitude"/></InputGroup.Addon>
<CoordinateEntry
owner="search"
format={format}
aeronauticalOptions={aeronauticalOptions}
coordinate="lat"
Expand All @@ -168,6 +222,7 @@ const CoordinatesSearch = ({
<InputGroup>
<InputGroup.Addon style={{minWidth: 45}}><Message msgId="search.longitude"/></InputGroup.Addon>
<CoordinateEntry
owner="search"
format={format}
aeronauticalOptions={aeronauticalOptions}
coordinate="lon"
Expand All @@ -194,7 +249,8 @@ CoordinatesSearch.propTypes = {
onClearCoordinatesSearch: PropTypes.func,
onZoomToPoint: PropTypes.func,
onChangeCoord: PropTypes.func,
defaultZoomLevel: PropTypes.number
defaultZoomLevel: PropTypes.number,
currentMapCRS: PropTypes.string
};

export default connect((state)=>{
Expand Down

0 comments on commit 694861c

Please sign in to comment.