diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx index 8bc0997ebe..2268f7ac62 100644 --- a/web/client/components/map/openlayers/Map.jsx +++ b/web/client/components/map/openlayers/Map.jsx @@ -304,8 +304,8 @@ class OpenlayersMap extends React.Component { }, 0); } - if (this.map && ((this.props.projection !== newProps.projection) || this.haveResolutionsChanged(newProps)) || this.props.limits !== newProps.limits) { - if (this.props.projection !== newProps.projection || this.props.limits !== newProps.limits) { + if (this.map && ((this.props.projection !== newProps.projection) || this.haveResolutionsChanged(newProps)) || this.haveRotationChanged(newProps) || this.props.limits !== newProps.limits) { + if (this.props.projection !== newProps.projection || this.props.limits !== newProps.limits || this.haveRotationChanged(newProps)) { let mapProjection = newProps.projection; const center = reproject([ newProps.center.x, @@ -502,6 +502,12 @@ class OpenlayersMap extends React.Component { return !isEqual(resolutions, newResolutions); }; + haveRotationChanged = (newProps) => { + const rotation = this.props.mapOptions && this.props.mapOptions.view ? this.props.mapOptions.view.rotation : undefined; + const newRotation = newProps.mapOptions && newProps.mapOptions.view ? newProps.mapOptions.view.rotation : undefined; + return !isEqual(rotation, newRotation); + }; + createView = (center, zoom, projection, options, limits = {}) => { // limit has a crs defined const extent = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, normalizeSRS(projection)); diff --git a/web/client/components/map/openlayers/__tests__/Map-test.jsx b/web/client/components/map/openlayers/__tests__/Map-test.jsx index 17de72813a..b9900912f7 100644 --- a/web/client/components/map/openlayers/__tests__/Map-test.jsx +++ b/web/client/components/map/openlayers/__tests__/Map-test.jsx @@ -712,6 +712,83 @@ describe('OpenlayersMap', () => { expect( map.haveResolutionsChanged(testProps({mapOptions: {view: {resolutions: [100, 50, 25]}}})) ).toBe(true); }); + it('check result of "haveRotationChanged()" when receiving new props', () => { + let map = ReactDOM.render( + + , document.getElementById("map")); + + let origProps = assign({}, map.props); + function testProps(newProps) { + // update original props with newProps + return assign({}, origProps, newProps); + } + + map = ReactDOM.render( + + , document.getElementById("map")); + + expect(map.haveRotationChanged(testProps({mapOptions: undefined}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: undefined}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 0}}}))).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 20}}}))).toBe(true); + + map = ReactDOM.render( + + , document.getElementById("map")); + expect(map.haveRotationChanged(testProps({mapOptions: undefined}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: undefined}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 0}}}))).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 20}}}))).toBe(true); + + map = ReactDOM.render( + + , document.getElementById("map")); + expect(map.haveRotationChanged(testProps({mapOptions: undefined}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: undefined}}}))).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 0}}}))).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 20}}}))).toBe(true); + map = ReactDOM.render( + + , document.getElementById("map")); + expect(map.haveRotationChanged(testProps({mapOptions: undefined})) ).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {}})) ).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {}}})) ).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: undefined}}})) ).toBe(true); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 1}}})) ).toBe(false); + expect(map.haveRotationChanged(testProps({mapOptions: {view: {rotation: 20}}})) ).toBe(true); + }); + it('check if the map has "auto" cursor as default', () => { const map = ReactDOM.render( ({ onChangeParameter: setPrintParameter })(ProjectionComp); +export const Rotation = connect((state) => ({ + spec: state.print?.spec || {}, + additionalProperty: false, + property: "rotation", + path: "", + type: "number", + label: "print.rotation" +}), { + onChangeParameter: setPrintParameter +})(TextInput); + export const Layout = connect((state) => ({ spec: state.print?.spec || {}, layouts: state?.print?.capabilities?.layouts || [] @@ -189,13 +200,17 @@ export const standardItems = { "projections": [{"name": "EPSG:3857", "value": "EPSG:3857"}, {"name": "EPSG:4326", "value": "EPSG:4326"}] }, position: 4 + }, { + id: "rotation", + plugin: Rotation, + position: 5 }, { id: "overlayLayers", plugin: AdditionalLayers, cfg: { enabled: false }, - position: 5 + position: 6 }], "left-panel-accordion": [{ id: "layout", diff --git a/web/client/reducers/print.js b/web/client/reducers/print.js index e7569e94bb..a8312dfd93 100644 --- a/web/client/reducers/print.js +++ b/web/client/reducers/print.js @@ -37,7 +37,8 @@ const initialSpec = { resolution: 96, name: '', description: '', - outputFormat: "pdf" + outputFormat: "pdf", + rotation: 0 }; const getSheetName = (name = '') => { diff --git a/web/client/themes/default/less/print.less b/web/client/themes/default/less/print.less index 82dd344f14..f347519f48 100644 --- a/web/client/themes/default/less/print.less +++ b/web/client/themes/default/less/print.less @@ -34,4 +34,14 @@ border-width: 1px; border-style: solid; } + #print_preview { + .ol-rotate { + background-color: transparent; + button{ + &.ol-rotate-reset{ + display: none; //hide rotate button on map-preview + } + } + } + } } diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index ba844d9ab2..6899dd8899 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -743,6 +743,7 @@ "iconsSize": "Symbolgröße:", "dpi": "dpi:" }, + "rotation": "Rotation", "layoutWarning": "Layout nicht erlaubt", "scale": "Maßstab", "includeScale": "in Druck einschließen", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index da89c8780c..c8d9d5caaf 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -704,6 +704,7 @@ "iconsSize": "Icons size:", "dpi": "Dpi:" }, + "rotation": "Rotation", "layoutWarning": "Not allowed layout", "scale": "Scale", "includeScale": "Include in print", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index eed58d283c..0dc09cca5e 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -704,6 +704,7 @@ "iconsSize": "Tamaño de los iconos:", "dpi": "ppp:" }, + "rotation": "Rotation", "layoutWarning": "Lienzo no permitido", "scale": "escala", "includeScale": "incluir en la impresión", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 29563dd488..e1f87eacf2 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -704,6 +704,7 @@ "iconsSize": "Taille d'icône :", "dpi": "Ppp :" }, + "rotation": "Rotation", "layoutWarning": "Mise en page non permise", "scale": "échelle", "includeScale": "inclure dans l'impression", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index bed7a10c0c..81be534871 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -704,6 +704,7 @@ "iconsSize": "Dimensione icone:", "dpi": "Dpi:" }, + "rotation": "Rotation", "layoutWarning": "Layout non consentito", "scale": "Scala", "includeScale": "Includi nella stampa", diff --git a/web/client/utils/PrintUtils.js b/web/client/utils/PrintUtils.js index 5d33b61b57..b2498c1785 100644 --- a/web/client/utils/PrintUtils.js +++ b/web/client/utils/PrintUtils.js @@ -31,6 +31,7 @@ import { printSpecificationSelector } from "../selectors/print"; import assign from 'object-assign'; import sortBy from "lodash/sortBy"; import head from "lodash/head"; +import isNil from "lodash/isNil"; import { getGridGeoJson } from "./grids/MapGridsUtils"; @@ -261,7 +262,7 @@ export const getMapfishPrintSpecification = (rawSpec, state) => { projectedCenter.y ], "scale": reprojectedScale, - "rotation": 0 + "rotation": !isNil(spec.rotation) ? -Number(spec.rotation) : 0 // negate the rotation value to match rotation in map preview and printed output } ], "legends": PrintUtils.getMapfishLayersSpecification(spec.layers, projectedSpec, state, 'legend'),