diff --git a/TODO_AFTER_LAYER_REFACTORING.md b/TODO_AFTER_LAYER_REFACTORING.md new file mode 100644 index 000000000..520db8970 --- /dev/null +++ b/TODO_AFTER_LAYER_REFACTORING.md @@ -0,0 +1,5 @@ +- remove `helpers` +- remove `voxel-helpers` +- re-add zoom to object on permalink (see behavior in old sidebar) +- reorder pick results by order of layers +- add 3dtiles v1.1 code to refactored layers (+ attempt to use shader instead of alpha color) \ No newline at end of file diff --git a/api/src/layers/config.rs b/api/src/layers/config.rs index 1f48ed6a5..7e1b9fcd3 100644 --- a/api/src/layers/config.rs +++ b/api/src/layers/config.rs @@ -30,11 +30,6 @@ pub struct LayerConfig { #[serde(default, skip_serializing)] pub voxel_mappings: HashMap, - /// A list of voxel filters that may be reused by multiple layers. - /// Each entry's key is used to identify it within this config. - #[serde(default, skip_serializing)] - pub voxel_filters: HashMap, - /// A list of voxel band displays that may be reused by multiple layers. /// Each entry's key is used to identify it within this config. #[serde(default, skip_serializing)] @@ -133,7 +128,6 @@ impl LayerConfig { layers: vec![], groups: vec![], voxel_mappings: std::mem::take(&mut self.voxel_mappings), - voxel_filters: std::mem::take(&mut self.voxel_filters), tiff_displays: Default::default(), }; @@ -175,17 +169,15 @@ impl LayerConfig { } for (key, mapping) in &context.config.voxel_mappings { - if mapping.use_count == 0 { + let use_count = match mapping { + VoxelMappingDefinition::Range(it) => it.use_count, + VoxelMappingDefinition::Category(it) => it.use_count, + }; + if use_count == 0 { tracing::warn!("[{}] Voxel mapping \"{key}\" is unused.", context.display) } } - for (key, filter) in &context.config.voxel_filters { - if filter.use_count == 0 { - tracing::warn!("[{}] Voxel filter \"{key}\" is unused.", context.display) - } - } - for (key, group) in context.known_groups { if group.use_count == 0 { tracing::warn!("[{}] Group \"{key}\" is unused.", context.display) diff --git a/api/src/layers/voxel.rs b/api/src/layers/voxel.rs index d2205f0fc..e1b49fab3 100644 --- a/api/src/layers/voxel.rs +++ b/api/src/layers/voxel.rs @@ -1,46 +1,87 @@ use crate::layers::config::{Parse, ParseContext}; use anyhow::anyhow; use serde::{Deserialize, Deserializer, Serialize}; +use crate::LayerSource; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] pub struct VoxelLayer { - pub url: String, - - /// The translation key providing the display name for the unit of the layer's values. - // TODO document what happens when this is not set. - pub unit_label: Option, + pub source: LayerSource, /// The key of the property that contains the layer's data points. + /// Note that there will need to be a mapping for this key for the layer to render. pub data_key: String, - /// The value that represents the absence of data on this layer. - pub no_data: i32, + pub values: VoxelLayerValues, + + /// The layer's value mappings. + /// This determines how the layer can be rendered and otherwise displayed to the user. + pub mappings: Vec, +} - /// The layer's value mapping. - /// This determines how the layer is rendered and otherwise displayed to the user. - pub mapping: VoxelMapping, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] +pub struct VoxelLayerValues { + /// The value that represents an absent datapoint. + /// "Absence" here means that it doesn't exist, and does not need to be displayed. + pub no_data: i32, - /// The layer's value filter configuration. - /// This determines how the user is able to filter the layer's data points. - pub filter: VoxelFilter, + /// The value that represents a datapoint without a value. + /// The datapoint still exists and should be displayed, it just isn't backed by a meaningful value. + /// + /// The main use of undefined values are datapoints that are only meaningful for specific mappings. + /// If a datapoint is undefined on all mapped keys, it may be treated as `no_data`. + pub undefined: i32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum VoxelMapping { +pub enum VoxelLayerMapping { Reference(String), Definition(VoxelMappingDefinition), } +impl Parse for VoxelLayer { + fn parse(mut self, context: &mut ParseContext) -> anyhow::Result { + for mapping in &mut self.mappings { + if let VoxelLayerMapping::Reference(name) = mapping { + let definition = context + .config + .voxel_mappings + .get_mut(name) + .ok_or_else(|| anyhow!("Unknown voxel mapping: {name}"))?; + match definition { + VoxelMappingDefinition::Range(it) => it.use_count += 1, + VoxelMappingDefinition::Category(it) => it.use_count += 1, + } + *mapping = VoxelLayerMapping::Definition(definition.clone()); + } + } + Ok(self) + } +} + + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VoxelMappingDefinition { + Range(VoxelRangeMapping), + Category(VoxelItemMapping), +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] -pub struct VoxelMappingDefinition { - /// The minimum and maximum values of the layer's data points. +pub struct VoxelRangeMapping { + /// The key of the property that contains the data points. + pub key: String, + + /// The minimum and maximum values of the data points. pub range: (i32, i32), - /// The sequence of colors applied to the range of values. - /// These will be scaled linearly to fit the value range. + /// The colors with which the range is displayed. + /// + /// If this has the same length as [range], each value gets its own, specific value. + /// If there are fewer colors than values, the colors are interpreted as a gradient on which the values can be placed. pub colors: Vec, /// The number of times this definition has been referenced. @@ -52,94 +93,51 @@ pub struct VoxelMappingDefinition { pub use_count: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum VoxelFilter { - Reference(String), - Definition(VoxelFilterDefinition), -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] -pub struct VoxelFilterDefinition { - #[serde(default)] - pub lithology: Option, +pub struct VoxelItemMapping { + /// The key of the property that contains the data points. + pub key: String, - #[serde(default)] - pub conductivity: Option, + /// The mapping's items. + /// Each item represents a unique value. + pub items: Vec, /// The number of times this definition has been referenced. /// This is used to ensure that the definition is not unused. /// /// If this definition is directly attached to a layer, - /// this field will never be used. + /// this field will never be used. #[serde(skip, default)] pub use_count: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] -pub struct LithologyVoxelFilter { - /// The key of the property that contains the lithology data points. - pub key: String, - - /// The filter's items. - /// Each item represents a value that can be filtered by. - pub items: Vec, -} - #[derive(Debug, Clone, Serialize)] #[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] -pub struct LithologyVoxelFilterItem { +pub struct VoxelItemMappingItem { /// The translation key providing the display name for the item. pub label: String, /// The value that the data points matching this item have. pub value: i32, -} -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all(serialize = "camelCase"))] -pub struct ConductivityVoxelFilter { - /// The key of the property that contains the conductivity data points. - pub key: String, - - /// The minimum and maximum values of the conductivity data points. - pub range: (i32, i32), + /// The color in which this value is displayed. + pub color: String, } -/// Custom Deserialize implementation for [LithologyVoxelFilterItem] that allows -/// the configuration to be written as tuple `(u32, String)`. -impl<'de> Deserialize<'de> for LithologyVoxelFilterItem { +/// Custom Deserialize implementation for [VoxelItemMappingItem] that allows +/// the configuration to be written as tuple `(i32, { label: String, color: String })`. +impl<'de> Deserialize<'de> for VoxelItemMappingItem { fn deserialize(d: D) -> Result where D: Deserializer<'de>, { - let (value, label) = <(i32, String)>::deserialize(d)?; - Ok(Self { label, value }) - } -} - -impl Parse for VoxelLayer { - fn parse(mut self, context: &mut ParseContext) -> anyhow::Result { - if let VoxelMapping::Reference(name) = &self.mapping { - let mapping = context - .config - .voxel_mappings - .get_mut(name) - .ok_or_else(|| anyhow!("Unknown voxel mapping: {name}"))?; - mapping.use_count += 1; - self.mapping = VoxelMapping::Definition(mapping.clone()); + #[derive(Deserialize)] + struct Item { + pub label: String, + pub color: String, } - if let VoxelFilter::Reference(name) = &self.filter { - let filter = context - .config - .voxel_filters - .get_mut(name) - .ok_or_else(|| anyhow!("Unknown voxel filter: {name}"))?; - filter.use_count += 1; - self.filter = VoxelFilter::Definition(filter.clone()); - } - Ok(self) + let (value, item) = <(i32, Item)>::deserialize(d)?; + Ok(Self { label: item.label, value, color: item.color }) } -} +} \ No newline at end of file diff --git a/layers/01-maps_and_models.json5 b/layers/01-maps_and_models.json5 index 8ccbd2074..34cb03796 100644 --- a/layers/01-maps_and_models.json5 +++ b/layers/01-maps_and_models.json5 @@ -101,7 +101,8 @@ { id: 'lyr_unconsolidated_rocks_label', children: [ - 'voxel_aaretal_litho' + 'voxel_aaretal_lithology', + 'voxel_aaretal_conductivity', ] }, diff --git a/layers/special/voxels.json5 b/layers/special/voxels.json5 index d5d79dcf7..8c8db1994 100644 --- a/layers/special/voxels.json5 +++ b/layers/special/voxels.json5 @@ -2,78 +2,66 @@ layers: [ { type: "Voxel", - id: "voxel_aaretal_litho", - url: "https://download.swissgeol.ch/testvoxel/20240415/Voxel-Aaretal-Combined/tileset.json", + id: "voxel_aaretal_lithology", + source: { + type: "CesiumIon", + asset_id: 3882543, + }, data_key: "Index", - no_data: -99999, - mapping: 'aaretal', - filter: 'aaretal', + mappings: ['conductivity','lithology'], + values: { + no_data: -99999, + undefined: -9999, + }, geocat_id: "b1a36f66-638a-4cfb-88d3-b0df6c7a7502", }, + { + type: "Voxel", + id: "voxel_aaretal_conductivity", + source: { + type: "CesiumIon", + asset_id: 3882543, + }, + data_key: "logk", + mappings: ['conductivity', 'lithology'], + values: { + no_data: -99999, + undefined: -9999, + }, + geocat_id: "b1a36f66-638a-4cfb-88d3-b0df6c7a7502", + } ], voxel_mappings: { - aaretal: { - range: [-9999, 23], - colors: [ - 'rgb(204, 204, 204)', - 'rgb(92, 255, 105)', - 'rgb(122, 211, 255)', - 'rgb(128, 128, 128)', - 'rgb(0, 255, 234)', - 'rgb(92, 201, 255)', - 'rgb(31, 255, 49)', - 'rgb(179, 204, 102)', - 'rgb(61, 190, 255)', - 'rgb(31, 180, 255)', - 'rgb(0, 170, 255)', - 'rgb(128, 153, 51)', - 'rgb(0, 150, 224)', - 'rgb(0, 224, 19)', - 'rgb(0, 129, 194)', - 'rgb(64, 77, 25)', - 'rgb(0, 163, 14)', - 'rgb(0, 109, 163)', - 'rgb(0, 88, 133)', - 'rgb(255, 255, 0)', - 'rgb(0, 68, 102)', - 'rgb(0, 102, 9)', + lithology: { + key: 'Index', + items: [ + [3, { label: 'Verlandungssedimente, Sumpf, Ried', color: 'rgb(92, 255, 105)' }], + [4, { label: 'Subrezente bis rezente Alluvionen (Fluss- und Bachschotter, Überschwemmungssediment, undifferenziert)', color: 'rgb(122, 211, 255)' }], + [5, { label: 'Hangschutt / Hanglehm (undifferenziert)', color: 'rgb(128, 128, 128)' }], + [6, { label: 'Bachschutt / Bachschuttkegel (undifferenziert)', color: 'rgb(0, 255, 234)' }], + [7, { label: 'Spät- bis postglaziale Schotter', color: 'rgb(92, 201, 255)' }], + [8, { label: 'Spät- bis postglaziale Stausedimente und Seeablagerungen (undifferenziert)', color: 'rgb(31, 255, 49)' }], + [9, { label: 'Spätglaziale Moräne (undifferenziert)', color: 'rgb(179, 204, 102)' }], + [10, { label: 'Rückzugsschotter der Letzten Vergletscherung ("Felderschotter" und Äquivalente)', color: 'rgb(61, 190, 255)' }], + [11, { label: 'Stauschotter (undifferenziert)', color: 'rgb(31, 180, 255)' }], + [12, { label: 'Rinnenschotter', color: 'rgb(0, 170, 255)' }], + [13, { label: 'Moräne der Letzten Vergletscherung', color: 'rgb(128, 153, 51)' }], + [14, { label: 'Vorstossschotter der Letzten Vergletscherung (vorwiegend Münsingen- u. Karlsruhe-Schotter)', color: 'rgb(0, 150, 224)' }], + [15, { label: 'Interglaziale Seetone (Eemzeitliche Seetone)', color: 'rgb(0, 224, 19)' }], + [16, { label: 'Rückzugsschotter der Vorletzten Vergletscherung, Kies-Sand-Komplex von Kleinhöchstetten', color: 'rgb(0, 129, 194)' }], + [17, { label: 'Moräne der Vorletzten Vergletscherung ("Altmoräne")', color: 'rgb(64, 77, 25)' }], + [18, { label: 'Vorletzteiszeitliche glaziolakustrische Ablagerungen und Schlammmoräne', color: 'rgb(0, 163, 14)' }], + [19, { label: 'Alte Deltaschotter im Belpmoos', color: 'rgb(0, 109, 163)' }], + [20, { label: 'Uttigen-Bümberg-Steghalde-Schotter', color: 'rgb(0, 88, 133)' }], + [21, { label: 'Oppligen-Sand', color: 'rgb(255, 255, 0)' }], + [22, { label: 'Raintal-Deltaschotter, Hani-Deltaschotter', color: 'rgb(0, 68, 102)' }], + [23, { label: 'Alte Seetone', color: 'rgb(0, 102, 9)' }] ], - } - }, - voxel_filters: { - aaretal: { - conductivity: { - key: 'logk', - range: [-9, -1], - }, - lithology: { - key: 'Index', - items: [ - // TODO translate the labels. - [-9999, 'vox_filter_undefined_lithology'], - [3, 'Verlandungssedimente, Sumpf, Ried'], - [4, 'Subrezente bis rezente Alluvionen (Fluss- und Bachschotter, Überschwemmungssediment, undifferenziert)'], - [5, 'Hangschutt / Hanglehm (undifferenziert)'], - [6, 'Bachschutt / Bachschuttkegel (undifferenziert)'], - [7, 'Spät- bis postglaziale Schotter'], - [8, 'Spät- bis postglaziale Stausedimente und Seeablagerungen (undifferenziert)'], - [9, 'Spätglaziale Moräne (undifferenziert)'], - [10, 'Rückzugsschotter der Letzten Vergletscherung ("Felderschotter" und Äquivalente)'], - [11, 'Stauschotter (undifferenziert)'], - [12, 'Rinnenschotter'], - [13, 'Moräne der Letzten Vergletscherung'], - [14, 'Vorstossschotter der Letzten Vergletscherung (vorwiegend Münsingen- u. Karlsruhe-Schotter)'], - [15, 'Interglaziale Seetone (Eemzeitliche Seetone)'], - [16, 'Rückzugsschotter der Vorletzten Vergletscherung, Kies-Sand-Komplex von Kleinhöchstetten'], - [17, 'Moräne der Vorletzten Vergletscherung ("Altmoräne")'], - [18, 'Vorletzteiszeitliche glaziolakustrische Ablagerungen und Schlammmoräne'], - [19, 'Alte Deltaschotter im Belpmoos'], - [20, 'Uttigen-Bümberg-Steghalde-Schotter'], - [21, 'Oppligen-Sand'], - [22, 'Raintal-Deltaschotter, Hani-Deltaschotter'], - [23, 'Alte Seetone'], - ] - } - } + }, + conductivity: { + key: 'logk', + range: [-9, -1], + colors: ['rgb(0, 102, 255)', 'rgb(255, 204, 0)', 'rgb(204, 0, 0)'] + }, } -} \ No newline at end of file +} diff --git a/ui/locales/app/app.de.json b/ui/locales/app/app.de.json index 98bae544e..ec4f838a0 100644 --- a/ui/locales/app/app.de.json +++ b/ui/locales/app/app.de.json @@ -276,18 +276,8 @@ "tracking_limitations_of_liability_header": "Haftungsausschluss", "tracking_limitations_of_liability_text": "Obwohl das Bundesamt für Landestopografie swisstopo mit aller Sorgfalt auf die Richtigkeit der veröffentlichten Informationen achtet, kann hinsichtlich der inhaltlichen Richtigkeit, Genauigkeit, Aktualität, Zuverlässigkeit und Vollständigkeit dieser Informationen keine Gewährleistung übernommen werden.
Swisstopo behält sich ausdrücklich vor, jederzeit Inhalte ohne Ankündigung ganz oder teilweise zu ändern, zu löschen oder zeitweise nicht zu veröffentlichen.
Haftungsansprüche gegen swisstopo wegen Schäden materieller oder immaterieller Art, welche aus dem Zugriff oder der Nutzung bzw. Nichtnutzung der veröffentlichten Informationen, durch Missbrauch der Verbindung oder durch technische Störungen entstanden sind, werden ausgeschlossen.
", "view": "Ansicht", - "vox_filter_and": "AND", - "vox_filter_apply": "Filter anwenden", "vox_filter_filtering_on": "Filtern von: ", - "vox_filter_hydraulic_conductivity": "Hydraulische Leitfähigkeit", - "vox_filter_lithology": "Modelliereinheit", - "vox_filter_klasse": "Durchlässigkeit", - "vox_filter_max": "Max", - "vox_filter_min": "Min", - "vox_filter_or": "OR", "vox_filter_select_all": "Alle auswählen", - "vox_filter_undefined_conductivity": "Undefinierte Hydraulische Leitfähigkeit", - "vox_filter_undefined_lithology": "undefiniert", "vox_filter_unselect_all": "Alle abwählen", "vox_filter_klasse_1": "sehr gross", "vox_filter_klasse_2": "gross", @@ -295,7 +285,6 @@ "vox_filter_klasse_4": "mässig", "vox_filter_klasse_5": "gering", "vox_filter_klasse_6": "sehr gering", - "vox_filter_xor": "XOR", "welcome_get_shortlink_error": "Es ist ein Fehler aufgetreten, so dass der Link konnte nicht erstellt werden konnte. Bitte proboieren Sie es zu einem späteren Zeitpunkt noch einmal.", "lyr_ch_swisstopo_geologie_geologischer_atlas_label": "Geologischer Atlas GA25", "lyr_ch_swisstopo_geologie_geotechnik_gk500_lithologie_hauptgruppen_label": "Lithologie 500", diff --git a/ui/locales/app/app.en.json b/ui/locales/app/app.en.json index 13dd51885..8e7a0a931 100644 --- a/ui/locales/app/app.en.json +++ b/ui/locales/app/app.en.json @@ -276,17 +276,11 @@ "tracking_limitations_of_liability_text": "Although every care has been taken by the Federal Office of Topography swisstopo to ensure the accuracy of the information published, no guarantee can be given with regard to the accurate, reliable, up-to-date or complete nature of this information.
swisstopo reserves the right to alter or remove the content, in full or in part, without prior notice.
Liability claims against swisstopo for material or immaterial damage resulting from access to or use or non-use of the published information, from misuse of the connection or from technical faults are excluded.
", "view": "View", "vox_filter_and": "AND", - "vox_filter_apply": "Apply filter", "vox_filter_filtering_on": "Filtering on: ", - "vox_filter_hydraulic_conductivity": "Hydraulic Conductivity", - "vox_filter_lithology": "Modelling Unit", - "vox_filter_klasse": "Permeability", "vox_filter_max": "Max", "vox_filter_min": "Min", "vox_filter_or": "OR", "vox_filter_select_all": "Select all", - "vox_filter_undefined_conductivity": "Undefined Conductivity", - "vox_filter_undefined_lithology": "undefined", "vox_filter_unselect_all": "Unselect all", "vox_filter_klasse_1": "very large", "vox_filter_klasse_2": "large", diff --git a/ui/locales/app/app.fr.json b/ui/locales/app/app.fr.json index ca4658934..7abd0518a 100644 --- a/ui/locales/app/app.fr.json +++ b/ui/locales/app/app.fr.json @@ -275,17 +275,11 @@ "tracking_limitations_of_liability_text": "Malgré la grande attention qu’il porte à la justesse des informations diffusées sur ce site, l’Office fédéral de topographie swisstopo ne peut endosser aucune responsabilité quant à la fidélité, à l’exactitude, à l’actualité, à la fiabilité et à l’intégralité de ces informations.
swisstopo se réserve expressément le droit de modifier en partie ou en totalité le contenu de ce site, de le supprimer ou d’en suspendre temporairement la diffusion, et ce à tout moment et sans avertissement préalable.
swisstopo ne saurait être tenu pour responsable des dommages matériels ou immatériels qui pourraient être causés par l’accès aux informations diffusées ou par leur utilisation ou non-utilisation, par le mauvais usage de la connexion ou par des problèmes techniques.
", "view": "Vue", "vox_filter_and": "AND", - "vox_filter_apply": "Appliquer le filtre", "vox_filter_filtering_on": "Filtre sur", - "vox_filter_hydraulic_conductivity": "Conductivité hydraulique", - "vox_filter_lithology": "Unité de modélisation", - "vox_filter_klasse": "Perméabilité", "vox_filter_max": "Max", "vox_filter_min": "Min", "vox_filter_or": "OR", "vox_filter_select_all": "Select all", - "vox_filter_undefined_conductivity": "Conductivité hydraulique indifinie", - "vox_filter_undefined_lithology": "indifinie", "vox_filter_unselect_all": "Désélectionner tout", "vox_filter_klasse_1": "très grand", "vox_filter_klasse_2": "grand", diff --git a/ui/locales/app/app.it.json b/ui/locales/app/app.it.json index d1e171c0e..bc2053b09 100644 --- a/ui/locales/app/app.it.json +++ b/ui/locales/app/app.it.json @@ -280,17 +280,11 @@ "tracking_limitations_of_liability_text": "Nonostante si presti grande attenzione all’esattezza delle informazioni pubblicate su questo sito, l’Ufficio federale di topografia swisstopo declina ogni responsabilità per la fedeltà, l’esattezza, l’attualità, l’affidabilità e la completezza di tali informazioni.
swisstopo si riserva esplicitamente il diritto in qualsiasi momento di modificare parzialmente o completamente il contenuto del sito, di cancellarlo o di sospenderne temporaneamente la pubblicazione, senza alcun preavviso.
swisstopo declina ogni responsabilità per danni materiali o immateriali derivanti dall’accesso alle informazioni diffuse, dall’uso o dal mancato uso del sito, oppure che sono riconducibili a un malfunzionamento del collegamento o a disturbi tecnici del sito.
", "view": "Vista", "vox_filter_and": "AND", - "vox_filter_apply": "Applica il filtro", "vox_filter_filtering_on": "Filtraggio su: ", - "vox_filter_hydraulic_conductivity": "Conducibilità idraulica", - "vox_filter_lithology": "Unità di modellazione", - "vox_filter_klasse": "Permeabilità", "vox_filter_max": "Max", "vox_filter_min": "Min", "vox_filter_or": "OR", "vox_filter_select_all": "Selezionare tutti", - "vox_filter_undefined_conductivity": "Conducibilità idraulica indefinita", - "vox_filter_undefined_lithology": "indefinita", "vox_filter_unselect_all": "Deselezionare tutti", "vox_filter_klasse_1": "molto grande", "vox_filter_klasse_2": "grande", diff --git a/ui/locales/catalog/catalog.de.json b/ui/locales/catalog/catalog.de.json index 5ca3e25c9..38bc04af7 100644 --- a/ui/locales/catalog/catalog.de.json +++ b/ui/locales/catalog/catalog.de.json @@ -32,12 +32,28 @@ } }, "add_content_from_cesium_ion": "Inhalte von Cesium ion hinzufügen", + "timesWindow": { + "all": "Alle" + }, + "voxelFilterWindow": { + "items": { + "select_all": "alles aktivieren", + "deselect_all": "alles deaktivieren" + }, + "range": { + "min": "Min", + "max": "Max", + "shouldIncludeUndefined": "undefinierte Werte immer anzeigen?" + }, + "operators": { + "And": "AND", + "Or": "OR", + "Xor": "XOR" + } + }, "tiffBandsWindow": { "title": "{{ layer }} - Bänder", "open": "Bänder anzeigen", "legend": "Legende" - }, - "timesWindow": { - "all": "Alle" } } diff --git a/ui/locales/layers/layers.de.json b/ui/locales/layers/layers.de.json index becc35366..729936e98 100644 --- a/ui/locales/layers/layers.de.json +++ b/ui/locales/layers/layers.de.json @@ -54,6 +54,17 @@ "ch.swisstopo.swissimage": "Luftbild", "lakes_rivers_map": "Seen & Flüsse" }, - "geocatUrl": "https://www.geocat.ch/geonetwork/srv/ger/catalog.search#/metadata/{{id}}", - "noData": "kein Wert" + "attributes": { + "Voxel": { + "cellCenter": "", + "logk": "Hydraulische Leitfähigkeit", + "Index": "Modelliereinheit", + "Klasse": "Durchlässigkeit" + } + }, + "values": { + "noData": "kein Wert", + "undefined": "undefiniert" + }, + "geocatUrl": "https://www.geocat.ch/geonetwork/srv/ger/catalog.search#/metadata/{{id}}" } diff --git a/ui/locales/layers/layers.en.json b/ui/locales/layers/layers.en.json index ae6e26f98..283813313 100644 --- a/ui/locales/layers/layers.en.json +++ b/ui/locales/layers/layers.en.json @@ -40,6 +40,17 @@ "ch.swisstopo.swissimage": "Aerial map", "lakes_rivers_map": "Lakes & rivers" }, - "geocatUrl": "https://www.geocat.ch/geonetwork/srv/eng/catalog.search#/metadata/{{id}}", - "noData": "no value" + "attributes": { + "Voxel": { + "cellCenter": "", + "logk": "Hydraulic Conductivity", + "Index": "Modelling Unit", + "Klasse": "Permeability" + } + }, + "values": { + "noData": "no value", + "undefined": "undefined" + }, + "geocatUrl": "https://www.geocat.ch/geonetwork/srv/eng/catalog.search#/metadata/{{id}}" } diff --git a/ui/locales/layers/layers.fr.json b/ui/locales/layers/layers.fr.json index 75f3509de..71c8e14d4 100644 --- a/ui/locales/layers/layers.fr.json +++ b/ui/locales/layers/layers.fr.json @@ -40,6 +40,17 @@ "ch.swisstopo.swissimage": "Carte aérienne", "lakes_rivers_map": "Lacs et rivières" }, - "geocatUrl": "https://www.geocat.ch/geonetwork/srv/fre/catalog.search#/metadata/{{id}}", - "noData": "aucune valeur" + "attributes": { + "Voxel": { + "cellCenter": "", + "logk": "Conductivité hydraulique", + "Index": "Unité de modélisation", + "Klasse": "Perméabilité" + } + }, + "values": { + "noData": "aucune valeur", + "undefined": "indifinie" + }, + "geocatUrl": "https://www.geocat.ch/geonetwork/srv/fre/catalog.search#/metadata/{{id}}" } diff --git a/ui/locales/layers/layers.it.json b/ui/locales/layers/layers.it.json index d27480267..907cb33f5 100644 --- a/ui/locales/layers/layers.it.json +++ b/ui/locales/layers/layers.it.json @@ -40,6 +40,17 @@ "ch.swisstopo.swissimage": "Foto aerea", "lakes_rivers_map": "Laghi e fiumi" }, - "geocatUrl": "https://www.geocat.ch/geonetwork/srv/ita/catalog.search#/metadata/{{id}}", - "noData": "nessun valore" + "attributes": { + "Voxel": { + "cellCenter": "", + "logk": "Conducibilità idraulica", + "Index": "Unità di modellazione", + "Klasse": "Permeabilità" + } + }, + "values": { + "noData": "nessun valore", + "undefined": "indefinita" + }, + "geocatUrl": "https://www.geocat.ch/geonetwork/srv/ita/catalog.search#/metadata/{{id}}" } diff --git a/ui/package-lock.json b/ui/package-lock.json index 64438e0b4..8170cc0d4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,7 +15,7 @@ "@lit/context": "^1.1.3", "@lit/task": "^1.0.1", "@swissgeol/ui-core": "1.6.0-dev7", - "cesium": "1.134.0", + "cesium": "1.135.0", "d3-array": "3.2.4", "d3-axis": "3.0.0", "d3-scale": "4.0.2", @@ -1164,6 +1164,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3060,9 +3061,9 @@ } }, "node_modules/@cesium/engine": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@cesium/engine/-/engine-21.0.0.tgz", - "integrity": "sha512-V/gzRnjz7Csq5ft0FggqYRIUQMgnGAlQm7n0kCAFm2r3IPLR30GltJfPd05pByOUd3bn/XMo8S64ZeGEQpC0yg==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@cesium/engine/-/engine-22.0.0.tgz", + "integrity": "sha512-7hYwgzu5MYD7TryiZoXz6EvNk6YaBgT1y10L8iHQcKwmv6EPone46C8YGBerYb3uUHR21qmbmDWMSN7xGpBUiw==", "license": "Apache-2.0", "dependencies": { "@cesium/wasm-splats": "^0.1.0-alpha.2", @@ -3098,12 +3099,12 @@ "license": "Apache-2.0" }, "node_modules/@cesium/widgets": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@cesium/widgets/-/widgets-13.2.0.tgz", - "integrity": "sha512-LfYNnyZFqnxkyc4MkukALMvLUOKC8dpZWf71dEx/dvqrKmniRtsiAL2XuQhJmaBNaH7KpuX7rSnf/t7s+Id9pw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@cesium/widgets/-/widgets-14.0.0.tgz", + "integrity": "sha512-XjYHO2nBPJ0I6qZTTOZ7RnM0OUWgWxtwgc6QCQnOUifR98VEZfDZjQywIlt3nhIXTTcc8EaYhfYsym73n169zQ==", "license": "Apache-2.0", "dependencies": { - "@cesium/engine": "^21.0.0", + "@cesium/engine": "^22.0.0", "nosleep.js": "^0.12.0" }, "engines": { @@ -3209,6 +3210,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3232,6 +3234,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3608,6 +3611,7 @@ "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -3618,6 +3622,7 @@ "integrity": "sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/uuid": "10.0.0", "class-transformer": "0.5.1", @@ -4620,7 +4625,6 @@ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -5922,7 +5926,6 @@ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -6082,6 +6085,7 @@ "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", @@ -6345,7 +6349,6 @@ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -6356,24 +6359,21 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", @@ -6381,7 +6381,6 @@ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -6393,8 +6392,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", @@ -6402,7 +6400,6 @@ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -6416,7 +6413,6 @@ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -6427,7 +6423,6 @@ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -6437,8 +6432,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", @@ -6446,7 +6440,6 @@ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -6464,7 +6457,6 @@ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -6479,7 +6471,6 @@ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -6493,7 +6484,6 @@ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -6509,7 +6499,6 @@ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -6520,21 +6509,19 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@zip.js/zip.js": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.7.tgz", - "integrity": "sha512-8daf29EMM3gUpH/vSBSCYo2bY/wbamgRPxPpE2b+cDnbOLBHAcZikWad79R4Guemth/qtipzEHrZMq1lFXxWIA==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.8.tgz", + "integrity": "sha512-v0KutehhSAuaoFAFGLp+V4+UiZ1mIxQ8vNOYMD7k9ZJaBbtQV49MYlg568oRLiuwWDg2Di58Iw3Q0ESNWR+5JA==", "license": "BSD-3-Clause", "engines": { "bun": ">=0.7.0", @@ -6548,6 +6535,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6608,6 +6596,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7420,6 +7409,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -7559,9 +7549,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001706", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", - "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", "dev": true, "funding": [ { @@ -7599,9 +7589,9 @@ "license": "Apache-2.0" }, "node_modules/cesium": { - "version": "1.134.0", - "resolved": "https://registry.npmjs.org/cesium/-/cesium-1.134.0.tgz", - "integrity": "sha512-Ok7cvo+VuXo85tFRobJw3LaL5BfuSANt2MGNHOim/tNUR5apZik0ho1O7TRaOkjbfhOywPfVF12sVN1pQjHIVQ==", + "version": "1.135.0", + "resolved": "https://registry.npmjs.org/cesium/-/cesium-1.135.0.tgz", + "integrity": "sha512-U0OuU9pim7ezTns0OVVw199NdMhJzuZWKRKRwQdRbBQH74Q8pmU0FLpQ/Xv94pzvCqJxxqdtziM2JGHa43xzcg==", "license": "Apache-2.0", "workspaces": [ "packages/engine", @@ -7609,8 +7599,8 @@ "packages/sandcastle" ], "dependencies": { - "@cesium/engine": "^21.0.0", - "@cesium/widgets": "^13.2.0" + "@cesium/engine": "^22.0.0", + "@cesium/widgets": "^14.0.0" }, "engines": { "node": ">=20.19.0" @@ -7798,7 +7788,6 @@ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -8337,6 +8326,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@cypress/request": "^3.0.8", "@cypress/xvfb": "^1.2.4", @@ -8987,9 +8977,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -9140,6 +9130,7 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -9258,8 +9249,7 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -9296,6 +9286,7 @@ "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -9392,6 +9383,7 @@ "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -9453,6 +9445,7 @@ "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9730,7 +9723,6 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -10644,8 +10636,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/global-dirs": { "version": "3.0.1", @@ -11803,7 +11794,6 @@ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -12214,7 +12204,6 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" } @@ -12608,6 +12597,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12835,8 +12825,7 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/no-case": { "version": "3.0.4", @@ -13470,6 +13459,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -13723,6 +13713,7 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14589,6 +14580,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz", "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -15653,7 +15645,6 @@ "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -15673,7 +15664,6 @@ "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -15727,7 +15717,6 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -15740,8 +15729,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "4.3.0", @@ -15749,7 +15737,6 @@ "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -15769,8 +15756,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/text-decoder": { "version": "1.2.3", @@ -16101,6 +16087,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16423,6 +16410,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -16605,7 +16593,6 @@ "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -16699,7 +16686,6 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -16728,7 +16714,6 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -16742,7 +16727,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -16757,7 +16741,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -16767,8 +16750,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.0", @@ -16776,7 +16758,6 @@ "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/ui/package.json b/ui/package.json index 5e994bdce..1c9d9efd1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -35,7 +35,7 @@ "@lit/context": "^1.1.3", "@lit/task": "^1.0.1", "@swissgeol/ui-core": "1.6.0-dev7", - "cesium": "1.134.0", + "cesium": "1.135.0", "d3-array": "3.2.4", "d3-axis": "3.0.0", "d3-scale": "4.0.2", diff --git a/ui/src/cesium/S3Resource.ts b/ui/src/cesium/S3Resource.ts index e2510aa71..de2cfc5b0 100644 --- a/ui/src/cesium/S3Resource.ts +++ b/ui/src/cesium/S3Resource.ts @@ -35,7 +35,6 @@ export default class S3Resource extends Resource { } return this.getSignedUrl(credentials).then((url) => { this.url = url; - console.log(url); return (Resource.prototype as any)._makeRequest.call(this, options); }); } diff --git a/ui/src/draw/CesiumDraw.ts b/ui/src/draw/CesiumDraw.ts index 100674e9c..f3f884478 100644 --- a/ui/src/draw/CesiumDraw.ts +++ b/ui/src/draw/CesiumDraw.ts @@ -25,6 +25,7 @@ import { } from '../cesiumutils'; import type { GeometryTypes } from '../toolbox/interfaces'; import { cartesianToLv95 } from '../projection'; +import { PickService } from 'src/services/pick.service'; type PointOptions = { color?: Color; @@ -512,7 +513,7 @@ export class CesiumDraw extends EventTarget { onLeftClick(event) { this.renderSceneIfTranslucent(); if (!event?.position) return; - const pickedPosition = this.viewer_.scene.pickPosition(event.position); + const pickedPosition = PickService.get().pick(event.position); if (pickedPosition == null) { return; } @@ -657,7 +658,7 @@ export class CesiumDraw extends EventTarget { onMouseMove_(event) { this.renderSceneIfTranslucent(); if (!event?.endPosition) return; - const pickedPosition = this.viewer_.scene.pickPosition(event.endPosition); + const pickedPosition = PickService.get().pick(event.endPosition); if (!pickedPosition) return; const position = Cartesian3.clone(pickedPosition); if (this.entityForEdit && !!this.leftPressedPixel_) { diff --git a/ui/src/elements/ngm-coordinate-popup.ts b/ui/src/elements/ngm-coordinate-popup.ts index 8739b2550..92c3b5307 100644 --- a/ui/src/elements/ngm-coordinate-popup.ts +++ b/ui/src/elements/ngm-coordinate-popup.ts @@ -10,6 +10,7 @@ import { import MainStore from '../store/main'; import { formatCartographicAs2DLv95, radToDeg } from '../projection'; import i18next from 'i18next'; +import { PickService } from 'src/services/pick.service'; @customElement('ngm-coordinate-popup') export class NgmCoordinatePopup extends LitElementI18n { @@ -39,7 +40,7 @@ export class NgmCoordinatePopup extends LitElementI18n { eventHandler.setInputAction(async (event) => { this.opened = false; - const cartesian = viewer.scene.pickPosition(event.position); + const cartesian = PickService.get().pick(event.position); if (!cartesian) { return; } diff --git a/ui/src/elements/ngm-voxel-filter.ts b/ui/src/elements/ngm-voxel-filter.ts deleted file mode 100644 index 3e25e1e4a..000000000 --- a/ui/src/elements/ngm-voxel-filter.ts +++ /dev/null @@ -1,343 +0,0 @@ -import i18next from 'i18next'; -import { html } from 'lit'; -import { customElement, property, query, queryAll } from 'lit/decorators.js'; -import { LitElementI18n } from '../i18n'; -import draggable from './draggable'; -import { dragArea } from './helperElements'; -import { - createLithologyIncludeUniform, - getVoxelShader, -} from '../layers/voxels-helper'; -import { repeat } from 'lit/directives/repeat.js'; -import { TextureUniform, Viewer } from 'cesium'; -import { LayerConfig, LithologyVoxelFilter } from '../layertree'; -import { PropertyValues } from '@lit/reactive-element'; - -@customElement('ngm-voxel-filter') -export class NgmVoxelFilter extends LitElementI18n { - @property({ type: Object }) - accessor config: LayerConfig | undefined; - - @property({ type: Object }) - accessor viewer!: Viewer; - - @query('.min-conductivity') - accessor minConductivityInput!: HTMLInputElement; - - @query('.max-conductivity') - accessor maxConductivityInput!: HTMLInputElement; - - @query('.vox_filter_include_undefined') - accessor includeUndefinedConductivity: HTMLInputElement | null = null; - - @queryAll('.lithology-checkbox input[type="checkbox"]') - accessor lithologyCheckbox!: NodeListOf; - - private minConductivity = NaN; - private maxConductivity = NaN; - - private minConductivityValue = NaN; - private maxConductivityValue = NaN; - - private isLithologyActiveByIndex: boolean[] = []; - - shouldUpdate(): boolean { - return this.config !== undefined; - } - - update(changedProperties: PropertyValues): void { - this.classList.toggle('is-klasse', this.isKlasse); - - if (changedProperties.has('config') && this.config !== undefined) { - this.initializeFromShader(); - } - - super.update(changedProperties); - } - - private initializeFromShader(): void { - const shader = getVoxelShader(this.config); - - this.minConductivity = shader.uniforms['u_filter_conductivity_min'] - .value as number; - - this.maxConductivity = shader.uniforms['u_filter_conductivity_max'] - .value as number; - - const lithologyUniform = shader.uniforms['u_filter_selected_lithology'] - .value as TextureUniform; - this.isLithologyActiveByIndex = [ - ...(lithologyUniform as { typedArray: Uint8Array }).typedArray, - ].map((value) => value === 1); - - if (this.includeUndefinedConductivity !== null) { - this.includeUndefinedConductivity.checked = shader.uniforms[ - 'u_filter_include_undefined_conductivity' - ].value as boolean; - } - - const operator = shader.uniforms['u_filter_operator'].value as number; - const operatorInputs = this.querySelectorAll( - 'input[name="operator"]', - ); - for (let i = 0; i < operatorInputs.length; i++) { - const input = operatorInputs[i]; - input.checked = i === operator; - } - } - - willUpdate() { - if (!this.config) return; - this.minConductivityValue = this.minConductivity = - this.config.voxelFilter!.conductivityRange[0]; - this.maxConductivityValue = this.maxConductivity = - this.config.voxelFilter!.conductivityRange[1]; - - this.hidden = false; - } - - minConductivityChanged(evt) { - this.minConductivity = parseFloat(evt.target.value); - this.maxConductivityInput.min = this.minConductivity.toString(); - } - - maxConductivityChanged(evt) { - this.maxConductivity = parseFloat(evt.target.value); - this.minConductivityInput.max = this.maxConductivity.toString(); - } - - close() { - this.hidden = true; - this.isLithologyActiveByIndex = []; - this.resetForm(); - - this.config = undefined; - } - - applyFilter() { - this.updateUniform('u_filter_conductivity_min', this.minConductivity); - this.updateUniform('u_filter_conductivity_max', this.maxConductivity); - - const lithologyInclude: number[] = []; - this.lithologyCheckbox.forEach((checkbox) => - lithologyInclude.push(checkbox.checked ? 1 : 0), - ); - - const lithologyUniform = createLithologyIncludeUniform(lithologyInclude); - - this.updateUniform('u_filter_selected_lithology', lithologyUniform); - - const operator = this.querySelector( - 'input[name="operator"]:checked', - ); - const value = operator ? parseInt(operator.value, 10) : 0; - this.updateUniform('u_filter_operator', value); - this.updateUniform( - 'u_filter_include_undefined_conductivity', - this.includeUndefinedConductivity?.checked ?? true, - ); - - this.viewer.scene.requestRender(); - } - - private updateUniform( - name: string, - value: boolean | number | TextureUniform, - ): void { - const shader = getVoxelShader(this.config); - // Update the uniform on the GPU. - shader.setUniform(name, value); - - // Update the cached value so we can use it to retrieve the uniform state later on. - shader.uniforms[name].value = value; - } - - resetForm() { - this.querySelectorAll('.content-container form').forEach( - (form) => form.reset(), - ); - if (this.includeUndefinedConductivity) { - this.includeUndefinedConductivity.checked = true; - } - } - - firstUpdated() { - draggable(this, { - allowFrom: '.drag-handle', - }); - } - - createRenderRoot() { - // no shadow dom - return this; - } - - private get isKlasse(): boolean { - return this.config!.voxelDataName === 'Klasse'; - } - - render() { - const { isKlasse } = this; - return html` -
- ${i18next.t('vox_filter_filtering_on')} ${i18next.t(this.config!.label)} -
this.close()}>
-
-
- ${isKlasse ? '' : this.renderRangeFilters()} - ${isKlasse ? '' : this.renderLogicalOperators()} - ${this.renderLayerFilters({ isKlasse })} -
- -
-
- ${dragArea} - `; - } - - private renderRangeFilters() { - return html`
-
- ${i18next.t('vox_filter_hydraulic_conductivity')} -
-
-
- - -
-
- - -
-
-
- -
-
`; - } - - private renderLogicalOperators() { - return html`
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
`; - } - - private renderLayerFilters({ isKlasse }: { isKlasse: boolean }) { - const hideCheckboxColor = - this.config!.voxelDataName !== 'Index' && !isKlasse; - return html` -
-
- - -
-
- ${isKlasse - ? i18next.t('vox_filter_klasse') - : i18next.t('vox_filter_lithology')} -
- ${'lithology' in this.config!.voxelFilter! - ? repeat( - (this.config!.voxelFilter as LithologyVoxelFilter).lithology, - (lithology) => lithology, - (lithology, index: number) => - html` `, - ) - : ''} -
- `; - } -} diff --git a/ui/src/elements/ngm-voxel-simple-filter.ts b/ui/src/elements/ngm-voxel-simple-filter.ts deleted file mode 100644 index 4042377da..000000000 --- a/ui/src/elements/ngm-voxel-simple-filter.ts +++ /dev/null @@ -1,138 +0,0 @@ -import i18next from 'i18next'; -import { html } from 'lit'; -import { customElement, property, query } from 'lit/decorators.js'; -import { LitElementI18n } from '../i18n'; -import draggable from './draggable'; -import { dragArea } from './helperElements'; -import { getVoxelShader } from '../layers/voxels-helper'; -import type { Viewer } from 'cesium'; -import { LayerConfig } from '../layertree'; - -@customElement('ngm-voxel-simple-filter') -export class NgmVoxelSimpleFilter extends LitElementI18n { - @property({ type: Object }) - accessor config: LayerConfig | undefined; - @property({ type: Object }) - accessor viewer!: Viewer; - - @query('.min-conductivity') - accessor minValueInput!: HTMLInputElement; - @query('.max-conductivity') - accessor maxValueInput!: HTMLInputElement; - - private minValue = NaN; - private maxValue = NaN; - - private minInitialValue = NaN; - private maxInitialValue = NaN; - - shouldUpdate(): boolean { - return this.config !== undefined; - } - - willUpdate() { - if (this.config) { - this.minInitialValue = this.minValue = this.config.voxelColors!.range[0]; - this.maxInitialValue = this.maxValue = this.config.voxelColors!.range[1]; - } - - this.hidden = false; - } - - minValueChanged(evt) { - this.minValue = parseFloat(evt.target.value); - this.maxValueInput.min = this.minValue.toString(); - } - - maxValueChanged(evt) { - this.maxValue = parseFloat(evt.target.value); - this.minValueInput.max = this.maxValue.toString(); - } - - close() { - this.hidden = true; - this.resetShader(); - this.resetForm(); - - this.config = undefined; - } - - applyFilter() { - const shader = getVoxelShader(this.config); - shader.setUniform('u_filter_min', this.minValue); - shader.setUniform('u_filter_max', this.maxValue); - - this.viewer.scene.requestRender(); - } - - resetShader() { - const shader = getVoxelShader(this.config); - shader.setUniform('u_filter_min', this.minInitialValue); - shader.setUniform('u_filter_max', this.maxInitialValue); - this.viewer.scene.requestRender(); - } - - resetForm() { - this.querySelectorAll('.content-container form').forEach( - (form) => form.reset(), - ); - } - - firstUpdated() { - draggable(this, { - allowFrom: '.drag-handle', - }); - } - - createRenderRoot() { - // no shadow dom - return this; - } - - render() { - if (!this.config) return; - return html` -
- ${i18next.t('vox_filter_filtering_on')} ${i18next.t(this.config.label)} -
this.close()}>
-
-
-
-
${this.config.voxelColors!.label}
-
-
- - -
-
- - -
-
-
-
- -
-
- ${dragArea} - `; - } -} diff --git a/ui/src/features/catalog/catalog.module.ts b/ui/src/features/catalog/catalog.module.ts index b26e28cbf..dc743750b 100644 --- a/ui/src/features/catalog/catalog.module.ts +++ b/ui/src/features/catalog/catalog.module.ts @@ -1,7 +1,8 @@ -import './display/catalog-display-legend.element'; import './display/catalog-display-list.element'; import './display/catalog-display-list-item.element'; -import './display/catalog-display-times.element'; +import './display/details/catalog-display-legend-detail.element'; +import './display/details/catalog-display-times-detail.element'; +import './display/details/catalog-display-voxel-filter-detail.element'; import './tree/catalog-tree.element'; import './tree/catalog-tree-group.element'; diff --git a/ui/src/features/catalog/display/catalog-display-list-item.element.ts b/ui/src/features/catalog/display/catalog-display-list-item.element.ts index 860a08c6a..4a441518c 100644 --- a/ui/src/features/catalog/display/catalog-display-list-item.element.ts +++ b/ui/src/features/catalog/display/catalog-display-list-item.element.ts @@ -41,27 +41,19 @@ export class CatalogDisplayList extends CoreElement { @state() accessor isBackgroundActive = false; - private readonly windows = { - legend: null as CoreWindow | null, - times: null as CoreWindow | null, - tiffFilter: null as CoreWindow | null, - } satisfies Record; + private windows!: WindowMapping; connectedCallback() { super.connectedCallback(); + this.windows = getWindowsOfLayer(this.layerId); + this.register( this.layerService.layer$(this.layerId).subscribe((layer) => { this.setAttribute('visible', `${layer.isVisible}`); this.layer = layer; }), ); - - this.register(() => { - for (const window of Object.values(this.windows)) { - window?.close(); - } - }); } updated() { @@ -104,18 +96,6 @@ export class CatalogDisplayList extends CoreElement { this.layerService.deactivate(this.layer.id as Id); }; - private readonly openVoxelFilter = (): void => { - this.dispatchEvent( - new CustomEvent('showVoxelFilter', { - composed: true, - bubbles: true, - detail: { - config: this.layer, - }, - }), - ); - }; - private openWindow( name: keyof typeof this.windows, options: Omit, @@ -135,7 +115,7 @@ export class CatalogDisplayList extends CoreElement { this.openWindow('legend', { title: () => getLayerLabel(this.layer), body: () => html` - `, @@ -145,12 +125,22 @@ export class CatalogDisplayList extends CoreElement { this.openWindow('times', { title: () => getLayerLabel(this.layer), body: () => html` - `, }); + private readonly openVoxelFilter = (): void => + this.openWindow('voxelFilter', { + title: () => getLayerLabel(this.layer), + body: () => html` + + `, + }); + private readonly openTiffFilter = (): void => this.openWindow('tiffFilter', { title: () => getLayerLabel(this.layer), @@ -494,3 +484,48 @@ export class CatalogDisplayList extends CoreElement { } `; } + +type WindowName = 'legend' | 'times' | 'voxelFilter' | 'tiffFilter'; + +type WindowMapping = Record; + +/** + * A collection of all currently open windows, mapped to the layer to which they belong. + * + * This is kept outside the layer's component itself, + * which enables us to close the catalog itself, while keeping the windows open. + * + * Cleanup is done when a layer is deactivated. + */ +const windowMappingsByLayerId = new Map, WindowMapping>(); + +const getWindowsOfLayer = (layerId: Id): WindowMapping => { + const mapping = windowMappingsByLayerId.get(layerId); + if (mapping !== undefined) { + return mapping; + } + const newMapping: WindowMapping = { + legend: null, + tiffFilter: null, + times: null, + voxelFilter: null, + }; + windowMappingsByLayerId.set(layerId, newMapping); + return newMapping; +}; + +LayerService.inject().then((layerService) => { + // Close the windows of any deactivated layer. + layerService.layerDeactivated$.subscribe((layerId) => { + const mapping = windowMappingsByLayerId.get(layerId); + if (mapping === undefined) { + return; + } + for (const window of Object.values(mapping)) { + if (window !== null) { + window.close(); + } + } + windowMappingsByLayerId.delete(layerId); + }); +}); diff --git a/ui/src/features/catalog/display/catalog-display-legend.element.ts b/ui/src/features/catalog/display/details/catalog-display-legend-detail.element.ts similarity index 98% rename from ui/src/features/catalog/display/catalog-display-legend.element.ts rename to ui/src/features/catalog/display/details/catalog-display-legend-detail.element.ts index 40eb0f2b2..668db1ee9 100644 --- a/ui/src/features/catalog/display/catalog-display-legend.element.ts +++ b/ui/src/features/catalog/display/details/catalog-display-legend-detail.element.ts @@ -12,7 +12,7 @@ import { Id } from 'src/models/id.model'; import { LayerService } from 'src/features/layer/new/layer.service'; import { consume } from '@lit/context'; -@customElement('ngm-catalog-display-legend') +@customElement('ngm-catalog-display-legend-detail') export class CatalogDisplayLegend extends CoreElement { @property() accessor layerId!: Id; diff --git a/ui/src/features/catalog/display/catalog-display-times.element.ts b/ui/src/features/catalog/display/details/catalog-display-times-detail.element.ts similarity index 97% rename from ui/src/features/catalog/display/catalog-display-times.element.ts rename to ui/src/features/catalog/display/details/catalog-display-times-detail.element.ts index 2d47b1aa1..26a5adb5c 100644 --- a/ui/src/features/catalog/display/catalog-display-times.element.ts +++ b/ui/src/features/catalog/display/details/catalog-display-times-detail.element.ts @@ -9,7 +9,7 @@ import { consume } from '@lit/context'; import { LayerService } from 'src/features/layer/new/layer.service'; import { Id } from 'src/models/id.model'; -@customElement('ngm-catalog-display-times') +@customElement('ngm-catalog-display-times-detail') export class CatalogDisplayTimes extends CoreElement { @property() accessor layerId!: Id; diff --git a/ui/src/features/catalog/display/details/catalog-display-voxel-filter-detail.element.ts b/ui/src/features/catalog/display/details/catalog-display-voxel-filter-detail.element.ts new file mode 100644 index 000000000..4a6cb8dc8 --- /dev/null +++ b/ui/src/features/catalog/display/details/catalog-display-voxel-filter-detail.element.ts @@ -0,0 +1,374 @@ +import { css, html, unsafeCSS } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { CoreElement } from 'src/features/core'; +import { + FilterOperator, + getLayerAttributeName, + VoxelItemMapping, + VoxelLayer, + VoxelLayerMapping, + VoxelLayerMappingType, + VoxelRangeMapping, +} from 'src/features/layer'; +import { repeat } from 'lit/directives/repeat.js'; +import { consume } from '@lit/context'; +import { LayerService } from 'src/features/layer/new/layer.service'; +import { Id } from 'src/models/id.model'; +import i18next from 'i18next'; +import { applyTypography } from 'src/styles/theme'; +import { when } from 'lit/directives/when.js'; +import { run } from 'src/utils/fn.utils'; + +import fomanticInputCss from 'fomantic-ui-css/components/input.css?raw'; +import fomanticFormCss from 'fomantic-ui-css/components/form.css?raw'; +import { live } from 'lit/directives/live.js'; + +@customElement('ngm-catalog-display-voxel-filter-detail') +export class CatalogDisplayTimes extends CoreElement { + @property() + accessor layerId!: Id; + + @consume({ context: LayerService.context() }) + accessor layerService!: LayerService; + + @state() + accessor layer!: VoxelLayer; + + connectedCallback(): void { + super.connectedCallback(); + + this.register( + this.layerService.layer$(this.layerId).subscribe((layer) => { + this.layer = layer; + }), + ); + } + + private readonly updateOperator = (newOperator: FilterOperator): void => { + this.layerService.update(this.layerId, { + filterOperator: newOperator, + }); + }; + + private readonly toggleMappingItem = ( + index: number, + itemIndex: number, + ): void => { + this.layerService.update(this.layerId, (layer) => { + const mappings = [...layer.mappings]; + const mapping = mappings[index] as VoxelItemMapping; + const items = [...mapping.items]; + const item = items[itemIndex]; + items[itemIndex] = { + ...item, + isEnabled: !item.isEnabled, + }; + mappings[index] = { + ...mapping, + items, + }; + return { + mappings, + }; + }); + }; + + private readonly setAllMappingItems = ( + index: number, + isEnabled: boolean, + ): void => { + this.layerService.update(this.layerId, (layer) => { + const mappings = [...layer.mappings]; + const mapping = mappings[index] as VoxelItemMapping; + const items = mapping.items.map((item) => ({ ...item, isEnabled })); + mappings[index] = { + ...mapping, + items, + }; + return { + mappings, + }; + }); + }; + + private readonly updateRangeMapping = ( + index: number, + range: [number | null, number | null], + ): void => { + this.layerService.update(this.layerId, (layer) => { + const mappings = [...layer.mappings]; + const mapping = mappings[index] as VoxelRangeMapping; + mappings[index] = { + ...mapping, + enabledRange: [ + range[0] ?? mapping.range[0], + range[1] ?? mapping.range[1], + ], + }; + return { + mappings, + }; + }); + }; + + private readonly toggleUndefinedAlwaysEnabled = (index: number): void => { + this.layerService.update(this.layerId, (layer) => { + const mappings = [...layer.mappings]; + const mapping = mappings[index] as VoxelRangeMapping; + mappings[index] = { + ...mapping, + isUndefinedAlwaysEnabled: mapping.isUndefinedAlwaysEnabled, + }; + return { + mappings, + }; + }); + }; + + readonly render = () => html` +
    + ${repeat( + this.layer.mappings, + (mapping) => mapping.key, + this.renderMapping, + )} +
+ `; + + private readonly renderMapping = ( + mapping: VoxelLayerMapping, + index: number, + ) => { + const operator = when(index === 1, () => this.renderOperators()); + const filter = run(() => { + switch (mapping.type) { + case VoxelLayerMappingType.Item: + return this.renderItemMapping(mapping, index); + case VoxelLayerMappingType.Range: + return this.renderRangeMapping(mapping, index); + } + }); + return html`${operator} ${filter}`; + }; + + private readonly renderItemMapping = ( + mapping: VoxelItemMapping, + index: number, + ) => html` +
  • +

    ${getLayerAttributeName(this.layer, mapping.key)}

    + +
    + + ${i18next.t('catalog:voxelFilterWindow.items.select_all')} + + + ${i18next.t('catalog:voxelFilterWindow.items.deselect_all')} + +
    + +
      + ${repeat( + mapping.items, + (item) => item.value, + (item, i) => { + const label = i18next.t(item.label); + return html` + + `; + }, + )} +
    +
  • + `; + + private readonly renderRangeMapping = ( + mapping: VoxelRangeMapping, + index: number, + ) => html` +
  • +

    ${getLayerAttributeName(this.layer, mapping.key)}

    +
    + + +
    +
    + +
    +
  • + `; + + private readonly renderOperators = () => html` +
  • + ${repeat( + Object.values(FilterOperator), + (operator) => html` + + ${i18next.t(`catalog:voxelFilterWindow.operators.${operator}`)} + + `, + )} +
  • + `; + + static readonly styles = css` + ${unsafeCSS(fomanticInputCss)} + ${unsafeCSS(fomanticFormCss)} + + :host, + :host * { + box-sizing: border-box; + } + + :host { + width: 520px; + } + + :host > ul { + list-style: none; + margin: 0; + padding: 0; + } + + :host > ul > li { + padding-block: 10px; + + &:not(:first-child) { + border-top: 1px solid var(--sgc-color-border--default); + } + + &:first-child { + padding-top: 0; + } + &:last-child { + padding-bottom: 0; + } + } + + h3 { + ${applyTypography('modal-title-2')}; + margin: 0 0 5px 0; + } + + label.field { + ${applyTypography('body-2-bold')}; + } + + label:has(sgc-checkbox) { + cursor: pointer; + } + + label.is-inline { + display: flex; + gap: 5px; + + .text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .filter.is-items { + display: flex; + flex-direction: column; + gap: 5px; + } + + .filter.is-items .controls { + display: flex; + gap: 5px; + + & > sgc-button { + width: 100%; + } + } + + .filter.is-items ul { + display: flex; + flex-direction: column; + gap: 5px; + + list-style: none; + padding: 0; + margin: 0; + + max-height: 300px; + overflow-y: auto; + } + + .filter.is-items .color { + width: 20px; + min-width: 20px; + height: 20px; + border-radius: 10px; + background-color: var(--color); + } + + sgc-checkbox { + min-width: 20px; + } + + .operators { + display: flex; + justify-content: center; + gap: 20px; + } + `; +} diff --git a/ui/src/features/controls/controls/control2d.controller.ts b/ui/src/features/controls/controls/control2d.controller.ts index 698e7fa2c..f2c203320 100644 --- a/ui/src/features/controls/controls/control2d.controller.ts +++ b/ui/src/features/controls/controls/control2d.controller.ts @@ -10,6 +10,7 @@ import { Viewer, } from 'cesium'; import { DEFAULT_VIEW } from 'src/constants'; +import { PickService } from 'src/services/pick.service'; /** * `Control2D` contains the ability to toggle the viewer in and out of the 2D mode. @@ -59,11 +60,11 @@ export class Control2dController { this.viewer.canvas.clientWidth / 2, this.viewer.canvas.clientHeight / 2, ); - const position = this.scene.pickPosition(center); + const position = PickService.get().pick(center); // If we can't find the center position, then the camera is looking at the sky. // In that case, we simply switch to the default 2d view. - if (position === undefined) { + if (position === null) { this.viewer.camera.flyTo({ duration: 2, ...DEFAULT_VIEW, diff --git a/ui/src/features/core/core-radio.element.ts b/ui/src/features/core/core-radio.element.ts index c57d0fdeb..87ea16b72 100644 --- a/ui/src/features/core/core-radio.element.ts +++ b/ui/src/features/core/core-radio.element.ts @@ -1,5 +1,6 @@ import { css, html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; +import { live } from 'lit/directives/live.js'; import { applyTransition, applyTypography } from 'src/styles/theme'; @customElement('ngm-core-radio') @@ -7,6 +8,9 @@ export class CoreRadio extends LitElement { @property({ type: Boolean }) accessor isActive: boolean = false; + @property({ type: String }) + accessor name: string | null = null; + @property({ type: Boolean, attribute: 'disabled', reflect: true }) accessor isDisabled: boolean = false; @@ -32,7 +36,11 @@ export class CoreRadio extends LitElement { readonly render = () => html`