diff --git a/web/client/actions/__tests__/print-test.js b/web/client/actions/__tests__/print-test.js index e71105efc0..b2fab7a1ad 100644 --- a/web/client/actions/__tests__/print-test.js +++ b/web/client/actions/__tests__/print-test.js @@ -88,6 +88,21 @@ describe('Test correctness of the print actions', () => { expect(retVal.projection).toBe('EPSG:4326'); expect(retVal.currentLocale).toBe('en-US'); }); + it('configurePrintMap with useFixedScales', () => { + const retVal = configurePrintMap({x: 1, y: 1}, 5, 6, 2.0, [], 'EPSG:4326', 'en-US', true); + expect(retVal).toExist(); + expect(retVal.type).toBe(CONFIGURE_PRINT_MAP); + expect(retVal.center).toExist(); + expect(retVal.center.x).toBe(1); + expect(retVal.zoom).toBe(5); + expect(retVal.scaleZoom).toBe(6); + expect(retVal.scale).toBe(2.0); + expect(retVal.layers).toExist(); + expect(retVal.layers.length).toBe(0); + expect(retVal.projection).toBe('EPSG:4326'); + expect(retVal.currentLocale).toBe('en-US'); + expect(retVal.useFixedScales).toBe(true); + }); it('changePrintZoomLevel', () => { const retVal = changePrintZoomLevel(5, 10000); diff --git a/web/client/actions/print.js b/web/client/actions/print.js index 6c54fbb25b..6467727342 100644 --- a/web/client/actions/print.js +++ b/web/client/actions/print.js @@ -132,7 +132,7 @@ export function printTransformerAdded(name) { }; } -export function configurePrintMap(center, zoom, scaleZoom, scale, layers, projection, currentLocale) { +export function configurePrintMap(center, zoom, scaleZoom, scale, layers, projection, currentLocale, useFixedScales) { return { type: CONFIGURE_PRINT_MAP, center, @@ -141,7 +141,8 @@ export function configurePrintMap(center, zoom, scaleZoom, scale, layers, projec scale, layers, projection, - currentLocale + currentLocale, + useFixedScales }; } diff --git a/web/client/components/print/MapPreview.jsx b/web/client/components/print/MapPreview.jsx index b6aee50810..475a80aa85 100644 --- a/web/client/components/print/MapPreview.jsx +++ b/web/client/components/print/MapPreview.jsx @@ -11,12 +11,13 @@ import PropTypes from 'prop-types'; import React from 'react'; import { Glyphicon } from 'react-bootstrap'; -import { getMapZoom, getResolutionMultiplier } from '../../utils/PrintUtils'; +import { getResolutionMultiplier } from '../../utils/PrintUtils'; import ScaleBox from '../mapcontrols/scale/ScaleBox'; import Button from '../misc/Button'; import isNil from 'lodash/isNil'; import isEmpty from 'lodash/isEmpty'; import { MapLibraries } from '../../utils/MapTypeUtils'; +import { get } from 'ol/proj'; let PMap; let Layer; @@ -83,23 +84,28 @@ class MapPreview extends React.Component { } }); } - componentWillUnmount() { this._isMounted = false; } getRatio = () => { - if (this.props.width && this.props.layoutSize && this.props.resolutions) { + if (this.props.width && this.props.layoutSize ) { return getResolutionMultiplier(this.props.layoutSize.width, this.props.width, this.props.printRatio); } return 1; }; - getResolutions = () => { - if (this.props.width && this.props.layoutSize && this.props.resolutions) { - return this.props.resolutions.map((resolution) => resolution * this.getRatio()); + getResolutions = (srs) => { + // cache resolutions + const projection = get(srs); + const metersPerUnit = projection.getMetersPerUnit(); + const scaleToResolution = s => s * 0.28E-3 / metersPerUnit; + const previewResolutions = this.props.useFixedScales && this.props.scales + ? this.props.scales.map(s => scaleToResolution(s)) : this.props.resolutions; + if (this.props.width && this.props.layoutSize && previewResolutions) { + return previewResolutions.map((resolution) => resolution * this.getRatio()); } - return this.props.resolutions; + return previewResolutions; }; renderLayerContent = (layer, projection) => { @@ -134,14 +140,15 @@ class MapPreview extends React.Component { width: this.props.width + "px", height: this.props.height + "px" }); - const resolutions = this.getResolutions(); + const projection = this.props.map && this.props.map.projection || 'EPSG:3857'; + const resolutions = this.getResolutions(projection); let mapOptions = !isEmpty(resolutions) || !isNil(this.props.rotation) ? { view: { ...(!isEmpty(resolutions) && {resolutions}), rotation: !isNil(this.props.rotation) ? Number(this.props.rotation) : 0 } } : {}; - const projection = this.props.map && this.props.map.projection || 'EPSG:3857'; + return this.props.map && this.props.map.center ?
{ scales={[100, 1000, 10000, 100000]} onLoadingMapPlugins={(loading) => { if (!loading) { - expect(cmp.refs.mappa.props.zoom).toBe(13); + expect(cmp.refs.mappa.props.zoom).toBe(3); done(); } }} diff --git a/web/client/plugins/Print.jsx b/web/client/plugins/Print.jsx index 937c75c5c4..42ced98c43 100644 --- a/web/client/plugins/Print.jsx +++ b/web/client/plugins/Print.jsx @@ -29,9 +29,9 @@ import { layersSelector } from '../selectors/layers'; import { currentLocaleSelector } from '../selectors/locale'; import { mapSelector, scalesSelector } from '../selectors/map'; import { mapTypeSelector } from '../selectors/maptype'; -import { normalizeSRS, reprojectBbox, convertDegreesToRadian } from '../utils/CoordinatesUtils'; +import { normalizeSRS, convertDegreesToRadian } from '../utils/CoordinatesUtils'; import { getMessageById } from '../utils/LocaleUtils'; -import { defaultGetZoomForExtent, getResolutions, mapUpdated, dpi2dpu, DEFAULT_SCREEN_DPI } from '../utils/MapUtils'; +import { defaultGetZoomForExtent, getResolutions, mapUpdated, dpi2dpu, DEFAULT_SCREEN_DPI, getScales, reprojectZoom } from '../utils/MapUtils'; import { isInsideResolutionsLimits } from '../utils/LayersUtils'; import { has, includes } from 'lodash'; import {additionalLayersSelector} from "../selectors/additionallayers"; @@ -289,7 +289,8 @@ export default { overrideOptions: PropTypes.object, items: PropTypes.array, addPrintParameter: PropTypes.func, - printingService: PropTypes.object + printingService: PropTypes.object, + printMap: PropTypes.object }; static contextTypes = { @@ -340,7 +341,8 @@ export default { currentLocale: 'en-US', overrideOptions: {}, items: [], - printingService: getDefaultPrintingService() + printingService: getDefaultPrintingService(), + printMap: {} }; state = { @@ -569,34 +571,30 @@ export default { const { map: newMap, capabilities, - minZoom, configurePrintMap: configurePrintMapProp, useFixedScales, - getZoomForExtent, - maxZoom, currentLocale, - scales: scalesProp, - layers + layers, + printMap, + printSpec } = props || this.props; if (newMap && newMap.bbox && capabilities) { - const bbox = reprojectBbox([ - newMap.bbox.bounds.minx, - newMap.bbox.bounds.miny, - newMap.bbox.bounds.maxx, - newMap.bbox.bounds.maxy - ], newMap.bbox.crs, newMap.projection); - const mapSize = this.getMapSize(); + const selectedPrintProjection = (printSpec && printSpec?.params?.projection) || (printSpec && printSpec?.projection) || (printMap && printMap.projection) || 'EPSG:3857'; + const printSrs = normalizeSRS(selectedPrintProjection); + const mapProjection = newMap.projection; + const mapSrs = normalizeSRS(mapProjection); + const zoom = reprojectZoom(newMap.zoom, mapSrs, printSrs); + const scales = getPrintScales(capabilities); + const printMapScales = getScales(printSrs); + const scaleZoom = getNearestZoom(zoom, scales, printMapScales); if (useFixedScales) { - const mapZoom = getZoomForExtent(bbox, mapSize, minZoom, maxZoom); - const scales = getPrintScales(capabilities); - const scaleZoom = getNearestZoom(newMap.zoom, scales); const scale = scales[scaleZoom]; - configurePrintMapProp(newMap.center, mapZoom, scaleZoom, scale, - layers, newMap.projection, currentLocale); + configurePrintMapProp(newMap.center, zoom, scaleZoom, scale, + layers, newMap.projection, currentLocale, useFixedScales); } else { - const scale = scalesProp[newMap.zoom]; - configurePrintMapProp(newMap.center, newMap.zoom, newMap.zoom, scale, - layers, newMap.projection, currentLocale); + const scale = printMapScales[zoom]; + configurePrintMapProp(newMap.center, zoom, scaleZoom, scale, + layers, newMap.projection, currentLocale, useFixedScales); } } }; @@ -629,8 +627,9 @@ export default { scalesSelector, (state) => state.browser && (!state.browser.ie || state.browser.ie11), currentLocaleSelector, - mapTypeSelector - ], (open, capabilities, printSpec, pdfUrl, error, map, layers, additionalLayers, scales, usePreview, currentLocale, mapType) => ({ + mapTypeSelector, + (state) => state.print.map + ], (open, capabilities, printSpec, pdfUrl, error, map, layers, additionalLayers, scales, usePreview, currentLocale, mapType, printMap) => ({ open, capabilities, printSpec, @@ -650,7 +649,8 @@ export default { scales, usePreview, currentLocale, - mapType + mapType, + printMap })); const PrintPlugin = connect(selector, { diff --git a/web/client/plugins/__tests__/Print-test.jsx b/web/client/plugins/__tests__/Print-test.jsx index c410cc1e8e..6e13211d02 100644 --- a/web/client/plugins/__tests__/Print-test.jsx +++ b/web/client/plugins/__tests__/Print-test.jsx @@ -202,6 +202,90 @@ describe('Print Plugin', () => { }); }); + it('test configuration with useFixedScales and enableScalebox = true', (done) => { + const printingService = { + getMapConfiguration() { + return { + layers: [], + center: { + x: 0, + y: 0, + crs: "EPSG:4326" + } + }; + }, + validate() { return {};} + }; + getPrintPlugin({ + state: {...initialState, + print: {...initialState.print, + capabilities: {...initialState.print.capabilities, + scales: [1000000, 500000, 100000].map(value => ({name: value, value}))} + }} + }).then(({ Plugin }) => { + try { + ReactDOM.render(, document.getElementById("container")); + const comp = document.getElementById("container"); + ReactTestUtils.act(() => new Promise((resolve) => resolve(comp))).then(()=>{ + expect(comp).toExist(); + const scaleBoxComp = document.querySelector("#mappreview-scalebox select"); + expect(scaleBoxComp).toExist(); + done(); + }); + } catch (ex) { + done(ex); + } + }); + }); + it('test configuration with useFixedScales and enableScalebox = false', (done) => { + const printingService = { + getMapConfiguration() { + return { + layers: [], + center: { + x: 0, + y: 0, + crs: "EPSG:4326" + } + }; + }, + validate() { return {};} + }; + getPrintPlugin({ + state: {...initialState, + print: {...initialState.print, + capabilities: {...initialState.print.capabilities, + scales: [1000000, 500000, 100000].map(value => ({name: value, value}))} + }} + }).then(({ Plugin }) => { + try { + ReactDOM.render(, document.getElementById("container")); + const comp = document.getElementById("container"); + ReactTestUtils.act(() => new Promise((resolve) => resolve(comp))).then(()=>{ + expect(comp).toExist(); + const scaleBoxComp = document.querySelector("#mappreview-scalebox select"); + expect(scaleBoxComp).toNotExist(); + done(); + }); + } catch (ex) { + done(ex); + } + }); + }); it('default configuration with not allowed layers', (done) => { getPrintPlugin({ layers: [{visibility: true, type: "bing"}] diff --git a/web/client/plugins/__tests__/print/Projection-test.jsx b/web/client/plugins/__tests__/print/Projection-test.jsx index 18d83c751f..a929e9bfed 100644 --- a/web/client/plugins/__tests__/print/Projection-test.jsx +++ b/web/client/plugins/__tests__/print/Projection-test.jsx @@ -24,7 +24,8 @@ const initialState = { map: { scale: 1784, scaleZoom: 2, - projection: "EPSG:3857" + projection: "EPSG:3857", + zoom: 3 }, capabilities: { createURL: "http://fakeservice", @@ -121,16 +122,18 @@ describe('PrintProjection Plugin', () => { it('map transformer with user chosen crs', (done) => { getPrintProjectionPlugin().then(({ Plugin, store }) => { try { - ReactDOM.render(, document.getElementById("container")); - const combo = getByXPath("//select"); - ReactTestUtils.Simulate.change(combo, { - target: { - value: "EPSG:4326" - } - }); - callMapTransformer(store.getState(), (map) => { - expect(map.projection).toBe('EPSG:4326'); - done(); + const comp = ReactDOM.render(, document.getElementById("container")); + ReactTestUtils.act(() => new Promise((resolve) => resolve(comp))).then(() => { + const combo = getByXPath("//select"); + ReactTestUtils.Simulate.change(combo, { + target: { + value: "EPSG:4326" + } + }); + callMapTransformer(store.getState(), (map) => { + expect(map.projection).toBe('EPSG:4326'); + done(); + }); }); } catch (ex) { done(ex); @@ -141,18 +144,20 @@ describe('PrintProjection Plugin', () => { it('validator without allowPreview', (done) => { getPrintProjectionPlugin().then(({ Plugin, store }) => { try { - ReactDOM.render(, document.getElementById("container")); - const combo = getByXPath("//select"); - ReactTestUtils.Simulate.change(combo, { - target: { - value: "EPSG:4326" - } - }); - callValidator(store.getState(), (validation) => { - expect(validation).toExist(); - expect(validation.valid).toBe(false); - expect(validation.errors.length).toBe(1); - done(); + const comp = ReactDOM.render(, document.getElementById("container")); + ReactTestUtils.act(() => new Promise((resolve) => resolve(comp))).then(() => { + const combo = getByXPath("//select"); + ReactTestUtils.Simulate.change(combo, { + target: { + value: "EPSG:4326" + } + }); + callValidator(store.getState(), (validation) => { + expect(validation).toExist(); + expect(validation.valid).toBe(false); + expect(validation.errors.length).toBe(1); + done(); + }); }); } catch (ex) { done(ex); diff --git a/web/client/plugins/print/Projection.jsx b/web/client/plugins/print/Projection.jsx index 86175f72a9..6d687cd703 100644 --- a/web/client/plugins/print/Projection.jsx +++ b/web/client/plugins/print/Projection.jsx @@ -8,7 +8,7 @@ import { setPrintParameter } from "../../actions/print"; import printReducer from "../../reducers/print"; import Choice from "../../components/print/Choice"; import { getMessageById } from '../../utils/LocaleUtils'; -import {getScales, reprojectZoom} from "../../utils/MapUtils"; +import {getScales} from "../../utils/MapUtils"; import { getAvailableCRS, normalizeSRS } from '../../utils/CoordinatesUtils'; @@ -16,21 +16,19 @@ export const projectionSelector = (state) => state?.print?.spec?.params?.project function mapTransformer(state, map) { const projection = projectionSelector(state); - const mapProjection = mapProjectionSelector(state); const srs = normalizeSRS(projection); + const scales = getScales(srs); + const mapProjection = mapProjectionSelector(state); const mapSrs = normalizeSRS(mapProjection); if (srs !== mapSrs) { - const zoom = reprojectZoom(map.scaleZoom, mapSrs, srs); - const scales = getScales(srs); return { ...map, - zoom: zoom, - scaleZoom: zoom, - scale: scales[zoom], + scale: scales[map.zoom], + zoom: map.zoom, projection: srs }; } - return map; + return {...map, scale: scales[map.zoom], zoom: map.zoom}; } const validator = (allowPreview) => (state) => { @@ -67,15 +65,17 @@ export const Projection = ({ addValidator("projection", "map-preview", validator(allowPreview)); } }, [allowPreview]); + function changeProjection(crs) { + onChangeParameter("params.projection", crs); + onRefresh(); + } useEffect(() => { if (enabled) { + changeProjection(projection); addMapTransformer("projection", mapTransformer); } }, []); - function changeProjection(crs) { - onChangeParameter("params.projection", crs); - onRefresh(); - } + return enabled ? ( <> { expect(state.map.layers.length).toBe(0); expect(state.map.projection).toBe('EPSG:4326'); }); + it('configure print map with useFixedScales = true', () => { + const state = print({capabilities: {}, spec: {}}, { + type: CONFIGURE_PRINT_MAP, + center: {x: 1, y: 1}, + zoom: 5, + scaleZoom: 6, + scale: 10000, + layers: [], + projection: 'EPSG:4326', + useFixedScales: true + }); + expect(state.map).toExist(); + expect(state.map.center).toExist(); + expect(state.map.center.x).toBe(1); + expect(state.map.zoom).toBe(5); + expect(state.map.scale).toBe(10000); + expect(state.map.layers.length).toBe(0); + expect(state.map.projection).toBe('EPSG:4326'); + expect(state.map.useFixedScales).toBe(true); + }); it('configure print map title', () => { const state = print({capabilities: {}, spec: {}}, { diff --git a/web/client/reducers/print.js b/web/client/reducers/print.js index c00fa47ace..a904d391a5 100644 --- a/web/client/reducers/print.js +++ b/web/client/reducers/print.js @@ -97,7 +97,8 @@ function print(state = {spec: initialSpec, capabilities: null, map: null, isLoad scale: action.scale, layers, size: action.size ?? state.map?.size, - projection: action.projection + projection: action.projection, + useFixedScales: action.useFixedScales }, error: null } @@ -108,7 +109,7 @@ function print(state = {spec: initialSpec, capabilities: null, map: null, isLoad return assign({}, state, { map: assign({}, state.map, { scaleZoom: action.zoom, - zoom: state.map.zoom + diff, + zoom: state.map.zoom + diff >= 0 ? state.map.zoom + diff : 0, scale: action.scale }) } diff --git a/web/client/utils/PrintUtils.js b/web/client/utils/PrintUtils.js index 0d0d9d10bb..ad56a0722d 100644 --- a/web/client/utils/PrintUtils.js +++ b/web/client/utils/PrintUtils.js @@ -9,7 +9,7 @@ import { reproject, getUnits, reprojectGeoJson, normalizeSRS } from './CoordinatesUtils'; import {addAuthenticationParameter} from './SecurityUtils'; -import { calculateExtent, getGoogleMercatorScales, getResolutionsForProjection, getScales, reprojectZoom } from './MapUtils'; +import { calculateExtent, getGoogleMercatorScales, getResolutionsForProjection, getScales } from './MapUtils'; import { optionsToVendorParams } from './VendorParamsUtils'; import { colorToHexStr } from './ColorUtils'; import { getLayerConfig } from './TileConfigProvider'; @@ -236,9 +236,10 @@ export const mapProjectionSelector = (state) => state?.print?.map?.projection ?? export const getMapfishPrintSpecification = (rawSpec, state) => { const {params, ...baseSpec} = rawSpec; const spec = {...baseSpec, ...params}; - const mapProjection = mapProjectionSelector(state); + const printMap = state?.print?.map; const projectedCenter = reproject(spec.center, 'EPSG:4326', spec.projection); - const projectedZoom = Math.round(reprojectZoom(spec.scaleZoom, mapProjection, spec.projection)); + // * use [spec.zoom] the actual zoom in case useFixedScale = false else use [spec.scaleZoom] the fixed zoom scale not actual + const projectedZoom = Math.round(printMap?.useFixedScales ? spec.scaleZoom : spec.zoom); const scales = spec.scales || getScales(spec.projection); const reprojectedScale = scales[projectedZoom] || defaultScales[projectedZoom]; diff --git a/web/client/utils/__tests__/PrintUtils-test.js b/web/client/utils/__tests__/PrintUtils-test.js index ad9d53d65d..b43b0ca26a 100644 --- a/web/client/utils/__tests__/PrintUtils-test.js +++ b/web/client/utils/__tests__/PrintUtils-test.js @@ -34,7 +34,7 @@ import { KVP1, REST1 } from '../../test-resources/layers/wmts'; import { poi as TMS110_1 } from '../../test-resources/layers/tms'; import { BasemapAT, NASAGIBS, NLS_CUSTOM_URL, LINZ_CUSTOM_URL } from '../../test-resources/layers/tileprovider'; import { setStore } from '../StateUtils'; -import { getGoogleMercatorScales } from '../MapUtils'; +import { getGoogleMercatorScales, getScales } from '../MapUtils'; const layer = { url: "http://mygeoserver", @@ -554,18 +554,51 @@ describe('PrintUtils', () => { ...testSpec, scaleZoom: 3, scales: [2000000, 1000000, 500000, 100000, 50000] + }, { + print: { + map: { + useFixedScales: true + } + } }); expect(printSpec).toExist(); expect(printSpec.pages[0].scale).toBe(100000); }); - it('getMapfishPrintSpecification with standard scales', () => { + it('getMapfishPrintSpecification with standard scales for print map with projection 3857 [google web mercator]', () => { const printSpec = getMapfishPrintSpecification({ ...testSpec, - scaleZoom: 3 + zoom: 3 }); expect(printSpec).toExist(); expect(printSpec.pages[0].scale).toBe(getGoogleMercatorScales(0, 21)[3]); }); + it('getMapfishPrintSpecification with fixed scales for print map with projection 4326', () => { + const projection = 'EPSG:4326'; + const printSpec = getMapfishPrintSpecification({ + ...testSpec, + projection, + scaleZoom: 3, + scales: [2000000, 1000000, 500000, 100000, 50000] + }, { + print: { + map: { + useFixedScales: true + } + } + }); + expect(printSpec).toExist(); + expect(printSpec.pages[0].scale).toBe(100000); + }); + it('getMapfishPrintSpecification with standard scales for print map with projection 4326', () => { + const projection = 'EPSG:4326'; + const printSpec = getMapfishPrintSpecification({ + ...testSpec, + zoom: 3, + projection + }); + expect(printSpec).toExist(); + expect(printSpec.pages[0].scale).toBe(getScales(projection)[3]); + }); it('from rgba to rgb', () => { const rgb = rgbaTorgb("rgba(255, 255, 255, 0.1)"); expect(rgb).toExist();