From 7adaf685167d91744ad92f7badd46c187a078ced Mon Sep 17 00:00:00 2001 From: mahmoudadel54 Date: Tue, 23 Jan 2024 11:00:48 +0200 Subject: [PATCH] #9830: Support for IFC as a further 3D model Description: - Creating ModelTransformation component that handle moving the model center via display TOC settings for 'model' layers - Add translations - Handle show/hide the model layers via TOC - Handle logic on show/hide 'model' layers based on max/min scale in display TOC settings --- web/client/api/Model.js | 54 ++++----- .../TOC/fragments/settings/Display.jsx | 5 + .../settings/ModelTransformation.jsx | 108 ++++++++++++++++++ web/client/components/map/cesium/Map.jsx | 3 +- .../map/cesium/plugins/ModelLayer.js | 94 +++++++-------- web/client/translations/data.de-DE.json | 2 + web/client/translations/data.en-US.json | 2 + web/client/translations/data.es-ES.json | 2 + web/client/translations/data.fr-FR.json | 2 + web/client/translations/data.is-IS.json | 2 + web/client/translations/data.it-IT.json | 2 + 11 files changed, 196 insertions(+), 80 deletions(-) create mode 100644 web/client/components/TOC/fragments/settings/ModelTransformation.jsx diff --git a/web/client/api/Model.js b/web/client/api/Model.js index b603417efe..75115c4151 100644 --- a/web/client/api/Model.js +++ b/web/client/api/Model.js @@ -5,8 +5,6 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -import * as Cesium from 'cesium'; - // extract the tile format from the uri function getFormat(uri) { const parts = uri.split(/\./g); @@ -27,18 +25,11 @@ function extractCapabilities(ifcApi, modelID, url) { }; } -const applyMatrix = (matrix, coords) => { - const result = Cesium.Matrix4.multiplyByPoint( - Cesium.Matrix4.fromArray(matrix), - new Cesium.Cartesian3(...coords), - new Cesium.Cartesian3() - ); - - return [result.x, result.y, result.z]; -}; - export const ifcDataToJSON = ({ data, ifcApi }) => { - const settings = {}; + const settings = { + COORDINATE_TO_ORIGIN: true, + USE_FAST_BOOLS: true + }; let rawFileData = new Uint8Array(data); const modelID = ifcApi.OpenModel(rawFileData, settings); // eslint-disable-line ifcApi.LoadAllGeometry(modelID); // eslint-disable-line @@ -61,14 +52,9 @@ export const ifcDataToJSON = ({ data, ifcApi }) => { const positions = new Float64Array(ifcVertices.length / 2); const normals = new Float32Array(ifcVertices.length / 2); for (let j = 0; j < ifcVertices.length; j += 6) { - const [x, y, z] = applyMatrix( - coordinationMatrix, - applyMatrix(placedGeometry.flatTransformation, [ - ifcVertices[j], - ifcVertices[j + 1], - ifcVertices[j + 2] - ], Cesium), Cesium - ); + const x = ifcVertices[j]; // index = 0 + const y = ifcVertices[j + 1]; // index = 1 + const z = ifcVertices[j + 2]; // index = 2 if (x < minx) { minx = x; } if (y < miny) { miny = y; } if (z < minz) { minz = z; } @@ -78,15 +64,16 @@ export const ifcDataToJSON = ({ data, ifcApi }) => { positions[j / 2] = x; positions[j / 2 + 1] = y; positions[j / 2 + 2] = z; - normals[j / 2] = ifcVertices[j + 3]; - normals[j / 2 + 1] = ifcVertices[j + 4]; - normals[j / 2 + 2] = ifcVertices[j + 5]; + normals[j / 2] = ifcVertices[j + 3]; // index = 3 + normals[j / 2 + 1] = ifcVertices[j + 4]; // index = 4 + normals[j / 2 + 2] = ifcVertices[j + 5]; // index = 5 } geometry.push({ color: placedGeometry.color, positions, normals, - indices: Array.from(ifcIndices) + indices: Array.from(ifcIndices), + flatTransformation: placedGeometry.flatTransformation }); ifcGeometry.delete(); } @@ -143,15 +130,16 @@ export const getCapabilities = (url) => { // todo: read IFCProjectedCRS, IFCMapCONVERSION in case of IFC4 let bbox = { bounds: capabilities.version !== "IFC4" ? { - minx: 0, - miny: 0, - maxx: 0, - maxy: 0 + minx: 0 - 2, + miny: 0 - 2, + maxx: 0 + 2, + maxy: 0 + 2 } : { - minx: 0, - miny: 0, - maxx: 0, - maxy: 0}, + minx: 0 - 2, + miny: 0 - 2, + maxx: 0 + 2, + maxy: 0 + 2 + }, crs: 'EPSG:4326' }; return { modelData: data, ...capabilities, ...(bbox && { bbox })}; diff --git a/web/client/components/TOC/fragments/settings/Display.jsx b/web/client/components/TOC/fragments/settings/Display.jsx index acea7b9a83..34d823d546 100644 --- a/web/client/components/TOC/fragments/settings/Display.jsx +++ b/web/client/components/TOC/fragments/settings/Display.jsx @@ -22,6 +22,7 @@ import Select from 'react-select'; import { getSupportedFormat } from '../../../../api/WMS'; import WMSCacheOptions from './WMSCacheOptions'; import ThreeDTilesSettings from './ThreeDTilesSettings'; +import ModelTransformation from './ModelTransformation'; export default class extends React.Component { static propTypes = { opacityText: PropTypes.node, @@ -211,6 +212,10 @@ export default class extends React.Component { layer={this.props.element} onChange={this.props.onChange} /> + {this.props.element.type === "wms" && diff --git a/web/client/components/TOC/fragments/settings/ModelTransformation.jsx b/web/client/components/TOC/fragments/settings/ModelTransformation.jsx new file mode 100644 index 0000000000..0699527a7e --- /dev/null +++ b/web/client/components/TOC/fragments/settings/ModelTransformation.jsx @@ -0,0 +1,108 @@ + +/* + * Copyright 2023, 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 from 'react'; +import PropTypes from 'prop-types'; +import { FormGroup, ControlLabel, InputGroup } from 'react-bootstrap'; +import DebouncedFormControl from '../../../misc/DebouncedFormControl'; +import Message from '../../../I18N/Message'; + +/** + * ModelTransformation. This component shows the model transformation options available + * @prop {object} layer the layer options + * @prop {object} onChange callback on every on change event + */ +function ModelTransformation({ + layer, + onChange +}) { + if (layer?.type !== 'model') { + return null; + } + return ( +
+ + + + { + const newCenter = [ + layer?.center?.[0] ?? 0, + val !== undefined + ? parseFloat(val) : 0, + layer?.center?.[2] ?? 0 + ]; + onChange('center', newCenter); + }} + /> + DD + + + + + + { + const newCenter = [ + val !== undefined + ? parseFloat(val) : 0, + layer?.center?.[1] ?? 0, + layer?.center?.[2] ?? 0 + ]; + onChange('center', newCenter); + }} + /> + DD + + + + + + { + const newCenter = [ + layer?.center?.[0] ?? 0, + layer?.center?.[1] ?? 0, + val !== undefined + ? parseFloat(val) : 0 + ]; + onChange('center', newCenter); + }} + /> + m + + +
+ ); +} + +ModelTransformation.propTypes = { + layer: PropTypes.object, + onChange: PropTypes.func +}; + +ModelTransformation.defaultProps = { + layer: {}, + onChange: () => {} +}; + +export default ModelTransformation; diff --git a/web/client/components/map/cesium/Map.jsx b/web/client/components/map/cesium/Map.jsx index 9caa4d90bd..4ac6f689dd 100644 --- a/web/client/components/map/cesium/Map.jsx +++ b/web/client/components/map/cesium/Map.jsx @@ -117,7 +117,8 @@ class CesiumMap extends React.Component { : undefined, requestRenderMode: true, maximumRenderTimeChange: Infinity, - skyBox: false + skyBox: false, + scene3DOnly: true }, this.getMapOptions(this.props.mapOptions))); // prevent default behavior diff --git a/web/client/components/map/cesium/plugins/ModelLayer.js b/web/client/components/map/cesium/plugins/ModelLayer.js index 9c900fc33d..27b26746df 100644 --- a/web/client/components/map/cesium/plugins/ModelLayer.js +++ b/web/client/components/map/cesium/plugins/ModelLayer.js @@ -7,38 +7,40 @@ */ import * as Cesium from 'cesium'; +import isEqual from 'lodash/isEqual'; import Layers from '../../../../utils/cesium/Layers'; import { ifcDataToJSON, getWebIFC } from '../../../../api/Model'; // todo: change path to MODEL - -const transform = (positions, coords, matrix) => { - let transformed = []; - for (let i = 0; i < positions.length; i += 3) { - const cartesian = Cesium.Matrix4.multiplyByPoint(matrix, new Cesium.Cartesian3( - positions[i] + coords[0], - positions[i + 1] + coords[1], - positions[i + 2] + coords[2] - ), new Cesium.Cartesian3()); - transformed.push( - cartesian.x, - cartesian.y, - cartesian.z +const updatePrimitivesPosition = (primitives, center) => { + for (let i = 0; i < primitives.length; i++) { + const primitive = primitives.get(i); + primitive.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame( + // review the center properties + // based on other existing layer parameters + Cesium.Cartesian3.fromDegrees(...(center ? [ + center[0], + center[1], + center[2] + ] : [0, 0, 0])) ); } - return transformed; }; - +const updatePrimitivesVisibility = (primitives, visibilityOption) => { + for (let i = 0; i < primitives.length; i++) { + const primitive = primitives.get(i); + primitive.show = visibilityOption; + } +}; const getGeometryInstances = ({ - meshes, - center, - options + meshes }) => { return meshes .map((mesh) => mesh.geometry.map(({ color, positions, normals, - indices + indices, + flatTransformation }) => { const rotationMatrix = Cesium.Matrix4.fromTranslationQuaternionRotationScale( new Cesium.Cartesian3(0.0, 0.0, 0.0), // 0,0 @@ -49,30 +51,15 @@ const getGeometryInstances = ({ new Cesium.Cartesian3(1.0, 1.0, 1.0), new Cesium.Matrix4() ); - const transformedPositions = transform( - positions, - [-center[0], -center[1], -center[2]], - Cesium.Matrix4.multiply( - Cesium.Transforms.eastNorthUpToFixedFrame( - // review the center properties - // based on other existing layer parameters - Cesium.Cartesian3.fromDegrees(...(options.center ? [ - options.center[0], - options.center[1], - options.center[2] - ] : [0, 0, 0])) - ), - rotationMatrix, - new Cesium.Matrix4() - ) - ); - const transformedNormals = transform( - normals, - [0, 0, 0], - rotationMatrix - ); + const transformedPositions = positions; + const transformedNormals = normals; return new Cesium.GeometryInstance({ id: mesh.id, + modelMatrix: Cesium.Matrix4.multiply( + rotationMatrix, + flatTransformation, + new Cesium.Matrix4() + ), geometry: new Cesium.Geometry({ attributes: { position: new Cesium.GeometryAttribute({ @@ -91,8 +78,6 @@ const getGeometryInstances = ({ primitiveType: Cesium.PrimitiveType.TRIANGLES, boundingSphere: Cesium.BoundingSphere.fromVertices(transformedPositions) }), - - // modelMatrix: , attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color( color.x, @@ -157,6 +142,8 @@ const createLayer = (options, map) => { opaquePrimitive.msId = options.id; opaquePrimitive.id = 'opaquePrimitive'; primitives.add(opaquePrimitive); + updatePrimitivesPosition(primitives, options.center); + }); }); map.scene.primitives.add(primitives); @@ -171,17 +158,32 @@ const createLayer = (options, map) => { } }, setVisible: ( - // newVisiblity + newVisibility ) => { // todo: add the logic of setting visibility + if (primitives && map) { + updatePrimitivesVisibility(primitives, newVisibility); + } } }; }; Layers.registerType('model', { create: createLayer, - update: (/* layer, newOptions, oldOptions, map */) => { - // todo: here we can put change opacity logic + update: (layer, newOptions, oldOptions) => { + if (layer?.primitives && !isEqual(newOptions?.center, oldOptions?.center)) { + // update layer.bbox + layer.bbox = { + ...layer.bbox, + bounds: { + minx: newOptions?.center?.[0] || 0 - 2, + miny: newOptions?.center?.[1] || 0 - 2, + maxx: newOptions?.center?.[0] || 0 + 2, + maxy: newOptions?.center?.[1] || 0 + 2 + } + }; + updatePrimitivesPosition(layer?.primitives, newOptions?.center); + } return null; } }); diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index d30fcef15f..7f6e4e7dfe 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -115,6 +115,8 @@ "templateError": "Beim Anwenden der Vorlage auf die vom Server zurückgegebenen Informationen ist ein Fehler aufgetreten. Bitte überprüfen Sie die Vorlage", "templatePreview": "Vorschau template", "heightOffset": "Höhenversatz (m)", + "modelCenterLat": "Breitengrad des Zentrums (DD)", + "modelCenterLng": "Längengrad des Zentrums (DD)", "wmsLayerTileSize": "Kachelgröße (WMS)", "serverType": "Servertyp", "formatError": "Es war nicht möglich, Format und Informationsblattformat vom konfigurierten Dienst abzurufen. Wahrscheinlich verwenden Sie einen No-Vendor-Dienst und dieser wird nicht unterstützt (z. B. GeoNetwork).", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index 82262591d2..2aee733d4a 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -115,6 +115,8 @@ "templateError": "There was an error applying the template to the information returned by the server, please check the template", "templatePreview": "Template preview", "heightOffset": "Height offset (m)", + "modelCenterLat": "Center's Latitude (DD)", + "modelCenterLng": "Center's Longitude (DD)", "wmsLayerTileSize": "Tile size (WMS)", "serverType": "Server Type", "formatError": "It was not possible to fetch format and information sheet format from the service configured. Probably you are using a No Vendor service and this is not supported (e.g. GeoNetwork)", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index f90e69a3be..0edc20e6c3 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -112,6 +112,8 @@ "templateFormatInfoAlert1": "Haga clic en el botón Editar para agregar una nueva plantilla.", "templateFormatInfoAlert2": "Use ${ attribute } para ajustar las propiedades que necesita visualizar", "heightOffset": "Desplazamiento de altura (m)", + "modelCenterLat": "Latitud del centro (DD)", + "modelCenterLng": "Longitud del centro (DD)", "wmsLayerTileSize": "Tamaño del mosaico (WMS)", "serverType": "Tipo de servidor", "formatError": "No fue posible recuperar el formato y el formato de la hoja de información del servicio configurado. Probablemente esté utilizando un servicio sin proveedor y no es compatible (por ejemplo, GeoNetwork)", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 7538df75a9..e00bf2ce11 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -115,6 +115,8 @@ "templateError": "Une erreur s'est produite lors de l'application du modèle aux informations renvoyées par le serveur, veuillez vérifier le modèle", "templatePreview": "Aperçu du modèle", "heightOffset": "Décalage en hauteur (m)", + "modelCenterLat": "Latitude du centre (DD)", + "modelCenterLng": "Longitude du centre(DD)", "wmsLayerTileSize": "Taille de la tuile (WMS)", "serverType": "Type de serveur", "formatError": "Il n'a pas été possible de récupérer le format et le format de la fiche d'information à partir du service configuré. Vous utilisez probablement un service No Vendor et celui-ci n'est pas pris en charge (par exemple GeoNetwork)", diff --git a/web/client/translations/data.is-IS.json b/web/client/translations/data.is-IS.json index 67a233c29b..c7cfe107d6 100644 --- a/web/client/translations/data.is-IS.json +++ b/web/client/translations/data.is-IS.json @@ -112,6 +112,8 @@ "templateError": "There was an error applying the template to the information returned by the server, please check the template", "templatePreview": "Template preview", "heightOffset": "Height offset (m)", + "modelCenterLat": "Center's Latitude (DD)", + "modelCenterLng": "Center's Longitude (DD)", "wmsLayerTileSize": "Tile size (WMS)", "serverType": "Server Type", "serverTypeOption": { diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index 9ab1ada5aa..3f7e6d4124 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -115,6 +115,8 @@ "templateError": "Si è verificato un errore durante l'applicazione del template alle informazioni restituite dal server, controllare il template.", "templatePreview": "Anteprima template", "heightOffset": "Spostamento in altezza (m)", + "modelCenterLat": "Latitudine del centro (DD)", + "modelCenterLng": "Longitudine del centro (DD)", "wmsLayerTileSize": "Dimensione tile (WMS)", "serverType": "Tipo di Server", "formatError": "Non è stato possibile recuperare le informazioni sui formati dal servizio configurato. Probabilmente stai usando un servizio \"No Vendor\" e questo non è supportato (es. GeoNetwork)",