Skip to content

Commit

Permalink
Merge branch 'master' into feature_9706
Browse files Browse the repository at this point in the history
  • Loading branch information
mahmoudadel54 authored Nov 23, 2023
2 parents fc0f4c8 + 05003d0 commit 5205513
Show file tree
Hide file tree
Showing 90 changed files with 1,788 additions and 517 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/release_steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ If stable release (YYYY.XX.00) follow these sub-steps:
- [ ] `mapstore-printing.zip` on github release
- [ ] Publish the release
- [ ] create on [ReadTheDocs](https://readthedocs.org/projects/mapstore/) project the version build for `vYYYY.XX.mm` (click on "Versions" and activate the version of the tag, created when release was published)
- [ ] Update `Default version` to point the release version in the `Advanced Settings` menu of the [ReadTheDocs](https://readthedocs.org/dashboard/mapstore/advanced/) admin panel


## Build and publish MapStoreExtension release
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@
"lodash": "4.17.21",
"lrucache": "1.0.3",
"md5": "2.3.0",
"moment": "2.21.0",
"moment": "2.29.4",
"node-geo-distance": "1.2.0",
"object-assign": "4.1.1",
"object-fit-images": "3.2.4",
Expand Down
7 changes: 7 additions & 0 deletions web/client/actions/__tests__/catalog-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
12 changes: 10 additions & 2 deletions web/client/actions/__tests__/details-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ describe('details actions tests', () => {
const a = closeDetailsPanel();
expect(a.type).toBe(CLOSE_DETAILS_PANEL);
});
it('detailsLoaded', () => {
it('detailsLoaded for map', () => {
const mapId = 1;
const detailsUri = "sada/da/";
const a = detailsLoaded(mapId, detailsUri);
expect(a.type).toBe(DETAILS_LOADED);
expect(a.detailsUri).toBe(detailsUri);
expect(a.mapId).toBe(mapId);
expect(a.id).toBe(mapId);
});
it('detailsLoaded for dashboard', () => {
const dashboardId = 1;
const detailsUri = "sada/da/";
const a = detailsLoaded(dashboardId, detailsUri);
expect(a.type).toBe(DETAILS_LOADED);
expect(a.detailsUri).toBe(detailsUri);
expect(a.id).toBe(dashboardId);
});
it('updateDetails', () => {
const a = updateDetails('text');
Expand Down
7 changes: 5 additions & 2 deletions web/client/actions/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const SAVING_SERVICE = 'CATALOG:SAVING_SERVICE';
export const CATALOG_INITED = 'CATALOG:INIT';
export const GET_METADATA_RECORD_BY_ID = 'CATALOG:GET_METADATA_RECORD_BY_ID';
export const SET_LOADING = 'CATALOG:SET_LOADING';
export const SHOW_FORMAT_ERROR = 'CATALOG:SHOW_FORMAT_ERROR';
export const TOGGLE_TEMPLATE = 'CATALOG:TOGGLE_TEMPLATE';
export const TOGGLE_THUMBNAIL = 'CATALOG:TOGGLE_THUMBNAIL';
export const TOGGLE_ADVANCED_SETTINGS = 'CATALOG:TOGGLE_ADVANCED_SETTINGS';
Expand Down Expand Up @@ -170,9 +171,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) {
Expand Down Expand Up @@ -284,6 +286,7 @@ export const toggleThumbnail = () => ({type: TOGGLE_THUMBNAIL});
export const formatOptionsFetch = (url, force) => ({type: FORMAT_OPTIONS_FETCH, url, force});
export const formatsLoading = (loading) => ({type: FORMAT_OPTIONS_LOADING, loading});
export const setSupportedFormats = (formats, url) => ({type: SET_FORMAT_OPTIONS, formats, url});
export const showFormatError = (status) => ({type: SHOW_FORMAT_ERROR, status});

import {error} from './notifications';

Expand Down
8 changes: 4 additions & 4 deletions web/client/actions/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ export const NO_DETAILS_AVAILABLE = "NO_DETAILS_AVAILABLE";
* @memberof actions.details
* @return {action} type `UPDATE_DETAILS`
*/
export const updateDetails = (detailsText) => ({
export const updateDetails = (detailsText, resourceId) => ({
type: UPDATE_DETAILS,
detailsText
detailsText, id: resourceId
});

/**
* detailsLoaded
* @memberof actions.details
* @return {action} type `DETAILS_LOADED`
*/
export const detailsLoaded = (mapId, detailsUri, detailsSettings) => ({
export const detailsLoaded = (resourceId, detailsUri, detailsSettings) => ({
type: DETAILS_LOADED,
mapId,
id: resourceId,
detailsUri,
detailsSettings
});
Expand Down
56 changes: 44 additions & 12 deletions web/client/api/catalog/COG.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = [];
Expand All @@ -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]
}}
)
}
})
};
Expand Down
2 changes: 2 additions & 0 deletions web/client/api/geofence/RuleService.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const normalizeKey = (key) => {
return 'userName';
case 'rolename':
return 'groupName';
case 'roleAny':
return 'groupAny';
default:
return key;
}
Expand Down
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, getLayerTypeGlyph } 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 @@ -202,7 +214,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 @@ -212,7 +224,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="grab-handle"/> : <span className="toc-layer-tool toc-grab"/>;
const isDummy = !!this.props.node.dummy;
Expand All @@ -234,6 +246,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
Loading

0 comments on commit 5205513

Please sign in to comment.