Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9588: Add support to multi-band color mapping for COG layers #9857

Merged
merged 11 commits into from
Mar 21, 2024
99 changes: 13 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';

export const COG_LAYER_TYPE = 'cog';
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 @@ -89,54 +44,26 @@ export const getRecords = (_url, startPosition, maxRecords, text, info = {}) =>
title: record.title,
type: COG_LAYER_TYPE,
sources: [{url}],
// sources: [{"url": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/31/T/GJ/2022/7/S2A_31TGJ_20220703_0_L2A/B04.tif", "max": 3000}, {"url": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/31/T/GJ/2022/7/S2A_31TGJ_20220703_0_L2A/B03.tif", "max": 3000}, {"url": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/31/T/GJ/2022/7/S2A_31TGJ_20220703_0_L2A/B02.tif", "max": 3000}, {"url": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/31/T/GJ/2022/7/S2A_31TGJ_20220703_0_L2A/B08.tif", "max": 3000}],
offtherailz marked this conversation as resolved.
Show resolved Hide resolved
options: service.options || {}
};
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,7 +5,7 @@
* 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, COG_LAYER_TYPE, getProjectionFromGeoKeys} from '../COG';
import { getLayerFromRecord, getCatalogRecords, validate, COG_LAYER_TYPE } from '../COG';
import expect from 'expect';


Expand Down Expand Up @@ -61,10 +61,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);
});
});
15 changes: 13 additions & 2 deletions web/client/components/map/openlayers/plugins/COGLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@
*/

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

import GeoTIFF from 'ol/source/GeoTIFF.js';
import TileLayer from 'ol/layer/WebGLTile.js';
import { isProjectionAvailable } from '../../../../utils/ProjectionUtils';

function create(options) {
const style = options.style;
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: {
...style,
...(style?.color && {
color: typeof style?.color === 'string'
? JSON.parse(style?.color) : style?.color
dsuren1 marked this conversation as resolved.
Show resolved Hide resolved
})
},
opacity: options.opacity !== undefined ? options.opacity : 1,
visible: options.visibility,
source: new GeoTIFF({
Expand All @@ -32,7 +40,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
Loading