diff --git a/build/createExtensionWebpackConfig.js b/build/createExtensionWebpackConfig.js
index fbab8b2231..c83af1fb23 100644
--- a/build/createExtensionWebpackConfig.js
+++ b/build/createExtensionWebpackConfig.js
@@ -35,6 +35,9 @@ module.exports = ({ prod = true, name, exposes, sharedLibrariesEager = true, ali
resolve: {
fallback: {
path: false,
+ http: false,
+ https: false,
+ zlib: false,
timers: false,
stream: false
},
diff --git a/docs/user-guide/catalog.md b/docs/user-guide/catalog.md
index 2da7639d9c..ed45266c32 100644
--- a/docs/user-guide/catalog.md
+++ b/docs/user-guide/catalog.md
@@ -340,3 +340,9 @@ MapStore allows to publish 3D Tiles contents in its 3D mode on top of the [Cesiu
In **general settings of** 3D Tiles service, the user can specify the title to assign to this service and the URL of the service.
+
+!!! warning
+ MapStore allows you to load also [Google Photorealistic 3D Tiles](https://cloud.google.com/blog/products/maps-platform/create-immersive-3d-map-experiences-photorealistic-3d-tiles) and some constraints need to be respected in this case.
+ Since the Google Photorealistic 3D Tiles are not ‘survey-grade’ at this time, the use of certain MapStore tools could be considered derivative and, for this reason, prohibited. Please, make sure you have read the [Google conditions of use](https://developers.google.com/maps/documentation/tile/policies)
+ (some [FAQs](https://cloud.google.com/blog/products/maps-platform/commonly-asked-questions-about-our-recently-launched-photorealistic-3d-tiles) are also available online for this purpose) before providing Google Photorealistic 3D Tile in your MapStore maps in order to enable only allowed tools (e.g. *Measurement* and *Identify* tools should be probably disabled).
+ For this purpose it is possible to appropriately set the [configuration of MapStore plugins](../../developer-guide/maps-configuration/#map-options) to exclude tools that could conflict with Google policies. Alternatively, it is possible to use a dedicated [application context](application-context.md#configure-plugins) to show Photorealistic 3D Tiles by including only the permitted tools within it.
diff --git a/web/client/components/TOC/fragments/settings/VisibilityLimitsForm.jsx b/web/client/components/TOC/fragments/settings/VisibilityLimitsForm.jsx
index 80c38f97d3..e51683cf7c 100644
--- a/web/client/components/TOC/fragments/settings/VisibilityLimitsForm.jsx
+++ b/web/client/components/TOC/fragments/settings/VisibilityLimitsForm.jsx
@@ -95,7 +95,7 @@ function SelectInput({
function VisibilityLimitsForm({
title,
layer,
- zoom,
+ zoom: zoomProp,
projection,
resolutions = getResolutions(),
defaultLimitsType,
@@ -104,6 +104,8 @@ function VisibilityLimitsForm({
onChange
}) {
+ const zoom = Math.round(zoomProp || 0);
+
const [limitsType, setLimitsType] = useState(defaultLimitsType || limitsTypesOptions[0].value);
const {
diff --git a/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx b/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx
index f65164baf9..5e0a27287e 100644
--- a/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx
+++ b/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx
@@ -5,118 +5,178 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
-import React from 'react';
-import ReactDOM from 'react-dom';
-import expect from 'expect';
+import React from "react";
+import ReactDOM from "react-dom";
+import expect from "expect";
-import NumberFormat from '../../../../I18N/Number';
-import {getFormatter, registerFormatter, unregisterFormatter} from '../index';
+import NumberFormat from "../../../../I18N/Number";
+import { getFormatter, registerFormatter, unregisterFormatter } from "../index";
-describe('Tests for the formatter functions', () => {
- it('test getFormatter for booleans', () => {
- const formatter = getFormatter({localType: "boolean"});
+describe("Tests for the formatter functions", () => {
+ it("test getFormatter for booleans", () => {
+ const formatter = getFormatter({ localType: "boolean" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
- expect(formatter({value: true}).type).toBe("span");
- expect(formatter({value: true}).props.children).toBe("true");
- expect(formatter({value: false}).props.children).toBe("false");
- expect(formatter({value: null})).toBe(null);
- expect(formatter({value: undefined})).toBe(null);
+ expect(formatter({ value: true }).type).toBe("span");
+ expect(formatter({ value: true }).props.children).toBe("true");
+ expect(formatter({ value: false }).props.children).toBe("false");
+ expect(formatter({ value: null })).toBe(null);
+ expect(formatter({ value: undefined })).toBe(null);
});
- it('test getFormatter for strings', () => {
- const value = 'Test https://google.com with google link';
- const formatter = getFormatter({localType: "string"});
+ it("test getFormatter for strings", () => {
+ const value = "Test https://google.com with google link";
+ const formatter = getFormatter({ localType: "string" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
- expect(formatter({value: 'Test no links'})[0]).toBe('Test no links');
- expect(formatter({value})[0]).toBe('Test ');
- expect(formatter({value})[1].props.href).toBe('https://google.com');
- expect(formatter({value})[2]).toBe(' with google link');
- expect(formatter({value: null})).toBe(null);
- expect(formatter({value: undefined})).toBe(null);
+ expect(formatter({ value: "Test no links" })[0]).toBe("Test no links");
+ expect(formatter({ value })[0]).toBe("Test ");
+ expect(formatter({ value })[1].props.href).toBe("https://google.com");
+ expect(formatter({ value })[2]).toBe(" with google link");
+ expect(formatter({ value: null })).toBe(null);
+ expect(formatter({ value: undefined })).toBe(null);
});
- it('test getFormatter for number', () => {
- const formatter = getFormatter({localType: "number"});
+ it("test getFormatter for number", () => {
+ const formatter = getFormatter({ localType: "number" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
- expect(formatter({value: 44.3333434353535}).type).toBe(NumberFormat);
- expect(formatter({value: 44.3333434353535}).props.value).toBe(44.3333434353535);
- expect(formatter({value: null})).toBe(null);
- expect(formatter({value: undefined})).toBe(null);
- expect(formatter({value: 0}).props.value).toBe(0);
+ expect(formatter({ value: 44.3333434353535 }).type).toBe(NumberFormat);
+ expect(formatter({ value: 44.3333434353535 }).props.value).toBe(
+ 44.3333434353535
+ );
+ expect(formatter({ value: null })).toBe(null);
+ expect(formatter({ value: undefined })).toBe(null);
+ expect(formatter({ value: 0 }).props.value).toBe(0);
});
- it('test getFormatter for int', () => {
- const formatter = getFormatter({localType: "int"});
+ it("test getFormatter for int", () => {
+ const formatter = getFormatter({ localType: "int" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
- expect(formatter({value: 2455567}).type).toBe(NumberFormat);
- expect(formatter({value: 2455567}).props.value).toBe(2455567);
- expect(formatter({value: null})).toBe(null);
- expect(formatter({value: undefined})).toBe(null);
- expect(formatter({value: 0}).props.value).toBe(0);
+ expect(formatter({ value: 2455567 }).type).toBe(NumberFormat);
+ expect(formatter({ value: 2455567 }).props.value).toBe(2455567);
+ expect(formatter({ value: null })).toBe(null);
+ expect(formatter({ value: undefined })).toBe(null);
+ expect(formatter({ value: 0 }).props.value).toBe(0);
});
- it('test getFormatter for geometry', () => {
- const formatter = getFormatter({localType: "Geometry"});
+ it("test getFormatter for geometry", () => {
+ const formatter = getFormatter({ localType: "Geometry" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
- expect(formatter({value: {properties: {}, geometry: {type: "Point", coordinates: [1, 2]}}})).toBe(null);
- expect(formatter({value: null})).toBe(null);
- expect(formatter({value: undefined})).toBe(null);
+ expect(
+ formatter({
+ value: {
+ properties: {},
+ geometry: { type: "Point", coordinates: [1, 2] }
+ }
+ })
+ ).toBe(null);
+ expect(formatter({ value: null })).toBe(null);
+ expect(formatter({ value: undefined })).toBe(null);
});
- describe('test featureGridFormatter', () => {
+ describe("test featureGridFormatter", () => {
beforeEach((done) => {
document.body.innerHTML = '
';
setTimeout(done);
});
afterEach((done) => {
- ReactDOM.unmountComponentAtNode(document.getElementById("container"));
- document.body.innerHTML = '';
+ ReactDOM.unmountComponentAtNode(
+ document.getElementById("container")
+ );
+ document.body.innerHTML = "";
setTimeout(done);
});
- it('base', () => {
+ it("base", () => {
try {
- registerFormatter("test", ({config, value}) => {
+ registerFormatter("test", ({ config, value }) => {
expect(config).toExist();
expect(value).toBe("test");
return test
;
});
- const Formatter = getFormatter({localType: "test"}, {featureGridFormatter: {name: "test"}});
- ReactDOM.render(, document.getElementById("container"));
- expect(document.getElementById("container").innerHTML).toBe('test
');
+ const Formatter = getFormatter(
+ { localType: "test" },
+ { featureGridFormatter: { name: "test" } }
+ );
+ ReactDOM.render(
+ ,
+ document.getElementById("container")
+ );
+ expect(document.getElementById("container").innerHTML).toBe(
+ "test
"
+ );
} finally {
unregisterFormatter("test");
}
});
- it('with directRender option', () => {
+ it("with directRender option", () => {
try {
const TEST_FUNC = () => test
;
registerFormatter("test", TEST_FUNC);
- const formatter = getFormatter({localType: "test"}, {featureGridFormatter: {name: "test", directRender: true}});
+ const formatter = getFormatter(
+ { localType: "test" },
+ {
+ featureGridFormatter: {
+ name: "test",
+ directRender: true
+ }
+ }
+ );
expect(formatter).toBe(TEST_FUNC);
} finally {
unregisterFormatter("test");
}
});
});
- it('test getFormatter for date / date-time / time', () => {
+ it("test getFormatter for date / date-time / time", () => {
const dateFormats = {
- date: 'YYYY',
- "date-time": 'YYYY DD',
- time: 'HH:mm'
+ date: "YYYY",
+ "date-time": "YYYY DD",
+ time: "HH:mm"
};
- const dateFormatter = getFormatter({localType: "date"}, undefined, {dateFormats});
- const dateTimeFormatter = getFormatter({localType: "date-time"}, undefined, {dateFormats});
- const timeFormatter = getFormatter({localType: "time"}, undefined, {dateFormats});
+ const dateFormatter = getFormatter({ localType: "date" }, undefined, {
+ dateFormats
+ });
+ const dateTimeFormatter = getFormatter(
+ { localType: "date-time" },
+ undefined,
+ { dateFormats }
+ );
+ const timeFormatter = getFormatter({ localType: "time" }, undefined, {
+ dateFormats
+ });
expect(typeof dateFormatter).toBe("function");
expect(dateFormatter()).toBe(null);
- expect(dateFormatter({value: '2015-02-01T12:45:00Z'})).toBe('2015');
+ expect(dateFormatter({ value: "2015-02-01T12:45:00Z" })).toBe("2015");
expect(typeof dateTimeFormatter).toBe("function");
expect(dateTimeFormatter()).toBe(null);
- expect(dateTimeFormatter({value: '2015-02-01Z'})).toBe('2015 01');
+ expect(dateTimeFormatter({ value: "2015-02-01Z" })).toBe("2015 01");
expect(typeof timeFormatter).toBe("function");
expect(timeFormatter()).toBe(null);
- expect(timeFormatter({value: '12:45:00Z'})).toBe('12:45');
- expect(timeFormatter({ value: '1970-01-01T02:30:00Z' })).toBe('02:30'); // still able to format time even when found a full date (sometimes GeoServer returns full date instead of time only)
+ expect(timeFormatter({ value: "12:45:00Z" })).toBe("12:45");
+ expect(timeFormatter({ value: "1970-01-01T02:30:00Z" })).toBe("02:30"); // still able to format time even when found a full date (sometimes GeoServer returns full date instead of time only)
+ });
+
+ it("test getFormatter for invalid date-time YYYY-MM-DD[Z]", () => {
+ const dateFormats = {
+ "date-time": "YYYY-MM-DD[Z]"
+ };
+ const dateTimeWithZFormatter = getFormatter(
+ { localType: "date-time" },
+ undefined,
+ { dateFormats }
+ );
+ expect(typeof dateTimeWithZFormatter).toBe("function");
+ expect(dateTimeWithZFormatter({ value: "2015-02-01Z" })).toBe(
+ "2015-02-01Z"
+ );
+ expect(dateTimeWithZFormatter({ value: "2015-02-01" })).toBe(
+ "2015-02-01Z"
+ );
+ expect(dateTimeWithZFormatter({ value: "2015/02/01" })).toBe(
+ "2015-02-01Z"
+ );
+ expect(dateTimeWithZFormatter({ value: "2015/02/01 03:20:10" })).toBe(
+ "2015-02-01Z"
+ );
+ expect(dateTimeWithZFormatter({ value: "2015-02-01T12:45:00Z"})).toBe('2015-02-01Z');
});
});
diff --git a/web/client/components/data/featuregrid/formatters/index.js b/web/client/components/data/featuregrid/formatters/index.js
index 45979a4ab2..28f2d339a6 100644
--- a/web/client/components/data/featuregrid/formatters/index.js
+++ b/web/client/components/data/featuregrid/formatters/index.js
@@ -20,13 +20,16 @@ const StringFormatter = ({value} = {}) => !isNil(value) ? reactStringReplace(val
)) : null;
const NumberFormatter = ({value} = {}) => !isNil(value) ? : null;
const DEFAULT_DATE_PART = "1970-01-01";
+const DATE_INPUT_FORAMAT = "YYYY-MM-DD[Z]";
const dateTimeFormatter = ({value, format, type}) => {
return !isNil(value)
? moment.utc(value).isValid() // geoserver sometimes returns UTC for time.
? moment.utc(value).format(format)
: type === 'time'
? moment(`${DEFAULT_DATE_PART}T${value}`).utc().format(format) // time format append default date part
- : moment(value).format(format) // date or date-time formats
+ : type === "date" && value?.toLowerCase()?.endsWith("z") // in case: date format and value ends with z
+ ? moment(value, DATE_INPUT_FORAMAT).format(format)
+ : moment(value).format(format)
: null;
};
export const register = {};
diff --git a/web/client/components/map/cesium/Map.jsx b/web/client/components/map/cesium/Map.jsx
index f5144a1fa5..a0f1f8ffc6 100644
--- a/web/client/components/map/cesium/Map.jsx
+++ b/web/client/components/map/cesium/Map.jsx
@@ -579,7 +579,7 @@ class CesiumMap extends React.Component {
roll: this.map.camera.roll
}
},
- getResolutions()[zoom]
+ getResolutions()[Math.round(zoom)]
);
};
diff --git a/web/client/components/map/cesium/plugins/ThreeDTilesLayer.js b/web/client/components/map/cesium/plugins/ThreeDTilesLayer.js
index 7d72c1decf..8e0d750c09 100644
--- a/web/client/components/map/cesium/plugins/ThreeDTilesLayer.js
+++ b/web/client/components/map/cesium/plugins/ThreeDTilesLayer.js
@@ -44,9 +44,9 @@ function getStyle({ style }) {
function updateModelMatrix(tileSet, { heightOffset }) {
if (!isNaN(heightOffset) && isNumber(heightOffset)) {
const boundingSphere = tileSet.boundingSphere;
- const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
- const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
- const offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);
+ const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center); // undefined if the cartesian is at the center of the ellipsoid
+ const surface = Cesium.Cartesian3.fromRadians(cartographic?.longitude || 0, cartographic?.latitude || 0, 0.0);
+ const offset = Cesium.Cartesian3.fromRadians(cartographic?.longitude || 0, cartographic?.latitude || 0, heightOffset);
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
tileSet.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
}
diff --git a/web/client/components/map/leaflet/Layer.jsx b/web/client/components/map/leaflet/Layer.jsx
index f33499cb8f..c111dfeecf 100644
--- a/web/client/components/map/leaflet/Layer.jsx
+++ b/web/client/components/map/leaflet/Layer.jsx
@@ -127,8 +127,9 @@ class LeafletLayer extends React.Component {
maxResolution = Infinity,
disableResolutionLimits
} = options || {};
- if (!disableResolutionLimits && !isNil(resolutions[zoom])) {
- const resolution = resolutions[zoom];
+ const zoomRound = Math.round(zoom);
+ if (!disableResolutionLimits && !isNil(resolutions[zoomRound])) {
+ const resolution = resolutions[zoomRound];
// use similar approach of ol
// maxResolution is exclusive
// minResolution is inclusive
diff --git a/web/client/components/map/leaflet/Map.jsx b/web/client/components/map/leaflet/Map.jsx
index 53e7b35f1d..e99ea2e8f0 100644
--- a/web/client/components/map/leaflet/Map.jsx
+++ b/web/client/components/map/leaflet/Map.jsx
@@ -463,7 +463,7 @@ class LeafletMap extends React.Component {
this.props.id,
this.props.projection,
viewerOptions, // viewerOptions
- this.getResolutions()[zoom] // resolution
+ this.getResolutions()[Math.round(zoom)] // resolution
);
};
diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx
index a90d3cdbc9..0dd1312a0f 100644
--- a/web/client/components/map/openlayers/Map.jsx
+++ b/web/client/components/map/openlayers/Map.jsx
@@ -505,7 +505,12 @@ class OpenlayersMap extends React.Component {
projection: normalizeSRS(projection),
center: [center.x, center.y],
zoom: zoom,
- minZoom: limits.minZoom
+ minZoom: limits.minZoom,
+ // allow to zoom to level 0 and see world wrapping
+ multiWorld: true,
+ // does not allow intermediary zoom levels
+ // we need this at true to set correctly the scale box
+ constrainResolution: true
}, newOptions || {});
return new View(viewOptions);
};
diff --git a/web/client/components/map/openlayers/MeasurementSupport.jsx b/web/client/components/map/openlayers/MeasurementSupport.jsx
index efa19fc09a..8028a0eed1 100644
--- a/web/client/components/map/openlayers/MeasurementSupport.jsx
+++ b/web/client/components/map/openlayers/MeasurementSupport.jsx
@@ -26,6 +26,7 @@ import {getMessageById} from '../../../utils/LocaleUtils';
import {createOLGeometry} from '../../../utils/openlayers/DrawUtils';
import {Polygon, LineString} from 'ol/geom';
+import { never } from 'ol/events/condition';
import Overlay from 'ol/Overlay';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
@@ -599,6 +600,7 @@ export default class MeasurementSupport extends React.Component {
// create an interaction to draw with
draw = new Draw({
source: new VectorSource(),
+ freehandCondition: never,
type: /** @type {ol.geom.GeometryType} */ geometryType,
style: new Style({
fill: new Fill({
@@ -621,9 +623,9 @@ export default class MeasurementSupport extends React.Component {
})
});
- this.clickListener = this.props.map.on('click', this.updateMeasurementResults.bind(this, this.props));
+ this.clickListener = this.props.map.on('click', this.updateMeasurementResults.bind(this));
if (this.props.updateOnMouseMove) {
- this.props.map.on('pointermove', this.updateMeasurementResults.bind(this, this.props));
+ this.props.map.on('pointermove', this.updateMeasurementResults.bind(this));
}
this.props.map.on('pointermove', (evt) => this.pointerMoveHandler(evt));
@@ -896,10 +898,10 @@ export default class MeasurementSupport extends React.Component {
this.props.map.removeInteraction(this.drawInteraction);
this.drawInteraction = null;
this.sketchFeature = null;
- this.props.map.un('click', this.updateMeasurementResults.bind(this, this.props), this);
+ this.props.map.un('click', this.updateMeasurementResults.bind(this), this);
unByKey(this.clickListener);
if (this.props.updateOnMouseMove) {
- this.props.map.un('pointermove', this.updateMeasurementResults.bind(this, this.props), this);
+ this.props.map.un('pointermove', this.updateMeasurementResults.bind(this), this);
}
}
};
@@ -929,13 +931,13 @@ export default class MeasurementSupport extends React.Component {
this.helpTooltipElement.classList.remove('hidden');
};
- updateMeasurementResults = (props) => {
+ updateMeasurementResults = () => {
if (!this.sketchFeature) {
return;
}
let sketchCoords = this.sketchFeature.getGeometry().getCoordinates();
- if (props.measurement.geomType === 'Bearing' && sketchCoords.length > 1) {
+ if (this.props.measurement.geomType === 'Bearing' && sketchCoords.length > 1) {
// calculate the azimuth as base for bearing information
if (sketchCoords.length > 2) {
this.drawInteraction.sketchCoords_ = [sketchCoords[0], sketchCoords[1], sketchCoords[0]];
diff --git a/web/client/components/mapcontrols/scale/ScaleBox.jsx b/web/client/components/mapcontrols/scale/ScaleBox.jsx
index e3c780bda7..39333c2303 100644
--- a/web/client/components/mapcontrols/scale/ScaleBox.jsx
+++ b/web/client/components/mapcontrols/scale/ScaleBox.jsx
@@ -45,7 +45,7 @@ class ScaleBox extends React.Component {
}
onComboChange = (event) => {
- var selectedZoomLvl = parseInt(event.nativeEvent.target.value, 10);
+ let selectedZoomLvl = parseInt(event.nativeEvent.target.value, 10);
this.props.onChange(selectedZoomLvl, this.props.scales[selectedZoomLvl]);
};
@@ -58,14 +58,15 @@ class ScaleBox extends React.Component {
};
render() {
- var control = null;
+ let control = null;
+ const currentZoomLvl = Math.round(this.props.currentZoomLvl);
if (this.props.readOnly) {
control =
-
+
;
} else if (this.props.useRawInput) {
control =
- (