From d7ced5c30bcd901d9dcbe9c2d8462f189d6047ae Mon Sep 17 00:00:00 2001 From: Victor Belomestnov Date: Thu, 27 Oct 2022 14:25:55 +0200 Subject: [PATCH] #8718 Slipping of points when depth test is active (a) (#8720) --- web/client/components/styleeditor/Fields.jsx | 9 ++ .../components/styleeditor/MultiInput.jsx | 99 +++++++++++++++ .../styleeditor/__tests__/MultiInput-test.jsx | 118 ++++++++++++++++++ .../components/styleeditor/config/blocks.js | 29 ++++- .../components/styleeditor/config/property.js | 59 +++++++++ .../themes/default/less/style-editor.less | 5 + web/client/translations/data.de-DE.json | 6 + web/client/translations/data.en-US.json | 6 + web/client/translations/data.es-ES.json | 6 + web/client/translations/data.fi-FI.json | 8 +- web/client/translations/data.fr-FR.json | 6 + web/client/translations/data.hr-HR.json | 8 +- web/client/translations/data.it-IT.json | 6 + web/client/translations/data.nl-NL.json | 10 +- web/client/translations/data.pt-PT.json | 8 +- web/client/translations/data.sk-SK.json | 8 +- web/client/translations/data.sv-SE.json | 8 +- web/client/translations/data.vi-VN.json | 8 +- web/client/translations/data.zh-ZH.json | 8 +- web/client/utils/VectorStyleUtils.js | 12 +- .../utils/styleparser/CesiumStyleParser.js | 56 ++++++++- .../__tests__/CesiumStyleParser-test.js | 6 +- 22 files changed, 466 insertions(+), 23 deletions(-) create mode 100644 web/client/components/styleeditor/MultiInput.jsx create mode 100644 web/client/components/styleeditor/__tests__/MultiInput-test.jsx diff --git a/web/client/components/styleeditor/Fields.jsx b/web/client/components/styleeditor/Fields.jsx index f12f7b149e..adfb5af0e6 100644 --- a/web/client/components/styleeditor/Fields.jsx +++ b/web/client/components/styleeditor/Fields.jsx @@ -25,6 +25,7 @@ import MarkSelector from './MarkSelector'; import Band from './Band'; import IconInput from './IconInput'; import SelectInput from './SelectInput'; +import MultiInput from './MultiInput'; const FormControl = localizedProps('placeholder')(FormControlRB); @@ -121,6 +122,14 @@ export const fields = { ); }, + multiInput: (props) => { + return ( + + + + ); + }, toolbar: ({ label, value, diff --git a/web/client/components/styleeditor/MultiInput.jsx b/web/client/components/styleeditor/MultiInput.jsx new file mode 100644 index 0000000000..531d08fd49 --- /dev/null +++ b/web/client/components/styleeditor/MultiInput.jsx @@ -0,0 +1,99 @@ +/* + * Copyright 2022, 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 Select from 'react-select'; +import { Glyphicon, MenuItem, DropdownButton, FormGroup, FormControl as FormControlRB } from 'react-bootstrap'; +import localizedProps from '../misc/enhancers/localizedProps'; +import Message from '../I18N/Message'; + +const ReactSelect = localizedProps(['placeholder', 'noResultsText'])(Select); +const FormControl = localizedProps('placeholder')(FormControlRB); + +function MultiInput({ + label, + value, + config: { + initialOptionValue, + getSelectOptions = () => [], + selectClearable = false + } = {}, + onChange, + disabled, + ...props +}) { + + const selectOptions = getSelectOptions(props); + + const dropdownButtonOptions = [ + { labelId: "styleeditor.constantValue", value: "constant" }, + { labelId: "styleeditor.attributeValue", value: "attribute" } + ]; + const dropdownButtonValue = value?.type === 'constant' ? 'constant' : 'attribute'; + + const onConstantValueChangeHandler = (event) => { + onChange({ + ...value, + value: event.target.value + }); + }; + + const onSelectValueChangeHandler = (option) => { + if (option.value === initialOptionValue) { + onChange({ type: "initial" }); + return; + } + onChange({ type: "attribute", name: option.value }); + }; + + const handleDropdownButtonSelect = (newValue) => { + if (value.type !== newValue) { + onChange({ + type: newValue + }); + } + }; + + return (
+ {value?.type === 'constant' && + + } + {value?.type !== 'constant' &&
+ +
} + }> + {dropdownButtonOptions.map((option) => ( + handleDropdownButtonSelect(option.value)}> + + + ))} + +
); +} + +export default MultiInput; diff --git a/web/client/components/styleeditor/__tests__/MultiInput-test.jsx b/web/client/components/styleeditor/__tests__/MultiInput-test.jsx new file mode 100644 index 0000000000..6dea8cc94c --- /dev/null +++ b/web/client/components/styleeditor/__tests__/MultiInput-test.jsx @@ -0,0 +1,118 @@ +/* + * Copyright 2022, 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 ReactDOM from 'react-dom'; +import MultiInput from '../MultiInput'; +import expect from 'expect'; +import { Simulate, act } from 'react-dom/test-utils'; + +describe('MultiInput component', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('should render with default', () => { + ReactDOM.render(, document.getElementById("container")); + const selectNode = document.querySelector('.Select'); + expect(selectNode).toBeTruthy(); + }); + + it('should render with constant value', () => { + ReactDOM.render(, document.getElementById("container")); + const inputNode = document.querySelector('.form-control'); + expect(inputNode.value).toBe("123.123"); + }); + + it('should render with select value', () => { + const INITIAL_OPTION_VALUE = 'original_option_value'; + const config = { + initialOptionValue: INITIAL_OPTION_VALUE, + selectOptions: [ + { label: 'Height', value: 'height' }, + { label: 'Id', value: 'id' }, + { label: 'Original value', value: INITIAL_OPTION_VALUE } + ] + }; + ReactDOM.render(, document.getElementById("container")); + const inputNode = document.querySelector('.form-control'); + expect(inputNode).toBeFalsy(); + const selectNode = document.querySelector('.Select'); + expect(selectNode).toBeTruthy(); + }); + + it('should handle onChange', () => { + const callbacks = { + onChange: () => { } + }; + const spy = expect.spyOn(callbacks, 'onChange'); + ReactDOM.render(, document.getElementById("container")); + const inputNode = document.querySelector('.form-control'); + inputNode.value = '-33'; + Simulate.change(inputNode); + expect(spy).toHaveBeenCalledWith({ type: 'constant', value: '-33' }); + }); + + it('should change height mode', () => { + const callbacks = { + onChange: () => { } + }; + const spy = expect.spyOn(callbacks, 'onChange'); + ReactDOM.render(, document.getElementById("container")); + const buttonNode = document.querySelector('button'); + Simulate.click(buttonNode); + const menuItems = document.querySelectorAll('ul.dropdown-menu li a'); + expect(menuItems.length).toBe(2); + Simulate.click(menuItems[1]); + expect(spy).toHaveBeenCalledWith({ type: 'attribute' }); + }); + + it('should render with select value', () => { + const callbacks = { + onChange: () => { } + }; + const INITIAL_OPTION_VALUE = 'original_option_value'; + const config = { + initialOptionValue: INITIAL_OPTION_VALUE, + getSelectOptions: () => [ + { label: 'Height', value: 'height' }, + { label: 'Id', value: 'id' }, + { label: 'Original value', value: INITIAL_OPTION_VALUE } + ] + }; + const spy = expect.spyOn(callbacks, 'onChange'); + ReactDOM.render( + , + document.getElementById("container") + ); + const selectNode = document.querySelector('.Select'); + const selectInputNode = selectNode.querySelector('input'); + act(() => { + Simulate.focus(selectInputNode); + Simulate.keyDown(selectInputNode, { key: 'ArrowDown', keyCode: 40 }); + }); + const selectMenuOptionNodes = selectNode.querySelectorAll('.Select-option'); + expect(selectMenuOptionNodes.length).toBe(3); + act(() => { + Simulate.mouseDown(selectMenuOptionNodes[1]); + }); + expect(spy).toHaveBeenCalledWith({ type: 'attribute', name: 'id' }); + }); +}); diff --git a/web/client/components/styleeditor/config/blocks.js b/web/client/components/styleeditor/config/blocks.js index 72793ab4b8..9990cd0ca7 100644 --- a/web/client/components/styleeditor/config/blocks.js +++ b/web/client/components/styleeditor/config/blocks.js @@ -18,9 +18,30 @@ const vector3dStyleOptions = { }) }; +const INITIAL_OPTION_VALUE = '__height_mode_original__'; const billboard3dStyleOptions = { msBringToFront: property.msBringToFront({ - label: 'styleeditor.msBringToFront' + label: "styleeditor.msBringToFront" + }), + msHeightReference: property.msHeightReference({ + label: "styleeditor.heightReferenceFromGround" + }), + msHeight: property.multiInput({ + label: "styleeditor.height", + key: "msHeight", + isDisabled: (value, properties) => properties?.msHeightReference === 'clamp', + initialOptionValue: INITIAL_OPTION_VALUE, + getSelectOptions: ({ attributes }) => { + const numberAttributes = attributes + .map(({ label, attribute, type }) => + type === "number" ? { label, value: attribute } : null + ) + .filter((x) => !!x); + return [ + ...numberAttributes, + { label: 'Point height', value: INITIAL_OPTION_VALUE } + ]; + } }) }; @@ -81,7 +102,8 @@ const getBlocks = ({ strokeWidth: 1, radius: 16, rotate: 0, - msBringToFront: false + msBringToFront: false, + msHeightReference: 'none' } }, Icon: { @@ -126,7 +148,8 @@ const getBlocks = ({ opacity: 1, size: 32, rotate: 0, - msBringToFront: false + msBringToFront: false, + msHeightReference: 'none' } }, Line: { diff --git a/web/client/components/styleeditor/config/property.js b/web/client/components/styleeditor/config/property.js index 278176544d..d5af6d6805 100644 --- a/web/client/components/styleeditor/config/property.js +++ b/web/client/components/styleeditor/config/property.js @@ -336,6 +336,28 @@ const property = { }, setValue: (value) => !!value }), + msHeightReference: ({ key = 'msHeightReference', label = 'Height reference from ground' }) => ({ + type: 'toolbar', + label, + config: { + options: [{ + labelId: 'styleeditor.none', + value: 'none' + }, { + labelId: 'styleeditor.relative', + value: 'relative' + }, { + labelId: 'styleeditor.clamp', + value: 'clamp' + }] + }, + getValue: (value) => { + return { + [key]: value, + ...(value === 'clamp' && { msHeight: undefined }) + }; + } + }), shape: ({ label, key = 'wellKnownName' }) => ({ type: 'mark', label, @@ -455,6 +477,43 @@ const property = { isDisabled, isVisible }), + multiInput: ({ label, key = '', initialOptionValue, getSelectOptions = () => [], isDisabled, isVisible }) => ({ + type: 'multiInput', + label, + config: { + initialOptionValue, + getSelectOptions + }, + setValue: (value) => { + if (value === undefined) { + return { type: 'initial' }; + } + if (!isObject(value)) { + return { + type: 'constant', + value + }; + } + return value; + }, + getValue: (value) => { + if (value?.type === 'initial') { + return { + [key]: undefined + }; + } + if (value?.type === 'constant') { + return { + [key]: value?.value ?? 0 + }; + } + return { + [key]: value + }; + }, + isDisabled, + isVisible + }), colorRamp: ({ label, key = '', getOptions = () => [] }) => ({ type: 'colorRamp', label, diff --git a/web/client/themes/default/less/style-editor.less b/web/client/themes/default/less/style-editor.less index 3ad31d01fe..8bbc82a0dc 100644 --- a/web/client/themes/default/less/style-editor.less +++ b/web/client/themes/default/less/style-editor.less @@ -615,6 +615,11 @@ Ported to CodeMirror by Peter Kroon flex: 1; } } + + .flex-grow-1 { + flex-grow: 1; + } + &.ms-symbolizer-value-invalid { outline: 1px solid @ms-danger; } diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index 6617b1b4c8..01efaa093e 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -2250,6 +2250,10 @@ "fill": "Füllfarbe", "clampToGround": "Klemme an Masse", "msBringToFront": "In den Vordergrund bringen", + "heightReferenceFromGround": "Höhenbezug vom boden", + "height": "Höhe", + "constantValue": "Konstanter wert", + "attributeValue": "Attributwert", "strokeColor": "Linienfarbe", "strokeWidth": "Linienbreite", "radius": "Radius", @@ -2258,6 +2262,8 @@ "3dTile": "3D", "terrain": "Gelände", "both": "Beide", + "relative": "Relativ", + "clamp": "Klemme", "addIconSymbolizer": "Symbol-Symbologie hinzufügen", "addIconRule": "Symbolregel hinzufügen", "image": "Bild", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index d5c1b7aba6..32aca5aa33 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -2213,6 +2213,10 @@ "fill": "Fill color", "clampToGround": "Clamp to ground", "msBringToFront": "Bring to front", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", "strokeColor": "Stroke color", "strokeWidth": "Stroke width", "radius": "Radius", @@ -2221,6 +2225,8 @@ "3dTile": "3D", "terrain": "Terrain", "both": "Both", + "relative": "Relative", + "clamp": "Clamp", "addIconSymbolizer": "Add icon symbolizer", "addIconRule": "Add icon rule", "image": "Image", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index 3e95896c13..6591c9e60d 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -2212,6 +2212,10 @@ "fill": "Color de relleno", "clampToGround": "Adaptar al terreno", "msBringToFront": "Traer al frente", + "heightReferenceFromGround": "Referencia de altura desde el suelo", + "height": "Altura", + "constantValue": "Valor constante", + "attributeValue": "Valor de atributo", "strokeColor": "Color de línea", "strokeWidth": "Ancho de línea", "radius": "Radio", @@ -2220,6 +2224,8 @@ "3dTile": "3D", "terrain": "Terreno", "both": "Ambos", + "relative": "Relativo", + "clamp": "Abrazadera", "addIconSymbolizer": "Agregar simbolizador de icono", "addIconRule": "Agregar regla de icono", "image": "Imagen", diff --git a/web/client/translations/data.fi-FI.json b/web/client/translations/data.fi-FI.json index c6cfac52db..d93d9468c8 100644 --- a/web/client/translations/data.fi-FI.json +++ b/web/client/translations/data.fi-FI.json @@ -1789,7 +1789,13 @@ "setDefaultStyleSuccessTitle": "Tyylin asetus oletukseksi onnistui", "setDefaultStyleSuccessMessage": "Oletustyyli on otettu käyttöön", "setDefaultStyleErrorTitle": "Virhe oletustyylin asettamisessa", - "setDefaultStyleErrorMessage": "Ei ole mahdollista käyttää valittua tyyliä oletusasetuksena" + "setDefaultStyleErrorMessage": "Ei ole mahdollista käyttää valittua tyyliä oletusasetuksena", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index c2acce9b50..db7cda1c57 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -2213,6 +2213,10 @@ "fill": "Fill color", "clampToGround": "S'adapter au terrain", "msBringToFront": "Mettre en avant", + "heightReferenceFromGround": "Référence de hauteur à partir du sol", + "height": "Hauteur", + "constantValue": "Valeur constante", + "attributeValue": "Valeur d'attribut", "strokeColor": "Couleur de ligne", "strokeWidth": "Largeur de ligne", "radius": "Rayon", @@ -2221,6 +2225,8 @@ "3dTile": "3D", "terrain": "Terrain", "both": "Les deux", + "relative": "Relatif", + "clamp": "Serrer", "addIconSymbolizer": "Ajouter un symboliseur d'icône", "addIconRule": "Ajouter une règle d'icône", "image": "Image", diff --git a/web/client/translations/data.hr-HR.json b/web/client/translations/data.hr-HR.json index 299a8cb127..03bf138529 100644 --- a/web/client/translations/data.hr-HR.json +++ b/web/client/translations/data.hr-HR.json @@ -1725,7 +1725,13 @@ "setDefaultStyleSuccessTitle": "Uspješna primjena zadanog stila", "setDefaultStyleSuccessMessage": "Zadani stil je uspješno postavljen", "setDefaultStyleErrorTitle": "Greška pri postavljanju zadanog stila", - "setDefaultStyleErrorMessage": "Nije moguće postaviti odabrani stil kao zadani" + "setDefaultStyleErrorMessage": "Nije moguće postaviti odabrani stil kao zadani", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index e8cc3e4dc1..bc454eef07 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -2214,6 +2214,10 @@ "fill": "Colore riempimento", "clampToGround": "Adattarsi al terreno", "msBringToFront": "Portare in primo piano", + "heightReferenceFromGround": "Riferimento di altezza da terra", + "height": "Altezza", + "constantValue": "Valore costante", + "attributeValue": "Valore attributo", "strokeColor": "Colore linea", "strokeWidth": "Spessore linea", "radius": "Raggio", @@ -2222,6 +2226,8 @@ "3dTile": "3D", "terrain": "Terreno", "both": "Ambedue", + "relative": "Parente", + "clamp": "Morsetto", "addIconSymbolizer": "Aggiungi stile icona", "addIconRule": "Aggiungi regola icona", "image": "Image", diff --git a/web/client/translations/data.nl-NL.json b/web/client/translations/data.nl-NL.json index 7b3a42413b..c135283b49 100644 --- a/web/client/translations/data.nl-NL.json +++ b/web/client/translations/data.nl-NL.json @@ -2064,7 +2064,7 @@ "deletedStyleErrorMessage": "Kan huidige stijl niet verwijderen", "savedStyleTitle": "Stijl opgeslagen", "savedStyleMessage": "Stijl is succesvol opgeslagen", - "errorTitle": "StyleEditor error", + "errorTitle": "StyleEditor error", "parsingCapabilitiesError": "

Mogelijke oorzaken:

het was niet mogelijk de mogelijkheden te ontleden en de stijlen op te halen", "globalError": "

Mogelijke oorzaken:

het was niet mogelijk om verbinding te maken met de service", "missingAvailableStyles": "Ontbrekende stijlen", @@ -2218,7 +2218,13 @@ "imageSrcInvalidBase64": "Dit is geen geldige base64-image. Sommige stijlrenderers ondersteunen dit type bron niet, je zou in plaats daarvan een externe url-bron kunnen gebruiken", "imageSrcNotSupportedBase64Image": "Sommige stijlrenderers ondersteunen geen base64-afbeeldingen, zelfs niet als de huidige bron geldig is", "warningTextOrderTitle": "Waarschuwing", - "warningTextOrder": "De volgorde van de regel met de tekststijl kan niet overeenkomen met de weergavevolgorde op de kaart. Dit gedrag is gerelateerd aan een rendering-engine die een label boven andere regels plaatst" + "warningTextOrder": "De volgorde van de regel met de tekststijl kan niet overeenkomen met de weergavevolgorde op de kaart. Dit gedrag is gerelateerd aan een rendering-engine die een label boven andere regels plaatst", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.pt-PT.json b/web/client/translations/data.pt-PT.json index ef9017ef8a..63d2519ce0 100644 --- a/web/client/translations/data.pt-PT.json +++ b/web/client/translations/data.pt-PT.json @@ -1691,7 +1691,13 @@ "setDefaultStyleSuccessTitle": "Success on set default style", "setDefaultStyleSuccessMessage": "Default Style has been successfully applied", "setDefaultStyleErrorTitle": "Error on set default style", - "setDefaultStyleErrorMessage": "It's not possible apply selected style as default" + "setDefaultStyleErrorMessage": "It's not possible apply selected style as default", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.sk-SK.json b/web/client/translations/data.sk-SK.json index ef595e87c7..05b444e199 100644 --- a/web/client/translations/data.sk-SK.json +++ b/web/client/translations/data.sk-SK.json @@ -2234,7 +2234,13 @@ "imageSrcInvalidBase64": "Toto nie je platný obrázok base64. Niektoré vykresľovače štýlov nepodporujú tento typ zdroja, môžeš namiesto toho použiť externý zdroj adries URL", "imageSrcNotSupportedBase64Image": "Niektoré vykresľovače štýlov nepodporujú obrázky base64, aj keď je aktuálny zdroj platný", "warningTextOrderTitle": "Upozornenie", - "warningTextOrder": "The order of rule containing text style could not match the rendering order on map. This behaviour is related to some rendering engine that draw label on top of other rules" + "warningTextOrder": "The order of rule containing text style could not match the rendering order on map. This behaviour is related to some rendering engine that draw label on top of other rules", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.sv-SE.json b/web/client/translations/data.sv-SE.json index eb803b9e4b..08310882f8 100644 --- a/web/client/translations/data.sv-SE.json +++ b/web/client/translations/data.sv-SE.json @@ -2230,7 +2230,13 @@ "imageSrcNotSupportedBase64Image": "Någon stilåtergivare stöder inte base64 -bilder även om den aktuella källan är giltig", "imageFormatEmpty": "Bildformatet kan inte identifieras. Välj bildformat bland de tillgängliga i rullgardinsmenyn Format ", "warningTextOrderTitle": "Varning", - "warningTextOrder": "Regelordningen som innehåller textstil kunde inte matcha återgivningsordningen på kartan. Detta beteende är relaterat till någon återgivningsmotor som ritar etikett ovanpå andra regler" + "warningTextOrder": "Regelordningen som innehåller textstil kunde inte matcha återgivningsordningen på kartan. Detta beteende är relaterat till någon återgivningsmotor som ritar etikett ovanpå andra regler", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/translations/data.vi-VN.json b/web/client/translations/data.vi-VN.json index 0d549ccdcc..b258a272f3 100644 --- a/web/client/translations/data.vi-VN.json +++ b/web/client/translations/data.vi-VN.json @@ -1382,7 +1382,13 @@ "updateStyleErrorTitle": "Chỉnh sửa kiểu", "updateTmpErrorTitle": "Cập nhật kiểu tạm thời", "updateTmpStyleErrorMessage": "Kiểu tạm thời không thể được cập nhậ, có thể do định dạng kiểu không được hỗ trợ hoặc vấn đề kết nối.", - "validationErrorTitle": "Lỗi xác nhận" + "validationErrorTitle": "Lỗi xác nhận", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "styler": { "layerlabel": "Lớp", diff --git a/web/client/translations/data.zh-ZH.json b/web/client/translations/data.zh-ZH.json index a08391ce30..4dae8da241 100644 --- a/web/client/translations/data.zh-ZH.json +++ b/web/client/translations/data.zh-ZH.json @@ -1668,7 +1668,13 @@ "setDefaultStyleSuccessTitle": "Success on set default style", "setDefaultStyleSuccessMessage": "Default Style has been successfully applied", "setDefaultStyleErrorTitle": "Error on set default style", - "setDefaultStyleErrorMessage": "It's not possible apply selected style as default" + "setDefaultStyleErrorMessage": "It's not possible apply selected style as default", + "heightReferenceFromGround": "Height reference from ground", + "height": "Height", + "constantValue": "Constant value", + "attributeValue": "Attribute value", + "relative": "Relative", + "clamp": "Clamp" }, "playback": { "settings": { diff --git a/web/client/utils/VectorStyleUtils.js b/web/client/utils/VectorStyleUtils.js index fd528dd4a3..7a74dc6cc7 100644 --- a/web/client/utils/VectorStyleUtils.js +++ b/web/client/utils/VectorStyleUtils.js @@ -733,7 +733,8 @@ function msStyleToSymbolizer(style, feature) { strokeOpacity: style.opacity, strokeWidth: style.weight, radius: style.radius ?? 10, - wellKnownName: 'Circle' + wellKnownName: 'Circle', + msHeightReference: 'none' }); } if (isAttrPresent(style, ['iconUrl']) && !style.iconGlyph && !style.iconShape) { @@ -742,7 +743,8 @@ function msStyleToSymbolizer(style, feature) { image: style.iconUrl, size: max(style.iconSize || [32]), opacity: 1, - rotate: 0 + rotate: 0, + msHeightReference: 'none' }); } if (isMarkerStyle(style)) { @@ -751,7 +753,8 @@ function msStyleToSymbolizer(style, feature) { image: MarkerUtils.extraMarkers.markerToDataUrl(style), size: 45, opacity: 1, - rotate: 0 + rotate: 0, + msHeightReference: 'none' }); } if (isSymbolStyle(style)) { @@ -767,7 +770,8 @@ function msStyleToSymbolizer(style, feature) { image: symbolUrlCustomized, size: style.size, opacity: 1, - rotate: 0 + rotate: 0, + msHeightReference: 'none' }; }) .catch(() => ({})); diff --git a/web/client/utils/styleparser/CesiumStyleParser.js b/web/client/utils/styleparser/CesiumStyleParser.js index 5884f223f9..48bd7d6e8f 100644 --- a/web/client/utils/styleparser/CesiumStyleParser.js +++ b/web/client/utils/styleparser/CesiumStyleParser.js @@ -8,7 +8,7 @@ import * as Cesium from 'cesium'; import chroma from 'chroma-js'; -import { castArray } from 'lodash'; +import { castArray, isNumber } from 'lodash'; import range from 'lodash/range'; function getCesiumColor({ color, opacity }) { @@ -45,6 +45,44 @@ function getCesiumDashArray({ color, opacity, dasharray }) { }); } +const getNumberAttributeValue = (value, properties) => { + const constantHeight = parseFloat(value); + + if (!isNaN(constantHeight) && isNumber(constantHeight)) { + return constantHeight; + } + + const attributeValue = value?.type === "attribute" && parseFloat(properties[value.name]); + + if (!isNaN(attributeValue) && isNumber(attributeValue)) { + return attributeValue; + } + return null; +}; + +function modifyPointHeight(map, entity, symbolizer, properties) { + // store the initial position of the feature created from the GeoJSON feature + if (!entity._msPosition) { + entity._msPosition = entity.position.getValue(Cesium.JulianDate.now()); + } + + const height = getNumberAttributeValue(symbolizer.msHeight, properties); + + if (height === null) { + entity.position.setValue(entity._msPosition); + return; + } + const ellipsoid = map?.scene?.globe?.ellipsoid; + if (!ellipsoid) { + return; + } + + const cartographic = ellipsoid.cartesianToCartographic(entity._msPosition); + cartographic.height = height; + entity.position.setValue(ellipsoid.cartographicToCartesian(cartographic)); + return; +} + function parseLabel(feature, label = '') { if (!feature.properties) { return label; @@ -76,6 +114,12 @@ const GRAPHIC_KEYS = [ 'wall' ]; +const HEIGHT_REFERENCE_CONSTANTS_MAP = { + none: 'NONE', + relative: 'RELATIVE_TO_GROUND', + clamp: 'CLAMP_TO_GROUND' +}; + function getStyleFuncFromRules({ rules = [] } = {}, { @@ -85,6 +129,7 @@ function getStyleFuncFromRules({ }) { return ({ entities, + map, opacity: globalOpacity = 1 }) => { @@ -119,12 +164,13 @@ function getStyleFuncFromRules({ scale, rotation: Cesium.Math.toRadians(-1 * symbolizer.rotate || 0), disableDepthTestDistance: symbolizer.msBringToFront ? Number.POSITIVE_INFINITY : 0, - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, + heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[symbolizer.msHeightReference] || 'NONE'], color: getCesiumColor({ color: '#ffffff', opacity: 1 * globalOpacity }) }); + modifyPointHeight(map, entity, symbolizer, properties); } } if (symbolizer.kind === 'Icon' && entity.position) { @@ -137,12 +183,13 @@ function getStyleFuncFromRules({ scale, rotation: Cesium.Math.toRadians(-1 * symbolizer.rotate || 0), disableDepthTestDistance: symbolizer.msBringToFront ? Number.POSITIVE_INFINITY : 0, - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, + heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[symbolizer.msHeightReference] || 'NONE'], color: getCesiumColor({ color: '#ffffff', opacity: symbolizer.opacity * globalOpacity }) }); + modifyPointHeight(map, entity, symbolizer, properties); } } if (symbolizer.kind === 'Line' && entity._msStoredCoordinates.polyline) { @@ -203,7 +250,7 @@ function getStyleFuncFromRules({ color: symbolizer.color, opacity: 1 * globalOpacity }), - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, + heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[symbolizer.msHeightReference] || 'NONE'], pixelOffset: new Cesium.Cartesian2(symbolizer?.offset?.[0] ?? 0, symbolizer?.offset?.[1] ?? 0), // outline is not working // rotation is not available as property @@ -213,6 +260,7 @@ function getStyleFuncFromRules({ }), outlineWidth: symbolizer.haloWidth }); + modifyPointHeight(map, entity, symbolizer, properties); } } }); diff --git a/web/client/utils/styleparser/__tests__/CesiumStyleParser-test.js b/web/client/utils/styleparser/__tests__/CesiumStyleParser-test.js index 0e21b0da90..25ef71f2fe 100644 --- a/web/client/utils/styleparser/__tests__/CesiumStyleParser-test.js +++ b/web/client/utils/styleparser/__tests__/CesiumStyleParser-test.js @@ -216,7 +216,7 @@ describe('CesiumStyleParser', () => { expect(entities[0].billboard.scale.getValue()).toBe(1); expect(entities[0].billboard.rotation.getValue()).toBe(-Math.PI / 2); expect(entities[0].billboard.disableDepthTestDistance.getValue()).toBe(Number.POSITIVE_INFINITY); - expect(entities[0].billboard.heightReference.getValue()).toBe(Cesium.HeightReference.CLAMP_TO_GROUND); + expect(entities[0].billboard.heightReference.getValue()).toBe(Cesium.HeightReference.NONE); } catch (e) { done(e); } @@ -273,7 +273,7 @@ describe('CesiumStyleParser', () => { expect(entities[0].billboard.scale.getValue()).toBe(0.125); expect(entities[0].billboard.rotation.getValue()).toBe(-Math.PI / 2); expect(entities[0].billboard.disableDepthTestDistance.getValue()).toBe(Number.POSITIVE_INFINITY); - expect(entities[0].billboard.heightReference.getValue()).toBe(Cesium.HeightReference.CLAMP_TO_GROUND); + expect(entities[0].billboard.heightReference.getValue()).toBe(Cesium.HeightReference.NONE); } catch (e) { done(e); } @@ -329,7 +329,7 @@ describe('CesiumStyleParser', () => { expect({ ...entities[0].label.fillColor.getValue() }).toEqual({ red: 0, green: 0, blue: 0, alpha: 1 }); expect({ ...entities[0].label.outlineColor.getValue() }).toEqual({ red: 1, green: 1, blue: 1, alpha: 1 }); expect(entities[0].label.outlineWidth.getValue()).toBe(2); - expect(entities[0].label.heightReference.getValue()).toBe(Cesium.HeightReference.CLAMP_TO_GROUND); + expect(entities[0].label.heightReference.getValue()).toBe(Cesium.HeightReference.NONE); } catch (e) { done(e);