diff --git a/web/client/actions/__tests__/catalog-test.js b/web/client/actions/__tests__/catalog-test.js
index b50ae764b2..17fdf54de5 100644
--- a/web/client/actions/__tests__/catalog-test.js
+++ b/web/client/actions/__tests__/catalog-test.js
@@ -238,6 +238,13 @@ describe('Test correctness of the catalog actions', () => {
expect(retval).toExist();
expect(retval.type).toBe(ADD_SERVICE);
});
+ it('addService with options', () => {
+ const options = {"test": "1"};
+ var retval = addService(options);
+ expect(retval).toExist();
+ expect(retval.type).toBe(ADD_SERVICE);
+ expect(retval.options).toEqual(options);
+ });
it('addCatalogService', () => {
var retval = addCatalogService(service);
diff --git a/web/client/actions/catalog.js b/web/client/actions/catalog.js
index d4fd402bc2..fd41eeab71 100644
--- a/web/client/actions/catalog.js
+++ b/web/client/actions/catalog.js
@@ -170,9 +170,10 @@ export function changeUrl(url) {
url
};
}
-export function addService() {
+export function addService(options) {
return {
- type: ADD_SERVICE
+ type: ADD_SERVICE,
+ options
};
}
export function addCatalogService(service) {
diff --git a/web/client/api/catalog/COG.js b/web/client/api/catalog/COG.js
index a566aa4813..a7de6e4814 100644
--- a/web/client/api/catalog/COG.js
+++ b/web/client/api/catalog/COG.js
@@ -8,8 +8,9 @@
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
+import isNil from 'lodash/isNil';
import { Observable } from 'rxjs';
-import { fromUrl } from 'geotiff';
+import { fromUrl as fromGeotiffUrl } from 'geotiff';
import { isValidURL } from '../../utils/URLUtils';
import ConfigUtils from '../../utils/ConfigUtils';
@@ -57,8 +58,25 @@ export const getProjectionFromGeoKeys = (image) => {
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');
let layers = [];
@@ -73,29 +91,43 @@ export const getRecords = (_url, startPosition, maxRecords, text, info = {}) =>
sources: [{url}],
options: service.options || {}
};
- if (service.fetchMetadata) {
+ const controller = get(info, 'options.controller');
+ const isSave = get(info, 'options.save', false);
+ // 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)
- .then(geotiff => geotiff.getImage())
+ 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) && isProjectionDefined && {
+ ...(!isEmpty(extent) && !isEmpty(crs) && {
bbox: {
crs,
- bounds: {
- minx: extent[0],
- miny: extent[1],
- maxx: extent[2],
- maxy: extent[3]
- }
+ ...(isProjectionDefined && {
+ bounds: {
+ minx: extent[0],
+ miny: extent[1],
+ maxx: extent[2],
+ maxy: extent[3]
+ }}
+ )
}
})
};
diff --git a/web/client/components/catalog/CatalogServiceEditor.jsx b/web/client/components/catalog/CatalogServiceEditor.jsx
index b4e747e75b..5eb06219a5 100644
--- a/web/client/components/catalog/CatalogServiceEditor.jsx
+++ b/web/client/components/catalog/CatalogServiceEditor.jsx
@@ -17,7 +17,20 @@ import Message from "../I18N/Message";
import AdvancedSettings from './editor/AdvancedSettings';
import MainForm from './editor/MainForm';
-export default ({
+const withAbort = (Component) => {
+ return (props) => {
+ const [abortController, setAbortController] = useState(null);
+ const onSave = () => {
+ // Currently abort request on saving is applicable only for COG service
+ const controller = props.format === 'cog' ? new AbortController() : null;
+ setAbortController(controller);
+ return props.onAddService({save: true, controller});
+ };
+ const onCancel = () => abortController && props.saving ? abortController?.abort() : props.onChangeCatalogMode("view");
+ return ;
+ };
+};
+const CatalogServiceEditor = ({
service = {
title: "",
type: "wms",
@@ -39,9 +52,9 @@ export default ({
onChangeServiceProperty = () => {},
onToggleTemplate = () => {},
onToggleThumbnail = () => {},
- onAddService = () => {},
onDeleteService = () => {},
- onChangeCatalogMode = () => {},
+ onCancel = () => {},
+ onSaveService = () => {},
onFormatOptionsFetch = () => {},
selectedService,
isLocalizedLayerStylesEnabled,
@@ -50,7 +63,8 @@ export default ({
layerOptions = {},
infoFormatOptions,
services,
- autoSetVisibilityLimits = false
+ autoSetVisibilityLimits = false,
+ disabled
}) => {
const [valid, setValid] = useState(true);
return (
-