Skip to content

Commit

Permalink
Add image layer type to support GeoTIFF and Cloud Optimized GeoTIFF i…
Browse files Browse the repository at this point in the history
…mages (#636)

* Add Image layer type

* Add image layer

* Add georaster-layer-for-leaflet package

* Try different package

* Add opacity setting for geotiff image layer

* Try georaster-layer-for-leaflet v4.1.1-0

* Use correct georaster library version

* Do not fetch entire COG file before loading

* Fix COG layer ordering

* Add variable to hide all values where there is no data

* Add js-colormaps library

* Working color ramp for single band 8bit images

* Add fillMinMax for single band 8 bit images

* Support color ramps for single band images

* Leaflet v1.5.1 patched with PR 6522

Leaflet/Leaflet#6522

* Fix opacity issues when zooming

* Export colormap data variable

* Add symlink to js-colormaps library

* Add color ramp selection dropdown for image layers

* Clear georaster layer cache before updating colors

* Working on updating image vars to match tile vars

* Add helper script to generate list of colormaps from TiTiler and js-colormaps

* Add updated js-colormaps to include colormaps from TiTiler

* Load TiTler or js-colormap colormaps depending on TiTiler availability

* Clear geotiff cache when toggling layer visibility

* Remove extraneous import

* Add image layer type to IdentifierTool

* Use minmax of image from gdalinfo if user did not input in layer settings

* Fix getUrl

* Add extra checks

* Add docs for Image layer

* Add missing elements in Image configuration

* Fix issue with loading COG scale text in LayerTool

* Make id for titiler colomap images more specific

* Fix colormap dropdown to include velocity layer's DEFAULT colormap value

* Make legends for Tile COG and Image layers load upon start

* Supress js-colormap alerts and print to console insteaad

* Add legend for velocity layer

* Add constant and todo note

* Use latest packages

* Clean up code

* Update generated files

* Check to if layer has cogTransform parameter before updating

* Fix loading of image layers

* Fix conditions for fillMinMax

* Show correct image layer if reordering layers in UI

* Do not trim whitespace for layer name and units

* Undo Leaflet v1.5.1 patched with PR 6522 commit

* Only display cogTransform options on single band images

* Update docs

* Use TiTiler colormaps when appropriate

* Show config dropdown colorramp using js-colormaps library if TiTiler is not available

* Use RDYLBU_R as default color instead of DEFAULT for velocity layers

* Copy js-colormaps to avoid symlink

---------

Co-authored-by: Tariq Soliman <[email protected]>
Co-authored-by: tariqksoliman <[email protected]>
  • Loading branch information
3 people authored Mar 4, 2025
1 parent 2e3d82b commit df8e02d
Show file tree
Hide file tree
Showing 41 changed files with 30,389 additions and 18,659 deletions.
2 changes: 1 addition & 1 deletion API/Backend/Config/routes/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ function addLayer(req, res, next, cb, forceConfig, caller = "addLayer") {
mission: "{mission_name}",
layer: {
name: "{new_layer_name}",
type: "header || vector || vectortile || query || model || tile || data",
type: "header || vector || vectortile || query || model || tile || data || image",
"more...": "...",
},
"placement?": {
Expand Down
10 changes: 10 additions & 0 deletions API/Backend/Config/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ const validateLayers = (config) => {
// Check model params (pos, rot, scale)
errs = errs.concat(isValidModelParams(layer));
break;
case "image":
// Check url
errs = errs.concat(isValidUrl(layer));
// Check zooms
errs = errs.concat(isValidZooms(layer));
break;
default:
errs = errs.concat(
err(`Unknown layer type: '${layer.type}'`, ["layers[layer].type"])
Expand Down Expand Up @@ -316,6 +322,10 @@ const fillInMissingFieldsWithDefaults = (layer) => {
layer.style = layer.style || {};
layer.style.className = layer.name.replace(/ /g, "").toLowerCase();
break;
case "image":
layer.style = layer.style || {};
layer.style.className = layer.name.replace(/ /g, "").toLowerCase();
break;
case "model":
break;
default:
Expand Down
19 changes: 19 additions & 0 deletions API/Backend/Utils/routes/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,25 @@ router.post("/getbands", function (req, res) {
);
});

//utils getminmax
router.post("/getminmax", function (req, res) {
const path = encodeURIComponent(req.body.path);
const bands = encodeURIComponent(req.body.bands);

execFile(
"python",
["private/api/gdalinfoMinMax.py", path, bands],
function (error, stdout, stderr) {
if (error) {
logger("warn", error);
res.status(400).send();
} else {
res.send(stdout);
}
}
);
});

//utils ll2aerll
router.post("/ll2aerll", function (req, res) {
const lng = encodeURIComponent(req.body.lng);
Expand Down
1 change: 1 addition & 0 deletions configuration/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function getClientEnvironment(publicUrl) {
THIRD_PARTY_COOKIES: process.env.THIRD_PARTY_COOKIES || "",
SKIP_CLIENT_INITIAL_LOGIN: process.env.SKIP_CLIENT_INITIAL_LOGIN || "",
IS_DOCKER: process.env.IS_DOCKER,
WITH_TITILER: process.env.WITH_TITILER,
}
);
// Stringify all values so we can feed into webpack DefinePlugin
Expand Down
2 changes: 1 addition & 1 deletion configuration/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ module.exports = function (webpackEnv) {
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
test: /\.(js|mjs|cjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve("babel-loader"),
options: {
Expand Down
2 changes: 1 addition & 1 deletion configure/public/toolConfigs.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions configure/src/components/Tabs/Layers/Layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import LanguageIcon from "@mui/icons-material/Language"; // Tile
import GridViewIcon from "@mui/icons-material/GridView"; // Vector tile
import ViewInArIcon from "@mui/icons-material/ViewInAr"; // Model
import AirIcon from "@mui/icons-material/Air"; // Velocity
import ImageIcon from '@mui/icons-material/Image'; // Image
import AddIcon from "@mui/icons-material/Add";

import VisibilityIcon from "@mui/icons-material/Visibility";
Expand Down Expand Up @@ -386,6 +387,9 @@ export default function Layers() {
case "velocity":
iconType = <AirIcon fontSize="small" />;
color = "#24807c";
case "image":
iconType = <ImageIcon fontSize="small" />;
color = "#b0518f";
break;
default:
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import tileConfig from "../../../../../metaconfigs/layer-tile-config.json";
import vectorConfig from "../../../../../metaconfigs/layer-vector-config.json";
import vectortileConfig from "../../../../../metaconfigs/layer-vectortile-config.json";
import velocityConfig from "../../../../../metaconfigs/layer-velocity-config.json";
import imageConfig from "../../../../../metaconfigs/layer-image-config.json";

const useStyles = makeStyles((theme) => ({
Modal: {
Expand Down Expand Up @@ -196,6 +197,10 @@ const LayerModal = (props) => {
config = velocityConfig;
break;

case "image":
config = imageConfig;
break;

default:
break;
}
Expand Down
103 changes: 84 additions & 19 deletions configure/src/core/Maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -721,31 +721,96 @@ const getComponent = (
</FormControl>
);

let domain =
window.mmgisglobal.NODE_ENV === "development"
? "http://localhost:8888/"
: window.mmgisglobal.ROOT_PATH || "";
if (domain.length > 0 && !domain.endsWith("/")) domain += "/";

let colormap_html
if (window.mmgisglobal.WITH_TITILER === "true") {
// Get colors from TiTiler if it is available
colormap_html = (
<div style={{width: "100%"}}>
<img id="titlerCogColormapImage" style={{height: "20px", width: "100%"}} src={`${domain}titiler/colorMaps/${dropdown_value.toLowerCase()}?format=png`} />
</div>
)
} else {
let colormap = dropdown_value
// js-colormaps data object only contains the non reversed color so we need to track if the color is reversed
let reverse = false

// TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps
if (colormap.toLowerCase().endsWith('_r')) {
colormap = colormap.substring(0, colormap.length - 2)
reverse = true
}

let index = Object.keys(colormapData).findIndex(v => {
return v.toLowerCase() === colormap.toLowerCase();
});

if (index > -1) {
colormap = Object.keys(colormapData)[index]
} else {
console.warn(`The colormap '${colormap}' does not exist`);
}

if (colormap in colormapData) {
colormap_html = colormapData[colormap].colors.map(
(hex) => {
return (
<div
className={c.colorDropdownArrayHex}
style={{ background: `rgb(${hex.map(v => {return Math.floor(v * 255)}).join(',')})` }}
></div>
);
}
)

if (reverse === true) {
colormap_html.reverse()
}
} else if (colormap === 'DEFAULT') {
// Default color for velocity layer
const defaultColors = [
'rgb(36,104, 180)',
'rgb(60,157, 194)',
'rgb(128,205,193 )',
'rgb(151,218,168 )',
'rgb(198,231,181)',
'rgb(238,247,217)',
'rgb(255,238,159)',
'rgb(252,217,125)',
'rgb(255,182,100)',
'rgb(252,150,75)',
'rgb(250,112,52)',
'rgb(245,64,32)',
'rgb(237,45,28)',
'rgb(220,24,32)',
'rgb(180,0,35)',
]

colormap_html = defaultColors.map(
(hex) => {
return (
<div
className={c.colorDropdownArrayHex}
style={{ background: `${hex}`}}
></div>
);
}
)
}
}

return (
<div>
{inlineHelp ? (
<>
{inner}
<div className={c.textArrayHexes}>
{typeof dropdown_value === "string"
? colormapData[dropdown_value] &&
colormapData[dropdown_value].colors
? colormapData[dropdown_value].colors.map((hex) => {
return (
<div
className={c.colorDropdownArrayHex}
style={{
background: `rgb(${hex
.map((v) => {
return Math.floor(v * 255);
})
.join(",")})`,
}}
></div>
);
})
: null
: null}
{colormap_html || null}
</div>
<Typography className={c.subtitle2}>
{com.description || ""}
Expand Down
70 changes: 46 additions & 24 deletions configure/src/core/injectables.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { calls } from "./calls";
import { data as colormapData } from '../external/js-colormaps.js'

const injectablesDefaults = {
TILE_MATRIX_SETS: ["WebMercatorQuad"],
COLORMAP_NAMES: ["viridis"],
VELOCITY_COLORMAP_NAMES: ["RDYLBU_R"],
};
// Initialize with reasonable defaults
const injectables = {
TILE_MATRIX_SETS: injectablesDefaults["TILE_MATRIX_SETS"],
COLORMAP_NAMES: injectablesDefaults["COLORMAP_NAMES"],
VELOCITY_COLORMAP_NAMES: injectablesDefaults["VELOCITY_COLORMAP_NAMES"],
};

export const getInjectables = () => {
getTileMatrixSets();
getColormapNames();
getColormapNames("COLORMAP_NAMES");
getColormapNames("VELOCITY_COLORMAP_NAMES");
};

export const inject = (configJson) => {
Expand Down Expand Up @@ -66,43 +70,61 @@ function getTileMatrixSets() {
}
}

function getColormapNames() {
const injectableName = "COLORMAP_NAMES";
function getColormapNames(injectableName) {
if (window.mmgisglobal.WITH_TITILER === "true") {
calls.api(
"titiler_colormapNames",
null,
(res) => {
// Get the intersection of colormaps from js-colormaps and TiTiler
const js_colormaps = Object.keys(colormapData).map((color => color.toLowerCase()));
let colormaps = res.colorMaps;
colormaps = colormaps.filter((color) => {
if (js_colormaps.includes(color.toLowerCase())) {
return color;
}

// js-colormaps only includes the non reversed names so check for the reverse
if (color.endsWith("_r") && js_colormaps.includes(color.substr(0, color.length - 2))) {
return color;
}
});

// Sort
colormaps.sort();

// ... new Set removes duplicates
injectables[injectableName] = [
...new Set(
injectablesDefaults["COLORMAP_NAMES"].concat(res.colorMaps)
injectablesDefaults[injectableName].concat(colormaps)
),
];
},
(res) => {
console.warn(`Failed to query for ${injectableName}. Using defaults.`);
injectables[injectableName] = [
"gist_earth",
"gist_earth_r",
"gist_gray",
"gist_gray_r",
"gist_heat",
"gist_heat_r",
"gist_ncar",
"gist_ncar_r",
"gist_rainbow",
"gist_rainbow_r",
"gist_stern",
"gist_stern_r",
"gist_yarg",
"gist_yarg_r",
"terrain",
"terrain_r",
"viridis",
"viridis_r",
];
injectables[injectableName] = Object.keys(colormapData);
}
);
} else {
// Get colormaps from js-colormaps and the inversed colors
const js_colormaps = Object.keys(colormapData).map((color => color.toLowerCase()));
let colormaps = [];
js_colormaps.forEach((color) => {
colormaps.push(color);
// js-colormaps only includes the non reversed names so add the reverse
if (!color.endsWith("_r")) {
colormaps.push(`${color}_r`);
}
});

// Sort
colormaps.sort();

// ... new Set removes duplicates
injectables[injectableName] = [
...new Set(
injectablesDefaults[injectableName].concat(colormaps)
),
];
}
}
240 changes: 0 additions & 240 deletions configure/src/external/js-colormaps.js

This file was deleted.

291 changes: 291 additions & 0 deletions configure/src/external/js-colormaps.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions configure/src/metaconfigs/layer-data-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"options": [
"data",
"header",
"image",
"model",
"query",
"tile",
Expand Down
1 change: 1 addition & 0 deletions configure/src/metaconfigs/layer-header-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"options": [
"data",
"header",
"image",
"model",
"query",
"tile",
Expand Down
Loading

0 comments on commit df8e02d

Please sign in to comment.