Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# The dependency graph has a pre-existing peer conflict (mapbox-gl vs. other
# mapbox/maplibre consumers). Fresh `npm install` requires legacy peer
# resolution; `npm ci` reads the lockfile directly and is unaffected.
legacy-peer-deps=true
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import {

import { inject } from "../../../../../core/injectables";

import {
getHiddenFieldsForEngine,
stripHiddenFields,
} from "./layerFieldVisibility";

import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
Expand Down Expand Up @@ -195,52 +200,6 @@ const LAYER_TYPE_CONFIGS = {
MVTLayer: vectortileConfig,
};

/**
* Config fields to hide per engine and layer type.
* Structure: ENGINE_HIDDEN_FIELDS[engine][layerType] = Set of field paths.
* Use "_all" to hide a field for every layer type under that engine.
*
* Field paths match the `field` key in the metaconfig JSON files.
* Add entries here when a field is not meaningful for a given engine/type combo.
*/
const ENGINE_HIDDEN_FIELDS = {
deckgl: {
_all: new Set([
"variables.markerIcon.shadowUrl",
"variables.markerIcon.shadowSize.0",
"variables.markerIcon.shadowSize.1",
"variables.markerIcon.shadowAnchor.0",
"variables.markerIcon.shadowAnchor.1",
]),
vector: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
GeoJsonLayer: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
ScatterplotLayer: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
tile: new Set([]),
TileLayer: new Set([]),
BitmapLayer: new Set([]),
Tile3DLayer: new Set([]),
PointCloudLayer: new Set([]),
MVTLayer: new Set([]),
},
};

const MODAL_NAME = "layer";
const LayerModal = (props) => {
Expand All @@ -267,19 +226,10 @@ const LayerModal = (props) => {
...ENGINE_LAYER_SUPPORT[mapEngine],
...(ENGINE_STRUCTURAL_LAYER_TYPES[mapEngine] ?? []),
];
const engineFields = ENGINE_HIDDEN_FIELDS[mapEngine] ?? {};
const hiddenFields = new Set([
...(engineFields._all ?? []),
...(engineFields[layer.type] ?? []),
]);

config = JSON.parse(JSON.stringify(config));
config.tabs.forEach((tab) => {
tab.rows.forEach((row) => {
row.components = row.components.filter(
(comp) => !comp.field || !hiddenFields.has(comp.field)
);

row.components.forEach((comp) => {
if (comp.field === "type" && comp.type === "dropdown") {
const filtered = comp.options.filter((opt) =>
Expand All @@ -292,13 +242,12 @@ const LayerModal = (props) => {
}
});
});

tab.rows = tab.rows.filter((row) => row.components.length > 0);
});

config.tabs = config.tabs.filter((tab) => tab.rows.length > 0);
}

// Hide engine-specific or deck-only fields for all engines (including leaflet).
config = stripHiddenFields(config, getHiddenFieldsForEngine(mapEngine, layer.type));

const handleClose = (skipSetConfiguration) => {
if (skipSetConfiguration !== true) {
const nextConfiguration = JSON.parse(JSON.stringify(configuration));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Engine-aware field visibility for the layer config modal.
// - Non-leaflet engines hide engine-unsupported fields (ENGINE_HIDDEN_FIELDS).
// - Leaflet hides deck-only fields (DECK_ONLY_FIELDS), e.g. the client-side
// COG renderer option, which must be ABSENT (not just disabled) in Leaflet.

// Moved verbatim from LayerModal.js (keep existing entries).
export const ENGINE_HIDDEN_FIELDS = {
deckgl: {
_all: new Set([
"variables.markerIcon.shadowUrl",
"variables.markerIcon.shadowSize.0",
"variables.markerIcon.shadowSize.1",
"variables.markerIcon.shadowAnchor.0",
"variables.markerIcon.shadowAnchor.1",
]),
vector: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
GeoJsonLayer: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
ScatterplotLayer: new Set([
"shape",
"style.shapeIcon",
"style.shapeProp",
"style.shapeRotationOffset",
"style.animation",
]),
tile: new Set([]),
TileLayer: new Set([]),
BitmapLayer: new Set([]),
Tile3DLayer: new Set([]),
PointCloudLayer: new Set([]),
MVTLayer: new Set([]),
},
}

// Fields that only make sense for deck.gl missions and must be ABSENT
// (not just disabled) when the mission engine is leaflet.
export const DECK_ONLY_FIELDS = {
_all: ['cogRendererMode'],
}

function unionForType(map, layerType) {
if (!map) return new Set()
return new Set([...(map._all ?? []), ...(map[layerType] ?? [])])
}

export function getHiddenFieldsForEngine(mapEngine, layerType) {
if (mapEngine === 'leaflet') return unionForType(DECK_ONLY_FIELDS, layerType)
return unionForType(ENGINE_HIDDEN_FIELDS[mapEngine], layerType)
}

export function stripHiddenFields(config, hidden) {
if (!hidden || hidden.size === 0 || !config.tabs) return config
const cloned = JSON.parse(JSON.stringify(config))
cloned.tabs.forEach((tab) => {
tab.rows.forEach((row) => {
row.components = row.components.filter(
(comp) => !comp.field || !hidden.has(comp.field)
)
})
tab.rows = tab.rows.filter((row) => row.components.length > 0)
})
cloned.tabs = cloned.tabs.filter((tab) => tab.rows.length > 0)
return cloned
}
14 changes: 14 additions & 0 deletions configure/src/metaconfigs/layer-tile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,20 @@
}
]
},
{
"name": "COG Renderer",
"components": [
{
"field": "cogRendererMode",
"name": "COG Renderer",
"description": "How a Cloud-Optimized GeoTIFF is rendered. 'titiler' uses the tile server (default). 'deckRaster' renders the COG directly in the browser on the GPU with no tile server — deck.gl missions only, single-band colormap + rescale.",
"type": "dropdown",
"width": 4,
"options": ["titiler", "deckRaster"],
"default": "titiler"
}
]
},
{
"name": "Single Data Band COG Transformation",
"components": [
Expand Down
Loading