Skip to content

Commit df8e02d

Browse files
ac-61tariqksoliman
andauthored
Add image layer type to support GeoTIFF and Cloud Optimized GeoTIFF images (#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 <Tariq.K.Soliman@jpl.nasa.gov> Co-authored-by: tariqksoliman <tariqksoliman@gmail.com>
1 parent 2e3d82b commit df8e02d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+30389
-18659
lines changed

API/Backend/Config/routes/configs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ function addLayer(req, res, next, cb, forceConfig, caller = "addLayer") {
694694
mission: "{mission_name}",
695695
layer: {
696696
name: "{new_layer_name}",
697-
type: "header || vector || vectortile || query || model || tile || data",
697+
type: "header || vector || vectortile || query || model || tile || data || image",
698698
"more...": "...",
699699
},
700700
"placement?": {

API/Backend/Config/validate.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ const validateLayers = (config) => {
8989
// Check model params (pos, rot, scale)
9090
errs = errs.concat(isValidModelParams(layer));
9191
break;
92+
case "image":
93+
// Check url
94+
errs = errs.concat(isValidUrl(layer));
95+
// Check zooms
96+
errs = errs.concat(isValidZooms(layer));
97+
break;
9298
default:
9399
errs = errs.concat(
94100
err(`Unknown layer type: '${layer.type}'`, ["layers[layer].type"])
@@ -316,6 +322,10 @@ const fillInMissingFieldsWithDefaults = (layer) => {
316322
layer.style = layer.style || {};
317323
layer.style.className = layer.name.replace(/ /g, "").toLowerCase();
318324
break;
325+
case "image":
326+
layer.style = layer.style || {};
327+
layer.style.className = layer.name.replace(/ /g, "").toLowerCase();
328+
break;
319329
case "model":
320330
break;
321331
default:

API/Backend/Utils/routes/utils.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,25 @@ router.post("/getbands", function (req, res) {
296296
);
297297
});
298298

299+
//utils getminmax
300+
router.post("/getminmax", function (req, res) {
301+
const path = encodeURIComponent(req.body.path);
302+
const bands = encodeURIComponent(req.body.bands);
303+
304+
execFile(
305+
"python",
306+
["private/api/gdalinfoMinMax.py", path, bands],
307+
function (error, stdout, stderr) {
308+
if (error) {
309+
logger("warn", error);
310+
res.status(400).send();
311+
} else {
312+
res.send(stdout);
313+
}
314+
}
315+
);
316+
});
317+
299318
//utils ll2aerll
300319
router.post("/ll2aerll", function (req, res) {
301320
const lng = encodeURIComponent(req.body.lng);

configuration/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ function getClientEnvironment(publicUrl) {
109109
THIRD_PARTY_COOKIES: process.env.THIRD_PARTY_COOKIES || "",
110110
SKIP_CLIENT_INITIAL_LOGIN: process.env.SKIP_CLIENT_INITIAL_LOGIN || "",
111111
IS_DOCKER: process.env.IS_DOCKER,
112+
WITH_TITILER: process.env.WITH_TITILER,
112113
}
113114
);
114115
// Stringify all values so we can feed into webpack DefinePlugin

configuration/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ module.exports = function (webpackEnv) {
381381
// Process any JS outside of the app with Babel.
382382
// Unlike the application JS, we only compile the standard ES features.
383383
{
384-
test: /\.(js|mjs)$/,
384+
test: /\.(js|mjs|cjs)$/,
385385
exclude: /@babel(?:\/|\\{1,2})runtime/,
386386
loader: require.resolve("babel-loader"),
387387
options: {

configure/public/toolConfigs.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

configure/src/components/Tabs/Layers/Layers.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import LanguageIcon from "@mui/icons-material/Language"; // Tile
2929
import GridViewIcon from "@mui/icons-material/GridView"; // Vector tile
3030
import ViewInArIcon from "@mui/icons-material/ViewInAr"; // Model
3131
import AirIcon from "@mui/icons-material/Air"; // Velocity
32+
import ImageIcon from '@mui/icons-material/Image'; // Image
3233
import AddIcon from "@mui/icons-material/Add";
3334

3435
import VisibilityIcon from "@mui/icons-material/Visibility";
@@ -386,6 +387,9 @@ export default function Layers() {
386387
case "velocity":
387388
iconType = <AirIcon fontSize="small" />;
388389
color = "#24807c";
390+
case "image":
391+
iconType = <ImageIcon fontSize="small" />;
392+
color = "#b0518f";
389393
break;
390394
default:
391395
}

configure/src/components/Tabs/Layers/Modals/LayerModal/LayerModal.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import tileConfig from "../../../../../metaconfigs/layer-tile-config.json";
4545
import vectorConfig from "../../../../../metaconfigs/layer-vector-config.json";
4646
import vectortileConfig from "../../../../../metaconfigs/layer-vectortile-config.json";
4747
import velocityConfig from "../../../../../metaconfigs/layer-velocity-config.json";
48+
import imageConfig from "../../../../../metaconfigs/layer-image-config.json";
4849

4950
const useStyles = makeStyles((theme) => ({
5051
Modal: {
@@ -196,6 +197,10 @@ const LayerModal = (props) => {
196197
config = velocityConfig;
197198
break;
198199

200+
case "image":
201+
config = imageConfig;
202+
break;
203+
199204
default:
200205
break;
201206
}

configure/src/core/Maker.js

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -721,31 +721,96 @@ const getComponent = (
721721
</FormControl>
722722
);
723723

724+
let domain =
725+
window.mmgisglobal.NODE_ENV === "development"
726+
? "http://localhost:8888/"
727+
: window.mmgisglobal.ROOT_PATH || "";
728+
if (domain.length > 0 && !domain.endsWith("/")) domain += "/";
729+
730+
let colormap_html
731+
if (window.mmgisglobal.WITH_TITILER === "true") {
732+
// Get colors from TiTiler if it is available
733+
colormap_html = (
734+
<div style={{width: "100%"}}>
735+
<img id="titlerCogColormapImage" style={{height: "20px", width: "100%"}} src={`${domain}titiler/colorMaps/${dropdown_value.toLowerCase()}?format=png`} />
736+
</div>
737+
)
738+
} else {
739+
let colormap = dropdown_value
740+
// js-colormaps data object only contains the non reversed color so we need to track if the color is reversed
741+
let reverse = false
742+
743+
// TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps
744+
if (colormap.toLowerCase().endsWith('_r')) {
745+
colormap = colormap.substring(0, colormap.length - 2)
746+
reverse = true
747+
}
748+
749+
let index = Object.keys(colormapData).findIndex(v => {
750+
return v.toLowerCase() === colormap.toLowerCase();
751+
});
752+
753+
if (index > -1) {
754+
colormap = Object.keys(colormapData)[index]
755+
} else {
756+
console.warn(`The colormap '${colormap}' does not exist`);
757+
}
758+
759+
if (colormap in colormapData) {
760+
colormap_html = colormapData[colormap].colors.map(
761+
(hex) => {
762+
return (
763+
<div
764+
className={c.colorDropdownArrayHex}
765+
style={{ background: `rgb(${hex.map(v => {return Math.floor(v * 255)}).join(',')})` }}
766+
></div>
767+
);
768+
}
769+
)
770+
771+
if (reverse === true) {
772+
colormap_html.reverse()
773+
}
774+
} else if (colormap === 'DEFAULT') {
775+
// Default color for velocity layer
776+
const defaultColors = [
777+
'rgb(36,104, 180)',
778+
'rgb(60,157, 194)',
779+
'rgb(128,205,193 )',
780+
'rgb(151,218,168 )',
781+
'rgb(198,231,181)',
782+
'rgb(238,247,217)',
783+
'rgb(255,238,159)',
784+
'rgb(252,217,125)',
785+
'rgb(255,182,100)',
786+
'rgb(252,150,75)',
787+
'rgb(250,112,52)',
788+
'rgb(245,64,32)',
789+
'rgb(237,45,28)',
790+
'rgb(220,24,32)',
791+
'rgb(180,0,35)',
792+
]
793+
794+
colormap_html = defaultColors.map(
795+
(hex) => {
796+
return (
797+
<div
798+
className={c.colorDropdownArrayHex}
799+
style={{ background: `${hex}`}}
800+
></div>
801+
);
802+
}
803+
)
804+
}
805+
}
806+
724807
return (
725808
<div>
726809
{inlineHelp ? (
727810
<>
728811
{inner}
729812
<div className={c.textArrayHexes}>
730-
{typeof dropdown_value === "string"
731-
? colormapData[dropdown_value] &&
732-
colormapData[dropdown_value].colors
733-
? colormapData[dropdown_value].colors.map((hex) => {
734-
return (
735-
<div
736-
className={c.colorDropdownArrayHex}
737-
style={{
738-
background: `rgb(${hex
739-
.map((v) => {
740-
return Math.floor(v * 255);
741-
})
742-
.join(",")})`,
743-
}}
744-
></div>
745-
);
746-
})
747-
: null
748-
: null}
813+
{colormap_html || null}
749814
</div>
750815
<Typography className={c.subtitle2}>
751816
{com.description || ""}

configure/src/core/injectables.js

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { calls } from "./calls";
2+
import { data as colormapData } from '../external/js-colormaps.js'
23

34
const injectablesDefaults = {
45
TILE_MATRIX_SETS: ["WebMercatorQuad"],
56
COLORMAP_NAMES: ["viridis"],
7+
VELOCITY_COLORMAP_NAMES: ["RDYLBU_R"],
68
};
79
// Initialize with reasonable defaults
810
const injectables = {
911
TILE_MATRIX_SETS: injectablesDefaults["TILE_MATRIX_SETS"],
1012
COLORMAP_NAMES: injectablesDefaults["COLORMAP_NAMES"],
13+
VELOCITY_COLORMAP_NAMES: injectablesDefaults["VELOCITY_COLORMAP_NAMES"],
1114
};
1215

1316
export const getInjectables = () => {
1417
getTileMatrixSets();
15-
getColormapNames();
18+
getColormapNames("COLORMAP_NAMES");
19+
getColormapNames("VELOCITY_COLORMAP_NAMES");
1620
};
1721

1822
export const inject = (configJson) => {
@@ -66,43 +70,61 @@ function getTileMatrixSets() {
6670
}
6771
}
6872

69-
function getColormapNames() {
70-
const injectableName = "COLORMAP_NAMES";
73+
function getColormapNames(injectableName) {
7174
if (window.mmgisglobal.WITH_TITILER === "true") {
7275
calls.api(
7376
"titiler_colormapNames",
7477
null,
7578
(res) => {
79+
// Get the intersection of colormaps from js-colormaps and TiTiler
80+
const js_colormaps = Object.keys(colormapData).map((color => color.toLowerCase()));
81+
let colormaps = res.colorMaps;
82+
colormaps = colormaps.filter((color) => {
83+
if (js_colormaps.includes(color.toLowerCase())) {
84+
return color;
85+
}
86+
87+
// js-colormaps only includes the non reversed names so check for the reverse
88+
if (color.endsWith("_r") && js_colormaps.includes(color.substr(0, color.length - 2))) {
89+
return color;
90+
}
91+
});
92+
93+
// Sort
94+
colormaps.sort();
95+
7696
// ... new Set removes duplicates
7797
injectables[injectableName] = [
7898
...new Set(
79-
injectablesDefaults["COLORMAP_NAMES"].concat(res.colorMaps)
99+
injectablesDefaults[injectableName].concat(colormaps)
80100
),
81101
];
82102
},
83103
(res) => {
84104
console.warn(`Failed to query for ${injectableName}. Using defaults.`);
85-
injectables[injectableName] = [
86-
"gist_earth",
87-
"gist_earth_r",
88-
"gist_gray",
89-
"gist_gray_r",
90-
"gist_heat",
91-
"gist_heat_r",
92-
"gist_ncar",
93-
"gist_ncar_r",
94-
"gist_rainbow",
95-
"gist_rainbow_r",
96-
"gist_stern",
97-
"gist_stern_r",
98-
"gist_yarg",
99-
"gist_yarg_r",
100-
"terrain",
101-
"terrain_r",
102-
"viridis",
103-
"viridis_r",
104-
];
105+
injectables[injectableName] = Object.keys(colormapData);
105106
}
106107
);
108+
} else {
109+
// Get colormaps from js-colormaps and the inversed colors
110+
const js_colormaps = Object.keys(colormapData).map((color => color.toLowerCase()));
111+
let colormaps = [];
112+
js_colormaps.forEach((color) => {
113+
colormaps.push(color);
114+
// js-colormaps only includes the non reversed names so add the reverse
115+
if (!color.endsWith("_r")) {
116+
colormaps.push(`${color}_r`);
117+
}
118+
});
119+
120+
// Sort
121+
colormaps.sort();
122+
123+
// ... new Set removes duplicates
124+
injectables[injectableName] = [
125+
...new Set(
126+
injectablesDefaults[injectableName].concat(colormaps)
127+
),
128+
];
107129
}
108130
}

0 commit comments

Comments
 (0)