Skip to content

Commit

Permalink
#9589: Notify data projection not compatible / available for COG (#9690
Browse files Browse the repository at this point in the history
…) (#9717)

(cherry picked from commit 76207ab)
  • Loading branch information
dsuren1 committed Nov 21, 2023
1 parent 6f87cf4 commit 6f59a61
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 12 deletions.
31 changes: 25 additions & 6 deletions web/client/components/TOC/DefaultLayer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import React from 'react';

import PropTypes from 'prop-types';
import Node from './Node';
import { isObject, castArray, find } from 'lodash';
import { isObject, castArray, find, isNil } from 'lodash';
import { Grid, Row, Col, Glyphicon } from 'react-bootstrap';
import draggableComponent from './enhancers/draggableComponent';
import VisibilityCheck from './fragments/VisibilityCheck';
Expand All @@ -23,6 +23,7 @@ import tooltip from '../misc/enhancers/tooltip';
import localizedProps from '../misc/enhancers/localizedProps';
import { isInsideResolutionsLimits } from '../../utils/LayersUtils';
import StyleBasedLegend from './fragments/StyleBasedLegend';
import { isSRSAllowed } from '../../utils/CoordinatesUtils';

const GlyphIndicator = localizedProps('tooltip')(tooltip(Glyphicon));

Expand Down Expand Up @@ -106,6 +107,17 @@ class DefaultLayer extends React.Component {
: 'toc.notVisibleZoomOut';
};

getErrorTooltipParams = () => {
if (!this.isCRSCompatible()) {
return {
tooltip: "toc.sourceCRSNotCompatible",
msgParams: {sourceCRS: this.getSourceCRS()}
};
}
return { tooltip: "toc.loadingerror" };
}
getSourceCRS = () => this.props.node?.bbox?.crs || this.props.node?.sourceMetadata?.crs;

renderOpacitySlider = (hideOpacityTooltip) => {
return (this.props.activateOpacityTool && this.props.node?.type !== '3dtiles') ? (
<OpacitySlider
Expand Down Expand Up @@ -136,10 +148,10 @@ class DefaultLayer extends React.Component {
};

renderVisibility = () => {
return this.props.node.loadingError === 'Error' ?
return this.isLayerError() ?
(<LayersTool key="loadingerror"
glyph="exclamation-mark text-danger"
tooltip="toc.loadingerror"
{...this.getErrorTooltipParams()}
className="toc-error" />)
:
(<VisibilityCheck key="visibilitycheck"
Expand All @@ -150,7 +162,7 @@ class DefaultLayer extends React.Component {
}

renderToolsLegend = (isEmpty) => {
return this.props.node.loadingError === 'Error' || isEmpty ?
return this.isLayerError() || isEmpty ?
null
:
(<LayersTool
Expand Down Expand Up @@ -200,7 +212,7 @@ class DefaultLayer extends React.Component {
return (
<Node className={(this.props.isDragging || this.props.node.placeholder ? "is-placeholder " : "") + 'toc-default-layer' + hide + selected + error + warning} style={this.props.style} type="layer" {...other}>
{other.isDraggable && !isDummy ? this.props.connectDragPreview(head) : head}
{isDummy || !this.props.activateOpacityTool || this.props.node.expanded || !this.props.node.visibility || this.props.node.loadingError === 'Error' ? null : this.renderOpacitySlider(this.props.hideOpacityTooltip)}
{isDummy || !this.props.activateOpacityTool || this.props.node.expanded || !this.props.node.visibility || this.isLayerError() ? null : this.renderOpacitySlider(this.props.hideOpacityTooltip)}
{isDummy || isEmpty ? null : this.renderCollapsible()}
</Node>
);
Expand All @@ -210,7 +222,7 @@ class DefaultLayer extends React.Component {
let {children, propertiesChangeHandler, onToggle, connectDragSource, connectDropTarget, ...other } = this.props;
const hide = !this.props.node.visibility || this.props.node.invalid || this.props.node.exclusiveMapType || !isInsideResolutionsLimits(this.props.node, this.props.resolution) ? ' visibility' : '';
const selected = this.props.selectedNodes.filter((s) => s === this.props.node.id).length > 0 ? ' selected' : '';
const error = this.props.node.loadingError === 'Error' ? ' layer-error' : '';
const error = this.isLayerError() ? ' layer-error' : '';
const warning = this.props.node.loadingError === 'Warning' ? ' layer-warning' : '';
const grab = other.isDraggable ? <LayersTool key="grabTool" tooltip="toc.grabLayerIcon" className="toc-grab" ref="target" glyph="menu-hamburger"/> : <span className="toc-layer-tool toc-grab"/>;
const isDummy = !!this.props.node.dummy;
Expand All @@ -232,6 +244,13 @@ class DefaultLayer extends React.Component {
return title.toLowerCase().indexOf(this.props.filterText.toLowerCase()) !== -1;
};

isLayerError = () => this.props.node.loadingError === 'Error' || !this.isCRSCompatible();

isCRSCompatible = () => {
const CRS = this.getSourceCRS();
// Check if source crs is compatible
return !isNil(CRS) ? isSRSAllowed(CRS) : true;
}
}

export default draggableComponent('LayerOrGroup', DefaultLayer);
39 changes: 39 additions & 0 deletions web/client/components/TOC/__tests__/DefaultLayer-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,43 @@ describe('test DefaultLayer module component', () => {
expect(button.length).toBe(1);
}
});
it('test with layer source crs', () => {
// Invalid CRS
let node = {
name: 'layer00',
title: 'Layer',
visibility: false,
storeIndex: 9,
opacity: 0.5,
bbox: {
crs: "EPSG:3946"
}
};

let comp = ReactDOM.render(<Layer node={node}/>, document.getElementById("container"));
expect(ReactDOM.findDOMNode(comp)).toBeTruthy();
let layerNode = document.querySelector('.toc-default-layer.layer-error');
let errorTooltip = document.querySelector('.toc-layer-tool.toc-error');
expect(layerNode).toBeTruthy();
expect(errorTooltip).toBeTruthy();

// Valid CRS
node = {
name: 'layer00',
title: 'Layer',
visibility: false,
storeIndex: 9,
opacity: 0.5,
bbox: {
crs: "EPSG:4326"
}
};

comp = ReactDOM.render(<Layer node={node}/>, document.getElementById("container"));
expect(ReactDOM.findDOMNode(comp)).toBeTruthy();
layerNode = document.querySelector('.toc-default-layer.layer-error');
errorTooltip = document.querySelector('.toc-layer-tool.toc-error');
expect(layerNode).toBeFalsy();
expect(errorTooltip).toBeFalsy();
});
});
3 changes: 2 additions & 1 deletion web/client/components/TOC/fragments/LayersTool.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class LayersTool extends React.Component {
style: PropTypes.object,
glyph: PropTypes.string,
tooltip: PropTypes.string,
msgParams: PropTypes.object,
className: PropTypes.string
};

Expand All @@ -34,7 +35,7 @@ class LayersTool extends React.Component {
glyph={this.props.glyph}
onClick={() => this.props.onClick(this.props.node)}/>);
return this.props.tooltip ?
<OverlayTrigger placement="bottom" overlay={(<Tooltip id={"Tooltip-" + this.props.tooltip}><strong><Message msgId={this.props.tooltip}/></strong></Tooltip>)}>
<OverlayTrigger placement="bottom" overlay={(<Tooltip id={"Tooltip-" + this.props.tooltip}><strong><Message msgId={this.props.tooltip} msgParams={this.props.msgParams}/></strong></Tooltip>)}>
{tool}
</OverlayTrigger> : tool;

Expand Down
16 changes: 12 additions & 4 deletions web/client/components/catalog/RecordItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
buildSRSMap,
getRecordLinks
} from '../../utils/CatalogUtils';
import {isAllowedSRS} from '../../utils/CoordinatesUtils';
import { isAllowedSRS, isSRSAllowed } from '../../utils/CoordinatesUtils';
import HtmlRenderer from '../misc/HtmlRenderer';
import {parseCustomTemplate} from '../../utils/TemplateUtils';
import {getMessageById} from '../../utils/LocaleUtils';
Expand Down Expand Up @@ -125,6 +125,16 @@ class RecordItem extends React.Component {

};

isSRSNotAllowed = (record) => {
if (record.serviceType !== 'cog') {
const ogcReferences = record.ogcReferences || { SRS: [] };
const allowedSRS = ogcReferences?.SRS?.length > 0 && buildSRSMap(ogcReferences.SRS);
return allowedSRS && !isAllowedSRS(this.props.crs, allowedSRS);
}
const crs = record?.sourceMetadata?.crs;
return crs && !isSRSAllowed(crs);
}

getButtons = (record) => {
const links = this.props.showGetCapLinks ? getRecordLinks(record) : [];
return [
Expand All @@ -136,9 +146,7 @@ class RecordItem extends React.Component {
loading: this.state.loading,
glyph: 'plus',
onClick: () => {
const ogcReferences = record.ogcReferences || { SRS: [] };
const allowedSRS = ogcReferences?.SRS?.length > 0 && buildSRSMap(ogcReferences.SRS);
if (allowedSRS && !isAllowedSRS(this.props.crs, allowedSRS)) {
if (this.isSRSNotAllowed(record)) {
return this.props.onError('catalog.srs_not_allowed');
}
this.setState({ loading: true });
Expand Down
18 changes: 17 additions & 1 deletion web/client/components/catalog/__tests__/RecordItem-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ describe('This test for RecordItem', () => {
}
};
let actionsSpy = expect.spyOn(actions, "onError");
const item = ReactDOM.render(<RecordItem
let item = ReactDOM.render(<RecordItem
record={sampleRecord2}
onError={actions.onError}
crs="EPSG:3857"/>, document.getElementById("container"));
Expand All @@ -617,6 +617,22 @@ describe('This test for RecordItem', () => {
expect(button).toBeTruthy();
button.click();
expect(actionsSpy.calls.length).toBe(1);

// With source metadata
const record = {...sampleRecord2, serviceType: "cog", sourceMetadata: {crs: "EPSG:3946"}};
item = ReactDOM.render(<RecordItem
record={record}
onError={actions.onError}
crs="EPSG:3857"/>, document.getElementById("container"));
expect(item).toBeTruthy();

button = TestUtils.findRenderedDOMComponentWithTag(
item, 'button'
);
expect(button).toBeTruthy();
button.click();
expect(actionsSpy.calls.length).toBe(2);

});
it('check add layer with bounding box', (done) => {
let actions = {
Expand Down
4 changes: 4 additions & 0 deletions web/client/components/map/openlayers/plugins/COGLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Layers from '../../../../utils/openlayers/Layers';

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

function create(options) {
return new TileLayer({
Expand Down Expand Up @@ -41,5 +42,8 @@ Layers.registerType('cog', {
layer.setMaxResolution(newOptions.maxResolution === undefined ? Infinity : newOptions.maxResolution);
}
return null;
},
isCompatible: (layer) => {
return isProjectionAvailable(layer?.sourceMetadata?.crs);
}
});
1 change: 1 addition & 0 deletions web/client/translations/data.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@
"browseData": "Attributtabelle der ausgewählten Ebene öffnen",
"removeLayer": "Ebene entfernen",
"loadingerror": "Diese Ebene wurde nicht korrekt geladen oder ist nicht verfügbar",
"sourceCRSNotCompatible": "Die Quelldatenprojektionsdefinition {sourceCRS} des Layers ist in der Anwendung nicht für die Neuprojektion der Daten in der aktuellen Karte verfügbar",
"measure": "Messen",
"backgroundSwitcher": "Hintergrund",
"layers": "Ebenen",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@
"browseData": "Open Attribute Table",
"removeLayer": "Remove layer",
"loadingerror": "The layer has not been loaded correctly or not available.",
"sourceCRSNotCompatible": "The layer's source data projection definition {sourceCRS} is not available in the application for reprojecting the data in the current map",
"measure": "Measure",
"layers": "Layers",
"drawerButton": "Layers",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@
"browseData": "Abrir la tabla de atributos",
"removeLayer": "Borrar la capa",
"loadingerror": "La capa no se ha cargado correctamente o no está disponible",
"sourceCRSNotCompatible": "La definición de proyección de datos de origen de la capa {sourceCRS} no está disponible en la aplicación para reproyectar los datos en el mapa actual",
"measure": "Medir",
"layers": "Capas",
"drawerButton": "Capas",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@
"browseData": "Ouvrir la table d'attribut",
"removeLayer": "Supprimer la couche",
"loadingerror": "La couche n'a pas été chargée correctement ou n'est pas disponible.",
"sourceCRSNotCompatible": "La définition de projection des données sources de la couche {sourceCRS} n'est pas disponible dans l'application de reprojection des données dans la carte actuelle",
"measure": "Mesurer",
"layers": "Couches",
"drawerButton": "Couches",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@
"browseData": "Apri tabella degli attributi",
"removeLayer": "Rimuovi livello",
"loadingerror": "Il livello non è stato caricato correttamente o non disponible.",
"sourceCRSNotCompatible": "La definizione di proiezione dei dati di origine del layer {sourceCRS} non è disponibile nell'applicazione per riproiettare i dati nella mappa corrente",
"measure": "Misure",
"layers": "Livelli",
"drawerButton": "Livelli",
Expand Down

0 comments on commit 6f59a61

Please sign in to comment.