{
+ const {zoomToPoint, areValidCoordinates} = CoordinateOptions;
+ const getConstraintsCoordEditor = (projection) =>{
+ const extent = getProjection(projection).extent;
+ const [minx, miny, maxx, maxy] = extent;
+ return {
+ decimal: {
+ yCoord: {
+ min: miny,
+ max: maxy
+ },
+ xCoord: {
+ min: minx,
+ max: maxx
+ }
+ }
+ };
+ };
+ // helper function to validate if the coordinate is within the crs extent or not
+ const isCoordWithinCrs = (value, coordType) => {
+ const min = getConstraintsCoordEditor(currentMapCRS).decimal[coordType]?.min || 0;
+ const max = getConstraintsCoordEditor(currentMapCRS).decimal[coordType]?.max || 0;
+ const coordValue = parseFloat(value);
+ if (isNaN(coordValue) || coordValue < min || coordValue > max ) {
+ return false;
+ }
+ return true;
+ };
+ React.useEffect(() => {
+ if (!currentMapCRS || currentMapCRS === 'EPSG:4326') return;
+ // if there are lat, lon values --> reproject the point and get xCoord and yCoord for map CRS
+ const isLatNumberVal = isNumber(coordinate.lat) && !isNaN(coordinate.lat);
+ const isLonNumberVal = isNumber(coordinate.lon) && !isNaN(coordinate.lon);
+ if (isLatNumberVal && isLonNumberVal) {
+ const reprojectedValue = reproject([coordinate.lon, coordinate.lat], 'EPSG:4326', currentMapCRS, true);
+ const parsedXCoord = parseFloat((reprojectedValue?.x));
+ const parsedYCoord = parseFloat((reprojectedValue?.y));
+ onChangeCoord('xCoord', parsedXCoord);
+ onChangeCoord('yCoord', parsedYCoord);
+ // if coords are out of crs extent --> clear the marker
+ if (!isCoordWithinCrs(parsedXCoord, 'xCoord') || !isCoordWithinCrs(parsedYCoord, 'yCoord')) onClearCoordinatesSearch({owner: "search"});
+ return;
+ }
+ coordinate.xCoord && onChangeCoord('xCoord', '');
+ coordinate.yCoord && onChangeCoord('yCoord', '');
+ }, [currentMapCRS]);
+
+ const changeCoordinates = (coord, value) => {
+ // clear coordinate marker
+ if (!areValidCoordinates()) {
+ onClearCoordinatesSearch({owner: "search"});
+ }
+ // set change value
+ const numValue = parseFloat(value);
+ onChangeCoord(coord, numValue);
+ // reproject the new point and set lat/lon
+ if (coord === 'yCoord') {
+ const yCoordValidNum = isNumber(numValue) && !isNaN(numValue) && isCoordWithinCrs(numValue, 'yCoord');
+ const xCoordValidNum = isNumber(coordinate.xCoord) && !isNaN(coordinate.xCoord) && isCoordWithinCrs(coordinate.xCoord, 'xCoord');
+ if (yCoordValidNum && xCoordValidNum) {
+ const projectedPt = reproject([coordinate.xCoord, numValue], currentMapCRS, 'EPSG:4326', true);
+ onChangeCoord('lat', (projectedPt.y));
+ onChangeCoord('lon', (projectedPt.x));
+ return;
+ }
+ } else {
+ const xCoordValidNum = isNumber(numValue) && !isNaN(numValue) && isCoordWithinCrs(numValue, 'xCoord');
+ const yCoordValidNum = isNumber(coordinate.yCoord) && !isNaN(coordinate.yCoord) && isCoordWithinCrs(coordinate.yCoord, 'yCoord');
+ if (yCoordValidNum && xCoordValidNum) {
+ const projectedPt = reproject([numValue, coordinate.yCoord], currentMapCRS, 'EPSG:4326', true);
+ onChangeCoord('lat', (projectedPt.y));
+ onChangeCoord('lon', (projectedPt.x));
+ return;
+ }
+ }
+ const resetValue = '';
+ onChangeCoord('lat', resetValue);
+ onChangeCoord('lon', resetValue);
+
+ };
+
+ const onZoom = () => {
+ zoomToPoint(onZoomToPoint, coordinate, defaultZoomLevel);
+ };
+
+ return (
+
+
+
+
+
+ changeCoordinates("xCoord", dd)}
+ onKeyDown={() => {
+ if (areValidCoordinates(coordinate)) {
+ onZoom();
+ }
+ }}
+ />
+
+
+
+
+
+
+
+ changeCoordinates("yCoord", dd)}
+ onKeyDown={() => {
+ if (areValidCoordinates(coordinate)) {
+ onZoom();
+ }
+ }}
+ />
+
+
+
+
+ );
+};
+
+CurrentMapCRSCoordinatesSearch.propTypes = {
+ coordinate: PropTypes.object,
+ format: PropTypes.string,
+ onClearCoordinatesSearch: PropTypes.func,
+ onZoomToPoint: PropTypes.func,
+ onChangeCoord: PropTypes.func,
+ defaultZoomLevel: PropTypes.number
+};
+
+export default connect((state)=>{
+ return {
+ coordinate: state.search.coordinate || {}
+ };
+}, {
+ onZoomToPoint: zoomAndAddPoint,
+ onChangeCoord: changeCoord
+})(CurrentMapCRSCoordinatesSearch);
diff --git a/web/client/components/misc/coordinateeditors/CoordinateEntry.jsx b/web/client/components/misc/coordinateeditors/CoordinateEntry.jsx
index 746a126d61..02510e890c 100644
--- a/web/client/components/misc/coordinateeditors/CoordinateEntry.jsx
+++ b/web/client/components/misc/coordinateeditors/CoordinateEntry.jsx
@@ -10,6 +10,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import DecimalCoordinateEditor from './editors/DecimalCoordinateEditor';
+import CRSCoordinateEditor from './editors/CRSCoordinateEditor';
import AeronauticalCoordinateEditor from './editors/AeronauticalCoordinateEditor';
import { isNil } from 'lodash';
import no90Lat from './enhancers/no90Lat';
@@ -26,7 +27,8 @@ class CoordinateEntry extends React.Component {
aeronauticalOptions: PropTypes.object,
coordinate: PropTypes.string,
onChange: PropTypes.func,
- onKeyDown: PropTypes.func
+ onKeyDown: PropTypes.func,
+ owner: PropTypes.string
};
static defaultProps = {
@@ -42,13 +44,14 @@ class CoordinateEntry extends React.Component {
max: 180
}
}
- }
+ },
+ owner: ''
}
render() {
- const {format} = this.props;
+ const {format, owner, currentMapCRS} = this.props;
return format === "decimal" || isNil(format) ?
- :
+ owner === 'search' && currentMapCRS ? : :
;
}
}
diff --git a/web/client/components/misc/coordinateeditors/editors/CRSCoordinateEditor.jsx b/web/client/components/misc/coordinateeditors/editors/CRSCoordinateEditor.jsx
new file mode 100644
index 0000000000..9206ed753a
--- /dev/null
+++ b/web/client/components/misc/coordinateeditors/editors/CRSCoordinateEditor.jsx
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+*/
+
+import {capitalize} from 'lodash';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {FormGroup} from 'react-bootstrap';
+import IntlNumberFormControl from '../../../I18N/IntlNumberFormControl';
+
+/**
+ This component renders a custom coordiante inpout for decimal degrees for default coordinate CRS and current map CRS as well
+*/
+class CRSCoordinateEditor extends React.Component {
+
+ static propTypes = {
+ idx: PropTypes.number,
+ value: PropTypes.number,
+ constraints: PropTypes.object,
+ format: PropTypes.string,
+ coordinate: PropTypes.string,
+ onChange: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onSubmit: PropTypes.func,
+ disabled: PropTypes.bool,
+ currentMapCRS: PropTypes.string
+ };
+ static contextTypes = {
+ intl: PropTypes.object
+ };
+ static defaultProps = {
+ format: "decimal",
+ coordinate: "lat",
+ constraints: {
+ decimal: {
+ lat: {
+ min: -90,
+ max: 90
+ },
+ lon: {
+ min: -180,
+ max: 180
+ }
+ }
+ },
+ onKeyDown: () => {},
+ disabled: false,
+ currentMapCRS: 'EPSG:4326'
+ }
+ render() {
+ const {coordinate, onChange, disabled, value} = this.props;
+ const validateNameFunc = "validateDecimal" + capitalize(coordinate);
+ return (
+
+ {
+ if (val === "") {
+ onChange("");
+ } else {
+ onChange(val);
+ }
+ }}
+ onKeyDown={this.verifyOnKeyDownEvent}
+ step={1}
+ validateNameFunc={this[validateNameFunc]}
+ type="number"
+ />
+
+ );
+ }
+ /**
+ * checking and blocking the keydown event to avoid
+ * the only letters matched by input type number 'e' or 'E'
+ * see https://github.com/geosolutions-it/MapStore2/issues/3523#issuecomment-502660391
+ * @param event keydown event
+ */
+ verifyOnKeyDownEvent = (event) => {
+ if (event.keyCode === 69) {
+ event.preventDefault();
+ }
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.props.onKeyDown(event);
+ }
+ };
+ validateDecimalX = (xCoordinate) => {
+ const min = this.props.constraints[this.props.format].xCoord.min;
+ const max = this.props.constraints[this.props.format].xCoord.max;
+ const xCoord = parseFloat(xCoordinate);
+ if (isNaN(xCoord) || xCoord < min || xCoord > max ) {
+ return "error";
+ }
+ return null; // "success"
+ };
+ validateDecimalY = (yCoordinate) => {
+ const min = this.props.constraints[this.props.format].yCoord.min;
+ const max = this.props.constraints[this.props.format].yCoord.max;
+ const yCoord = parseFloat(yCoordinate);
+ if (isNaN(yCoord) || yCoord < min || yCoord > max ) {
+ return "error";
+ }
+ return null; // "success"
+ }
+
+}
+
+export default CRSCoordinateEditor;
diff --git a/web/client/plugins/Search.jsx b/web/client/plugins/Search.jsx
index 834cc8b0e1..1f6dfc0622 100644
--- a/web/client/plugins/Search.jsx
+++ b/web/client/plugins/Search.jsx
@@ -48,7 +48,7 @@ import {
import mapInfoReducers from '../reducers/mapInfo';
import searchReducers from '../reducers/search';
import { layersSelector } from '../selectors/layers';
-import {mapSelector, mapSizeValuesSelector} from '../selectors/map';
+import {mapSelector, mapSizeValuesSelector, projectionSelector} from '../selectors/map';
import ConfigUtils from '../utils/ConfigUtils';
import { defaultIconStyle } from '../utils/SearchUtils';
import ToggleButton from './searchbar/ToggleButton';
@@ -60,8 +60,9 @@ const searchSelector = createSelector([
state => state.search || null,
state => state.controls && state.controls.searchBookmarkConfig || null,
state => state.mapConfigRawData || {},
- state => state?.searchbookmarkconfig || ''
-], (searchState, searchBookmarkConfigControl, mapInitial, bookmarkConfig) => ({
+ state => state?.searchbookmarkconfig || '',
+ projectionSelector
+], (searchState, searchBookmarkConfigControl, mapInitial, bookmarkConfig, currentMapCRS) => ({
enabledSearchBookmarkConfig: searchBookmarkConfigControl && searchBookmarkConfigControl.enabled || false,
error: searchState && searchState.error,
coordinate: searchState && searchState.coordinate || {},
@@ -71,7 +72,8 @@ const searchSelector = createSelector([
format: get(searchState, "format") || ConfigUtils.getConfigProp("defaultCoordinateFormat"),
selectedItems: searchState && searchState.selectedItems,
mapInitial,
- bookmarkConfig: bookmarkConfig || {}
+ bookmarkConfig: bookmarkConfig || {},
+ currentMapCRS
}));
const SearchBar = connect(searchSelector, {
diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json
index ab5a49c758..0e72bfdf38 100644
--- a/web/client/translations/data.de-DE.json
+++ b/web/client/translations/data.de-DE.json
@@ -903,6 +903,7 @@
"addressSearch": "Suche nach Standortname",
"searchOnAllServices": "Suchen Sie unten nach allen Diensten",
"coordinatesSearch": "Suche nach Koordinaten",
+ "currentMapCRS": "Aktuelles Kartenkoordinatensystem",
"searchservicesbutton": "Suchdienste konfigurieren",
"configpaneltitle": "Einen Suchdienst anlegen / bearbeiten",
"serviceslistlabel": "Verfügbare Suchdienste",
@@ -924,6 +925,8 @@
"b_newpaneltitle": "Neues Lesezeichen hinzufügen",
"latitude": "Lat",
"longitude": "Lon",
+ "yCoord": "Y",
+ "xCoord": "X",
"b_title": "Titel",
"b_layer_tooltip": "Deaktivieren Sie das Neuladen der Ebenensichtbarkeit",
"b_bbox": "Begrenzungsrahmen",
diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json
index 8bb7a0907c..58c7af2b42 100644
--- a/web/client/translations/data.en-US.json
+++ b/web/client/translations/data.en-US.json
@@ -864,6 +864,7 @@
"addressSearch": "Search by location name",
"searchOnAllServices": "Search on all services below",
"coordinatesSearch": "Search by coordinates",
+ "currentMapCRS": "Current Map CRS",
"searchservicesbutton": "Configure search services",
"configpaneltitle": "Create/edit a search service",
"serviceslistlabel": "Available services",
@@ -885,6 +886,8 @@
"b_newpaneltitle": "Add new bookmark",
"latitude": "Lat",
"longitude": "Lon",
+ "yCoord": "Y",
+ "xCoord": "X",
"b_title": "Title",
"b_layer_tooltip": "Toggle layer visibility reload",
"b_bbox": "Bounding Box",
diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json
index cf2df613ff..e67a269938 100644
--- a/web/client/translations/data.es-ES.json
+++ b/web/client/translations/data.es-ES.json
@@ -864,6 +864,7 @@
"addressSearch": "Buscar por nombre de ubicación",
"searchOnAllServices": "Busque en todos los servicios a continuación",
"coordinatesSearch": "buscar por coordenadas",
+ "currentMapCRS": "Sistema de coordenadas del mapa actual",
"searchservicesbutton": "Configurar los servicios de búsqueda",
"configpaneltitle": "Crear / modificar un servicio de búsqueda",
"serviceslistlabel": "Servicios disponibles",
@@ -885,6 +886,8 @@
"b_newpaneltitle": "Agregar nuevo marcador",
"latitude": "Lat",
"longitude": "Lon",
+ "yCoord": "Y",
+ "xCoord": "X",
"b_title": "Título",
"b_layer_tooltip": "Alternar recarga de visibilidad de capa",
"b_bbox": "Cuadro delimitador",
diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json
index 32e1607fc5..94ea4bf37b 100644
--- a/web/client/translations/data.fr-FR.json
+++ b/web/client/translations/data.fr-FR.json
@@ -864,6 +864,7 @@
"addressSearch": "Recherche par nom de lieu",
"searchOnAllServices": "Recherchez tous les services ci-dessous",
"coordinatesSearch": "Rechercher par coordonnées",
+ "currentMapCRS": "Système de coordonnées cartographiques actuel",
"searchservicesbutton": "Configurer les services de recherche",
"configpaneltitle": "Créer / modifier un service de recherche",
"serviceslistlabel": "Services disponibles",
@@ -885,6 +886,8 @@
"b_newpaneltitle": "Ajouter un nouveau signet",
"latitude": "Lat",
"longitude": "Lon",
+ "yCoord": "Y",
+ "xCoord": "X",
"b_title": "Titre",
"b_layer_tooltip": "Basculer le rechargement de la visibilité des calques",
"b_bbox": "Boîte englobante",
diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json
index e2c5069c69..9771c33d74 100644
--- a/web/client/translations/data.it-IT.json
+++ b/web/client/translations/data.it-IT.json
@@ -864,6 +864,7 @@
"addressSearch": "Cerca un indirizzo",
"searchOnAllServices": "Cerca su tutti i servizi di seguito",
"coordinatesSearch": "Cerca per coordinate",
+ "currentMapCRS": "Sistema di coordinate della mappa corrente",
"searchservicesbutton": "Configura servizi di ricerca",
"configpaneltitle": "Aggiungi/modifica un servizio di ricerca",
"serviceslistlabel": "Servizi disponibili",
@@ -885,6 +886,8 @@
"b_newpaneltitle": "Aggiungi nuovo segnalibro",
"latitude": "Lat",
"longitude": "Lon",
+ "yCoord": "Y",
+ "xCoord": "X",
"b_title": "Titolo",
"b_layer_tooltip": "Interruttore ricarica visibilità livello",
"b_bbox": "Rettangolo di selezione",