Skip to content

Commit 1a224d7

Browse files
authored
Default projection configuration for print projection selector (#11957)
1 parent b607877 commit 1a224d7

4 files changed

Lines changed: 140 additions & 11 deletions

File tree

web/client/plugins/Print.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import Portal from '../components/misc/Portal';
119119
* @prop {string[]} cfg.excludeLayersFromLegend list of layer names e.g. ["workspace:layerName"] to exclude from printed document
120120
* @prop {object} cfg.mergeableParams object to pass to mapfish-print v2 to merge params, example here https://github.com/mapfish/mapfish-print-v2/blob/main/docs/protocol.rst#printpdf
121121
* @prop {object[]} cfg.projectionOptions.projections array of available projections, e.g. [{"name": "EPSG:3857", "value": "EPSG:3857"}]
122+
* @prop {string} cfg.projectionOptions.defaultProjection default projection when the print dialog opens; should be one of the values from projections list
122123
* @prop {object} cfg.overlayLayersOptions options for overlay layers
123124
* @prop {boolean} cfg.overlayLayersOptions.enabled if true a checkbox will be shown to exclude or include overlay layers to the print
124125
*
@@ -191,7 +192,7 @@ import Portal from '../components/misc/Portal';
191192
* }
192193
*
193194
* @example
194-
* // enable custom projections for printing
195+
* // enable custom projections for printing; defaultProjection must be one of the values from projections list
195196
* "projectionDefs": [{
196197
* "code": "EPSG:23032",
197198
* "def": "+proj=utm +zone=32 +ellps=intl +towgs84=-87,-98,-121,0,0,0,0 +units=m +no_defs",
@@ -203,7 +204,8 @@ import Portal from '../components/misc/Portal';
203204
* "name": "Print",
204205
* "cfg": {
205206
* "projectionOptions": {
206-
* "projections": [{"name": "UTM32N", "value": "EPSG:23032"}, {"name": "EPSG:3857", "value": "EPSG:3857"}, {"name": "EPSG:4326", "value": "EPSG:4326"}]
207+
* "projections": [{"name": "UTM32N", "value": "EPSG:23032"}, {"name": "EPSG:3857", "value": "EPSG:3857"}, {"name": "EPSG:4326", "value": "EPSG:4326"}],
208+
* "defaultProjection": "EPSG:23032"
207209
* }
208210
* }
209211
* }

web/client/plugins/__tests__/print/Projection-test.jsx

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import expect from "expect";
44
import { getPluginForTest, getByXPath } from '../pluginsTestUtils';
55
import { getMapTransformerChain, getValidatorsChain, resetDefaultPrintingService } from "../../../utils/PrintUtils";
66

7-
import PrintProjection from "../../print/Projection";
7+
import PrintProjection, { projectionSelector } from "../../print/Projection";
88
import last from "lodash/last";
99
import ReactTestUtils from "react-dom/test-utils";
10+
import proj4 from "proj4";
11+
12+
const PROJECTION_DEF_EPSG_32122 = {
13+
code: "EPSG:32122",
14+
def: "+proj=lcc +lat_1=41.7 +lat_2=40.43333333333333 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
15+
};
1016

1117
const initialState = {
1218
controls: {
@@ -42,12 +48,20 @@ const initialState = {
4248
}
4349
};
4450

45-
function getPrintProjectionPlugin() {
51+
function getPrintProjectionPlugin(state = initialState) {
4652
return Promise.resolve(getPluginForTest(PrintProjection, {
47-
...initialState
53+
...initialState,
54+
...state
4855
}));
4956
}
5057

58+
const stateWithNoProjection = {
59+
print: {
60+
...initialState.print,
61+
map: { scale: 1784, scaleZoom: 2, zoom: 3 }
62+
}
63+
};
64+
5165
function callMapTransformer(state, callback) {
5266
setTimeout(() => {
5367
const map = last(getMapTransformerChain()).transformer(state, initialState.print.map);
@@ -62,6 +76,44 @@ function callValidator(state, callback) {
6276
}, 0);
6377
}
6478

79+
describe('projectionSelector', () => {
80+
beforeEach(() => {
81+
proj4.defs(PROJECTION_DEF_EPSG_32122.code, PROJECTION_DEF_EPSG_32122.def);
82+
});
83+
84+
afterEach(() => {
85+
delete proj4.defs[PROJECTION_DEF_EPSG_32122.code];
86+
});
87+
88+
it('returns params.projection when set', () => {
89+
const state = { print: { spec: { params: { projection: "EPSG:4326" } }, map: { projection: "EPSG:3857" } } };
90+
expect(projectionSelector(state)).toBe("EPSG:4326");
91+
expect(projectionSelector(state, "EPSG:32122")).toBe("EPSG:4326");
92+
});
93+
94+
it('returns map.projection when params.projection is not set', () => {
95+
const state = { print: { spec: { params: {} }, map: { projection: "EPSG:3857" } } };
96+
expect(projectionSelector(state)).toBe("EPSG:3857");
97+
expect(projectionSelector(state, "EPSG:32122")).toBe("EPSG:3857");
98+
});
99+
100+
it('returns defaultProjection when params and map projection are missing and defaultProjection is in availableCRS', () => {
101+
const state = { print: { spec: { params: {} }, map: {} } };
102+
expect(projectionSelector(state, "EPSG:32122")).toBe("EPSG:32122");
103+
});
104+
105+
it('returns EPSG:3857 when defaultProjection is not in availableCRS', () => {
106+
const state = { print: { spec: { params: {} }, map: {} } };
107+
expect(projectionSelector(state, "EPSG:99999")).toBe("EPSG:3857");
108+
});
109+
110+
it('returns EPSG:3857 when all are missing and no defaultProjection passed', () => {
111+
const state = { print: { spec: { params: {} }, map: {} } };
112+
expect(projectionSelector(state)).toBe("EPSG:3857");
113+
expect(projectionSelector(state, undefined)).toBe("EPSG:3857");
114+
});
115+
});
116+
65117
describe('PrintProjection Plugin', () => {
66118
beforeEach((done) => {
67119
document.body.innerHTML = '<div id="container"></div>';
@@ -186,3 +238,66 @@ describe('PrintProjection Plugin', () => {
186238
});
187239
});
188240
});
241+
242+
// defaultProjection must be defined in proj4 (e.g. EPSG:32122) for these tests
243+
describe('PrintProjection Plugin with defaultProjection', () => {
244+
beforeEach((done) => {
245+
document.body.innerHTML = '<div id="container"></div>';
246+
resetDefaultPrintingService();
247+
proj4.defs(PROJECTION_DEF_EPSG_32122.code, PROJECTION_DEF_EPSG_32122.def);
248+
setTimeout(done);
249+
});
250+
251+
afterEach((done) => {
252+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
253+
document.body.innerHTML = '';
254+
delete proj4.defs[PROJECTION_DEF_EPSG_32122.code];
255+
setTimeout(done);
256+
});
257+
258+
it('custom projections with default projection', (done) => {
259+
getPrintProjectionPlugin(stateWithNoProjection).then(({ Plugin }) => {
260+
try {
261+
ReactDOM.render(
262+
<Plugin
263+
projections={[{"name": "Ohio North", "value": "EPSG:32122"}, {"name": "WGS84", "value": "EPSG:4326"}, {"name": "Mercator", "value": "EPSG:3857"}]}
264+
defaultProjection="EPSG:32122"
265+
/>,
266+
document.getElementById("container")
267+
);
268+
expect(getByXPath("//*[text()='print.projection']")).toExist();
269+
expect(getByXPath("//option[@value='EPSG:32122']")).toExist();
270+
expect(getByXPath("//option[@value='EPSG:4326']")).toExist();
271+
expect(getByXPath("//option[@value='EPSG:3857']")).toExist();
272+
const select = getByXPath("//select");
273+
expect(select).toExist();
274+
expect(select.value).toBe("EPSG:32122");
275+
done();
276+
} catch (ex) {
277+
done(ex);
278+
}
279+
});
280+
});
281+
282+
it('map transformer uses defaultProjection when map has no projection', (done) => {
283+
getPrintProjectionPlugin(stateWithNoProjection).then(({ Plugin, store }) => {
284+
try {
285+
ReactDOM.render(
286+
<Plugin
287+
projections={[{"name": "Ohio North", "value": "EPSG:32122"}, {"name": "Mercator", "value": "EPSG:3857"}, {"name": "WGS84", "value": "EPSG:4326"}]}
288+
defaultProjection="EPSG:32122"
289+
/>,
290+
document.getElementById("container")
291+
);
292+
setTimeout(() => {
293+
callMapTransformer(store.getState(), (map) => {
294+
expect(map.projection).toBe('EPSG:32122');
295+
done();
296+
});
297+
}, 0);
298+
} catch (ex) {
299+
done(ex);
300+
}
301+
});
302+
});
303+
});

web/client/plugins/print/Projection.jsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ import {getScales} from "../../utils/MapUtils";
1212

1313
import { getAvailableCRS, normalizeSRS } from '../../utils/CoordinatesUtils';
1414

15-
export const projectionSelector = (state) => state?.print?.spec?.params?.projection ?? state?.print?.map?.projection ?? "EPSG:3857";
15+
/**
16+
* Returns the projection for print: from spec params, then map, then defaultProjection.
17+
* defaultProjection is used only if it exists in getAvailableCRS(); otherwise falls back to EPSG:3857.
18+
*/
19+
export const projectionSelector = (state, defaultProjection) => {
20+
const fromState = state?.print?.spec?.params?.projection ?? state?.print?.map?.projection;
21+
if (fromState) return fromState;
22+
const availableCRS = getAvailableCRS();
23+
const candidate = defaultProjection ?? "EPSG:3857";
24+
return Object.prototype.hasOwnProperty.call(availableCRS, candidate) ? candidate : "EPSG:3857";
25+
};
1626

1727
function mapTransformer(state, map) {
1828
const projection = projectionSelector(state);
@@ -104,6 +114,7 @@ Projection.contextTypes = {
104114
* by default. You can enable it again by setting this option to true.
105115
* @prop {object[]} cfg.projections optional list of projections to offer ({name: <description>, value: "EPSG:3003"})
106116
* is filtered by the available CRS in MapStore configuration.
117+
* @prop {string} cfg.defaultProjection default projection when the print dialog opens; should be one of the values from projections list.
107118
*
108119
* @example
109120
* // include the widget in the Print plugin right-panel container, after resolution
@@ -118,16 +129,17 @@ Projection.contextTypes = {
118129
* },
119130
* "cfg": {
120131
* "allowPreview": true,
121-
* "projections": [{"name": "WGS84", "value": "EPSG:4326"}, {"name": "Mercator", "value": "EPSG:3857"}]
132+
* "projections": [{"name": "WGS84", "value": "EPSG:4326"}, {"name": "Mercator", "value": "EPSG:3857"}],
133+
* "defaultProjection": "EPSG:4326"
122134
* }
123135
* }
124136
*/
125137
export default createPlugin("PrintProjection", {
126138
component: connect(
127-
(state) => ({
139+
(state, ownProps) => ({
128140
spec: state?.print?.spec || {},
129141
map: state?.print?.map,
130-
projection: projectionSelector(state),
142+
projection: projectionSelector(state, ownProps?.defaultProjection),
131143
items: Object.keys(getAvailableCRS()).map(p => ({
132144
name: p,
133145
value: p

web/client/plugins/print/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ export const OutputFormat = connect((state) => ({
7070
onChangeParameter: setPrintParameter
7171
})(OutputFormatComp);
7272

73-
export const Projection = connect((state) => ({
73+
export const Projection = connect((state, ownProps) => ({
7474
spec: state?.print?.spec || {},
7575
map: state?.print?.map,
76-
projection: projectionSelector(state),
76+
projection: projectionSelector(state, ownProps?.defaultProjection),
7777
items: Object.keys(getAvailableCRS()).map(p => ({
7878
name: p,
7979
value: p

0 commit comments

Comments
 (0)