Skip to content

Commit

Permalink
geosolutions-it#9588: Add support to multi-band color mapping for COG…
Browse files Browse the repository at this point in the history
… layers (geosolutions-it#9857)

(cherry picked from commit d683b82)
  • Loading branch information
dsuren1 committed Mar 21, 2024
1 parent c3535a1 commit 352f8b0
Show file tree
Hide file tree
Showing 15 changed files with 543 additions and 99 deletions.
13 changes: 10 additions & 3 deletions docs/developer-guide/maps-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1292,9 +1292,16 @@ i.e.
"visibility": false,
"name": "Name",
"sources": [
{ "url": "https://host-sample/cog1.tif" },
{ "url": "https://host-sample/cog2.tif" }
]
{ "url": "https://host-sample/cog1.tif", min: 1, max: 100, nodata: 0},
{ "url": "https://host-sample/cog2.tif", min: 1, max: 100, nodata: 255}
],
"style": {
"body": { // cog style currently supports only RGB with alpha band or single/gray band
"color": ["array", ["band", 1], ["band", 2], ["band", 3], ["band", 4]] // RGB with alpha band
// "color": ["array", ["band", 1], ["band", 1], ["band", 1], ["band", 2]] - single/gray band
},
"format": "openlayers",
}
}
```

Expand Down
98 changes: 12 additions & 86 deletions web/client/api/catalog/COG.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
*/

import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isNil from 'lodash/isNil';
import { Observable } from 'rxjs';
import { fromUrl as fromGeotiffUrl } from 'geotiff';


import { isValidURL } from '../../utils/URLUtils';
import ConfigUtils from '../../utils/ConfigUtils';
import { isProjectionAvailable } from '../../utils/ProjectionUtils';
import LayerUtils from '../../utils/cog/LayerUtils';
import { COG_LAYER_TYPE } from '../../utils/CatalogUtils';

const searchAndPaginate = (layers, startPosition, maxRecords, text) => {
Expand All @@ -31,51 +30,7 @@ const searchAndPaginate = (layers, startPosition, maxRecords, text) => {
records
};
};
/**
* Get projection code from geokeys
* @param {Object} image
* @returns {string} projection code
*/
export const getProjectionFromGeoKeys = (image) => {
const geoKeys = image.geoKeys;
if (!geoKeys) {
return null;
}

if (
geoKeys.ProjectedCSTypeGeoKey &&
geoKeys.ProjectedCSTypeGeoKey !== 32767
) {
return "EPSG:" + geoKeys.ProjectedCSTypeGeoKey;
}

if (
geoKeys.GeographicTypeGeoKey &&
geoKeys.GeographicTypeGeoKey !== 32767
) {
return "EPSG:" + geoKeys.GeographicTypeGeoKey;
}

return null;
};
const abortError = (reject) => reject(new DOMException("Aborted", "AbortError"));
/**
* fromUrl with abort fetching of data and data slices
* Note: The abort action will not cancel data fetch request but just the promise,
* because of the issue in https://github.com/geotiffjs/geotiff.js/issues/408
*/
const fromUrl = (url, signal) => {
if (signal?.aborted) {
return abortError(Promise.reject);
}
return new Promise((resolve, reject) => {
signal?.addEventListener("abort", () => abortError(reject));
return fromGeotiffUrl(url)
.then((image)=> image.getImage()) // Fetch and read first image to get medatadata of the tif
.then((image) => resolve(image))
.catch(()=> abortError(reject));
});
};
let capabilitiesCache = {};
export const getRecords = (_url, startPosition, maxRecords, text, info = {}) => {
const service = get(info, 'options.service');
Expand All @@ -93,50 +48,21 @@ export const getRecords = (_url, startPosition, maxRecords, text, info = {}) =>
};
const controller = get(info, 'options.controller');
const isSave = get(info, 'options.save', false);
const cached = capabilitiesCache[url];
if (cached && new Date().getTime() < cached.timestamp + (ConfigUtils.getConfigProp('cacheExpire') || 60) * 1000) {
return {...cached.data};
}
// Fetch metadata only on saving the service (skip on search)
if ((isNil(service.fetchMetadata) || service.fetchMetadata) && isSave) {
const cached = capabilitiesCache[url];
if (cached && new Date().getTime() < cached.timestamp + (ConfigUtils.getConfigProp('cacheExpire') || 60) * 1000) {
return {...cached.data};
}
return fromUrl(url, controller?.signal)
.then(image => {
const crs = getProjectionFromGeoKeys(image);
const extent = image.getBoundingBox();
const isProjectionDefined = isProjectionAvailable(crs);
layer = {
...layer,
sourceMetadata: {
crs,
extent: extent,
width: image.getWidth(),
height: image.getHeight(),
tileWidth: image.getTileWidth(),
tileHeight: image.getTileHeight(),
origin: image.getOrigin(),
resolution: image.getResolution()
},
// skip adding bbox when geokeys or extent is empty
...(!isEmpty(extent) && !isEmpty(crs) && {
bbox: {
crs,
...(isProjectionDefined && {
bounds: {
minx: extent[0],
miny: extent[1],
maxx: extent[2],
maxy: extent[3]
}}
)
}
})
};
return LayerUtils.getLayerConfig({url, controller, layer})
.then(updatedLayer => {
capabilitiesCache[url] = {
timestamp: new Date().getTime(),
data: {...layer}
data: {...updatedLayer}
};
return layer;
}).catch(() => ({...layer}));
return updatedLayer;
})
.catch(() => ({...layer}));
}
return Promise.resolve(layer);
});
Expand Down
8 changes: 1 addition & 7 deletions web/client/api/catalog/__tests__/COG-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import { getLayerFromRecord, getCatalogRecords, validate } from '../COG';
import { COG_LAYER_TYPE } from '../../../utils/CatalogUtils';
import { getLayerFromRecord, getCatalogRecords, validate, getProjectionFromGeoKeys} from '../COG';
import expect from 'expect';


Expand Down Expand Up @@ -62,10 +62,4 @@ describe('COG (Abstraction) API', () => {
const service = {title: "some", records: [{url: "https://some.tif"}]};
expect(validate(service)).toBeTruthy();
});
it('test getProjectionFromGeoKeys', () => {
expect(getProjectionFromGeoKeys({geoKeys: {ProjectedCSTypeGeoKey: 4326}})).toBe('EPSG:4326');
expect(getProjectionFromGeoKeys({geoKeys: {GeographicTypeGeoKey: 3857}})).toBe('EPSG:3857');
expect(getProjectionFromGeoKeys({geoKeys: null})).toBe(null);
expect(getProjectionFromGeoKeys({geoKeys: {ProjectedCSTypeGeoKey: 32767}})).toBe(null);
});
});
9 changes: 7 additions & 2 deletions web/client/components/map/openlayers/plugins/COGLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

import Layers from '../../../../utils/openlayers/Layers';
import isEqual from 'lodash/isEqual';
import get from 'lodash/get';

import GeoTIFF from 'ol/source/GeoTIFF.js';
import TileLayer from 'ol/layer/WebGLTile.js';
Expand All @@ -15,7 +17,7 @@ import { isProjectionAvailable } from '../../../../utils/ProjectionUtils';
function create(options) {
return new TileLayer({
msId: options.id,
style: options.style, // TODO style needs to be improved. Currently renders only predefined band and ranges when specified in config
style: get(options, 'style.body'),
opacity: options.opacity !== undefined ? options.opacity : 1,
visible: options.visibility,
source: new GeoTIFF({
Expand All @@ -32,7 +34,10 @@ function create(options) {
Layers.registerType('cog', {
create,
update(layer, newOptions, oldOptions, map) {
if (newOptions.srs !== oldOptions.srs) {
if (newOptions.srs !== oldOptions.srs
|| !isEqual(newOptions.style, oldOptions.style)
|| !isEqual(newOptions.sources, oldOptions.sources) // min/max source data value can change
) {
return create(newOptions, map);
}
if (oldOptions.minResolution !== newOptions.minResolution) {
Expand Down
Loading

0 comments on commit 352f8b0

Please sign in to comment.