diff --git a/build/docma-config.json b/build/docma-config.json index 8ecb34a90f..8af64ec8a7 100644 --- a/build/docma-config.json +++ b/build/docma-config.json @@ -235,7 +235,11 @@ "web/client/plugins/BackgroundSelector.jsx", "web/client/plugins/BurgerMenu.jsx", "web/client/plugins/CRSSelector.jsx", +<<<<<<< HEAD "web/client/plugins/CameraPosition/CameraPosition.jsx", +======= + "web/client/plugins/CameraPosition.jsx", +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) "web/client/plugins/Context.jsx", "web/client/plugins/ContextCreator.jsx", "web/client/plugins/ContextExport.jsx", diff --git a/package.json b/package.json index ea9a7fd268..360f8fa721 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,11 @@ "@mapbox/geojsonhint": "3.3.0", "@mapbox/togeojson": "0.16.2", "@mapstore/patcher": "https://github.com/geosolutions-it/Patcher/tarball/master", +<<<<<<< HEAD "@math.gl/geoid": "4.1.0", +======= + "@math.gl/geoid": "^4.1.0", +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) "@turf/along": "6.5.0", "@turf/area": "6.5.0", "@turf/bbox": "4.1.0", diff --git a/web/client/components/mapcontrols/mouseposition/CRSSelector.jsx b/web/client/components/mapcontrols/mouseposition/CRSSelector.jsx index d24ce5a723..8bd669d744 100644 --- a/web/client/components/mapcontrols/mouseposition/CRSSelector.jsx +++ b/web/client/components/mapcontrols/mouseposition/CRSSelector.jsx @@ -88,7 +88,11 @@ const CRSSelector = (props) => { centerChildrenVertically gap="sm" > +<<<<<<< HEAD +======= + +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) {label} { value={crs} onChange={launchNewCRSAction} bsSize="small" +<<<<<<< HEAD className="selector-control" +======= + style={{ borderRadius: 4 }} +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) > {options} diff --git a/web/client/components/mapcontrols/mouseposition/HeightTypeSelector.jsx b/web/client/components/mapcontrols/mouseposition/HeightTypeSelector.jsx index 2e0a01a449..edde5b04de 100644 --- a/web/client/components/mapcontrols/mouseposition/HeightTypeSelector.jsx +++ b/web/client/components/mapcontrols/mouseposition/HeightTypeSelector.jsx @@ -46,7 +46,11 @@ const HeightTypeSelector = (props, context) => { return ( +<<<<<<< HEAD +======= + +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) {label} { value={heightType} onChange={(e) => onHeightTypeChange(e.target.value)} bsSize="small" +<<<<<<< HEAD className="selector-control" +======= + style={{ borderRadius: 4 }} +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) > {options} diff --git a/web/client/components/mapcontrols/mouseposition/mousePosition.css b/web/client/components/mapcontrols/mouseposition/mousePosition.css index 7408343ba0..bf7d50d28d 100644 --- a/web/client/components/mapcontrols/mouseposition/mousePosition.css +++ b/web/client/components/mapcontrols/mouseposition/mousePosition.css @@ -108,6 +108,7 @@ width: 90px !important; font-size: 11px; padding: 2px; +<<<<<<< HEAD } .selector-label{ @@ -117,4 +118,6 @@ } .selector-control{ border-radius: 4px; +======= +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) } \ No newline at end of file diff --git a/web/client/configs/localConfig.json b/web/client/configs/localConfig.json index 0cbac63e5f..ccb43f1297 100644 --- a/web/client/configs/localConfig.json +++ b/web/client/configs/localConfig.json @@ -587,6 +587,9 @@ "additionalCRS": {} } }, + { + "name": "CameraPosition" + }, { "name": "CRSSelector", "cfg": { diff --git a/web/client/plugins/CameraPosition.jsx b/web/client/plugins/CameraPosition.jsx new file mode 100644 index 0000000000..4be8c9faff --- /dev/null +++ b/web/client/plugins/CameraPosition.jsx @@ -0,0 +1,284 @@ +/* + * Copyright 2025, 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 React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { Tooltip } from 'react-bootstrap'; +import PropTypes from 'prop-types'; +import axios from 'axios'; + +import { createPlugin } from '../utils/PluginsUtils'; +import ToggleButton from '../components/buttons/ToggleButton'; +import Message from '../components/I18N/Message'; +import MousePositionComponent from '../components/mapcontrols/mouseposition/MousePosition'; +import { getTemplate } from '../components/mapcontrols/mouseposition/templates'; +import { mapSelector, projectionDefsSelector } from '../selectors/map'; +import { getCameraPositionCrs, getCameraPositionHeightType, getShowCameraPosition } from './CameraPosition/selectors/cameraPosition'; +import { showCameraPosition, hideCameraPosition, changeCameraPositionCrs, changeCameraPositionHeightType } from './CameraPosition/actions/cameraPosition'; +import cameraPosition from './CameraPosition/reducers/cameraPosition'; +import './CameraPosition/cameraPosition.css'; + +const selector = createSelector([ + (state) => state, + mapSelector, + (state) => getShowCameraPosition(state), + (state) => getCameraPositionCrs(state), + (state) => getCameraPositionHeightType(state) +], (state, map, showCameraPositionEnabled, crs, heightType) => ({ + showCameraPosition: showCameraPositionEnabled, + cameraPosition: map?.viewerOptions?.cameraPosition, + projectionDefs: projectionDefsSelector(state), + crs: crs, + heightType: heightType +})); + +const CameraPositionButton = connect((state) => ({ + pressed: getShowCameraPosition(state), + active: getShowCameraPosition(state), + tooltip: , + tooltipPlace: 'left', + pressedStyle: "success active", + defaultStyle: "primary", + glyphicon: "camera", + btnConfig: { className: 'square-button-md' }, + style: { + height: '25px', + width: '25px' + } +}), { showCameraPosition, hideCameraPosition }, (stateProps, dispatchProps) => { + return { ...stateProps, onClick: () => { + if (stateProps.pressed) { + dispatchProps.hideCameraPosition(); + } else { + dispatchProps.showCameraPosition(); + } + } }; +})(ToggleButton); + +let geoidCache = {}; + +const getGeoidByUrl = (url) => { + return import('@math.gl/geoid') + .then(({ parsePGM }) => { + if (geoidCache[url]) { + return Promise.resolve(geoidCache[url]); + } + return axios.get(url, { + responseType: 'arraybuffer' + }) + .then(({ data }) => { + geoidCache[url] = parsePGM(new Uint8Array(data), { + cubic: false + }); + return geoidCache[url]; + }); + }); +}; + +const CameraPosition = ({ + availableHeightTypes = [ + { value: "Ellipsoidal", labelId: "plugins.CameraPosition.ellipsoidal" } + ], + editHeight = true, + showElevation = true, + additionalCRS = {}, + editCRS = true, + filterAllowedCRS = ["EPSG:4326", "EPSG:3857"], + showLabels = true, + showToggle = true, + ...props +}) => { + const { degreesTemplate = 'MousePositionLabelDMS', projectedTemplate = 'MousePositionLabelYX', ...other } = props; + const { cameraPosition: cameraPositionData = {}, showCameraPosition: showCameraPositionEnabled, heightType } = props; + const [mousePosition, setMousePosition] = useState(null); + + const geoidUrl = availableHeightTypes.find((entry) => entry.value === heightType)?.geoidUrl; + + useEffect(() => { + if (!cameraPositionData || Object.keys(cameraPositionData).length === 0) { + return; + } + + if (geoidUrl) { + getGeoidByUrl(geoidUrl) + .then(geoid => { + const heightMSL = cameraPositionData.height - geoid.getHeight(cameraPositionData.latitude, cameraPositionData.longitude); + setMousePosition({ + x: cameraPositionData.longitude, + y: cameraPositionData.latitude, + z: Number(heightMSL.toFixed(2)), + crs: "EPSG:4326" + }); + }) + .catch(() => { + setMousePosition({ + x: cameraPositionData.longitude, + y: cameraPositionData.latitude, + z: Number(cameraPositionData.height?.toFixed(2) || 0), + crs: "EPSG:4326" + }); + }); + } else { + setMousePosition({ + x: cameraPositionData.longitude, + y: cameraPositionData.latitude, + z: Number(cameraPositionData.height?.toFixed(2) || 0), + crs: "EPSG:4326" + }); + } + }, [ + cameraPositionData?.longitude, + cameraPositionData?.latitude, + cameraPositionData?.height, + geoidUrl + ]); + + return ( + } + mousePosition={mousePosition} + availableHeightTypes={availableHeightTypes} + {...other} + /> + ); +}; + +CameraPosition.propTypes = { + degreesTemplate: PropTypes.string, + projectedTemplate: PropTypes.string, + editHeight: PropTypes.bool, + showElevation: PropTypes.bool, + additionalCRS: PropTypes.object, + editCRS: PropTypes.bool, + filterAllowedCRS: PropTypes.array, + showLabels: PropTypes.bool, + showToggle: PropTypes.bool, + heightType: PropTypes.string +}; + +/** + * CameraPosition Plugin is a plugin that shows the coordinate of the camera position in a selected crs along with the height above ellipsoid or mean sea level. + * it gets displayed into the mapFooter plugin + * @name CameraPosition + * @memberof plugins + * @class + * @prop {string} cfg.editCRS if true shows a combobox to select the crs of the camera position. + * @prop {string} cfg.showLabels if true shows the labels of the coordinates. + * @prop {string} cfg.showToggle if true shows a toggle button to enable/disable the plugin. + * @prop {boolean} cfg.showElevation shows elevation in Ellipsoidal or MSL in 3D map. + * @prop {function} cfg.elevationTemplate custom template to show the elevation if showElevation is true (default template shows the elevation number with no formatting) + * @prop {object[]} projectionDefs list of additional project definitions + * @prop {string[]} cfg.filterAllowedCRS list of allowed crs in the combobox list to used as filter for the one of retrieved proj4.defs() + * @prop {string[]} cfg.allowedHeightTypes list of allowed height type in the combobox list. Accepted values are "Ellipsoidal" and "MSL" + * @prop {object} cfg.additionalCRS additional crs added to the list. The label param is used after in the combobox. + * @example + * // If you want to add some crs you need to provide a definition and adding it in the additionalCRS property + * // Put the following lines at the first level of the localconfig + * { + * "projectionDefs": [{ + * "code": "EPSG:3003", + * "def": "+proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl+towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs", + * "extent": [1241482.0019, 973563.1609, 1830078.9331, 5215189.0853], + * "worldExtent": [6.6500, 8.8000, 12.0000, 47.0500] + * }] + * } + * @example + * // And configure the mouse position plugin as below: + * { + * "cfg": { + * "additionalCRS": { + * "EPSG:3003": { "label": "EPSG:3003" } + * }, + * "filterAllowedCRS": ["EPSG:4326", "EPSG:3857"] + * } + * } + * @example + * // to show elevation and (optionally) use a custom template configure the plugin this way: + * { + * "cfg": { + * ... + * "showElevation": true, + * "elevationTemplate": "{(function(elevation) {return 'myelev: ' + (elevation || 0);})}", + * ... + * } + * } + * @example + * // to add MSL height type with geoid model configure the plugin this way: + * { + * "cfg": { + * ... + * "availableHeightTypes": [ + * { "value": "Ellipsoidal", "labelId":"plugins.CameraPosition.ellipsoidal" }, + * { "value": "MSL", "labelId": "plugins.CameraPosition.msl" , "geoidUrl": "http://localhost:port/static/egm96-15.pgm"} + * ], + * ... + * } + * } + * @example + * // to show the crs and height type labels configure the plugin this way: + * { + * "cfg": { + * ... + * "showLabels": true, + * ... + * } + * } + * @example + * // to show the toggle button configure the plugin this way: + * { + * "cfg": { + * ... + * "showToggle": true, + * ... + * } + * } + * @example + * // to show the crs selector configure the plugin this way: + * { + * "cfg": { + * ... + * "editCRS": true, + * ... + * } + * } +*/ + +export default createPlugin('CameraPosition', { + component: connect(selector, { + onCRSChange: changeCameraPositionCrs, + onHeightTypeChange: changeCameraPositionHeightType + })(CameraPosition), + containers: { + MapFooter: { + name: 'cameraPosition', + position: 3, + target: 'right-footer', + priority: 1 + } + }, + reducers: { + cameraPosition + }, + options: { + disablePluginIf: "{state('mapType') !== 'cesium'}" + } +}); diff --git a/web/client/plugins/CameraPosition/cameraPosition.css b/web/client/plugins/CameraPosition/cameraPosition.css index b42463b8d2..3752e0f236 100644 --- a/web/client/plugins/CameraPosition/cameraPosition.css +++ b/web/client/plugins/CameraPosition/cameraPosition.css @@ -7,7 +7,11 @@ border-width: 1px !important; border-style: solid !important; line-height: 30px; +<<<<<<< HEAD background-color: var(--ms-main-variant-bg,#e6e6e6); +======= + background-color: var(--ms-main-variant-color,#e6e6e6); +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) } #mapstore-heightselector-cameraposition{ border-radius: 4px; @@ -18,5 +22,9 @@ border-width: 1px !important; border-style: solid !important; line-height: 30px; +<<<<<<< HEAD background-color: var(--ms-main-variant-bg,#e6e6e6); +======= + background-color: var(--ms-main-variant-color,#e6e6e6); +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) } \ No newline at end of file diff --git a/web/client/product/plugins.js b/web/client/product/plugins.js index a3dc6cf535..52df8275c9 100644 --- a/web/client/product/plugins.js +++ b/web/client/product/plugins.js @@ -28,7 +28,10 @@ import SecurityPopup from "../plugins/SecurityPopup"; import Isochrone from "../plugins/Isochrone"; import MapFooter from '../plugins/MapFooter'; import CameraPosition from '../plugins/CameraPosition'; +<<<<<<< HEAD import CRSSelector from "../plugins/CRSSelector"; +======= +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) import {toModulePlugin} from "../utils/ModulePluginsUtils"; @@ -61,7 +64,10 @@ export const plugins = { IsochronePlugin: Isochrone, MapFooterPlugin: MapFooter, CameraPositionPlugin: CameraPosition, +<<<<<<< HEAD CRSSelectorPlugin: CRSSelector, +======= +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) // ### DYNAMIC PLUGINS ### // // product plugins diff --git a/web/client/translations/data.da-DK.json b/web/client/translations/data.da-DK.json index c949ba7654..820407ad4d 100644 --- a/web/client/translations/data.da-DK.json +++ b/web/client/translations/data.da-DK.json @@ -293,7 +293,11 @@ "mousePositionCoordinates": "Cursorens koordinater", "mouseCoordinates": "Koordinater:", "mousePositionCRS": "CRS:", +<<<<<<< HEAD "mousePositionHeight": "Højde:", +======= + "mousePositionHeight": "Height:", +>>>>>>> 1dee3e8 (Fix : #11455 Added a new Plugin called CameraPosition. (#11514)) "mousePositionElevation": "Elev.:", "mousePositionNoElevation": "N/A", "elevationLoading": "Indlæser...", @@ -3560,6 +3564,12 @@ "ellipsoidal": "Ellipsoidisk", "msl": "MSL" }, + "CameraPosition": { + "description": "Shows the current camera position (only in 3D mode)", + "title": "Camera Position", + "ellipsoidal": "Ellipsoidal", + "msl": "MSL" + }, "Print": { "description": "Tillader udskrivning af det nuværende kort med signatur", "title": "Udskriv"