diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx index 3e5550da1e9..069c8f51b20 100644 --- a/web/client/components/map/openlayers/Map.jsx +++ b/web/client/components/map/openlayers/Map.jsx @@ -353,14 +353,34 @@ class OpenlayersMap extends React.Component { * - custom grid set with custom extent. You need to customize the projection definition extent to make it work. * - custom grid set is partially supported by mapOptions.view.resolutions but this is not managed by projection change yet * - custom tile sizes - * + * ** NOTES**: If mapOptions.view.resolutions + mapOptions.view.projection are provided and match → will use them. + * - Else → will compute resolutions for provided/mapView projection. */ getResolutions = (srs) => { - if (this.props.mapOptions && this.props.mapOptions.view && this.props.mapOptions.view.resolutions) { - return this.props.mapOptions.view.resolutions; + // Resolve requested projection + const requestedProj = srs + ? getProjection(srs) + : (this.map?.getView()?.getProjection()); + const requestedSRS = normalizeSRS(srs || requestedProj.getCode()); + + // Check for explicitly configured resolutions + matching projection + const viewOptions = this.props?.mapOptions?.view || {}; + const configuredResolutions = viewOptions.resolutions; + const configuredResProjection = viewOptions.projection && normalizeSRS(viewOptions.projection); + + // If resolutions are explicitly configured *and* tied to a projection that matches our target, + // return them directly — avoids recomputation and ensures consistency with custom tile sources. + if ( + configuredResolutions && + Array.isArray(configuredResolutions) && + configuredResProjection && + requestedSRS === configuredResProjection + ) { + return configuredResolutions; } - const projection = srs ? getProjection(srs) : this.map.getView().getProjection(); - const extent = projection.getExtent(); + + // Otherwise compute dynamically + const extent = requestedProj.getExtent(); return getResolutionsForProjection( srs ?? this.map.getView().getProjection().getCode(), { @@ -513,15 +533,34 @@ class OpenlayersMap extends React.Component { }; createView = (center, zoom, projection, options, limits = {}) => { + const srs = normalizeSRS(projection); // limit has a crs defined const extent = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, normalizeSRS(projection)); - const newOptions = !options || (options && !options.view) ? Object.assign({}, options, { extent }) : Object.assign({}, options); + + // Determine whether to use configured resolutions + const configuredResolutions = options?.resolutions; + const configuredProj = normalizeSRS(options?.projection); + + let resolutionsToUse; + if (configuredResolutions && configuredProj === normalizeSRS(projection)) { + // use provided resolutions (keep backward compatibility) + resolutionsToUse = configuredResolutions; + } else { + // compute resolutions dynamically (e.g., EPSG:4326) + resolutionsToUse = this.getResolutions(normalizeSRS(projection)); + } + const newOptions = { + ...options, + projection: srs, + resolutions: resolutionsToUse, + extent: options?.extent !== undefined ? options.extent : extent + }; /* * setting the zoom level in the localConfig file is co-related to the projection extent(size) * it is recommended to use projections with the same coverage area (extent). If you want to have the same restricted zoom level (minZoom) */ const viewOptions = Object.assign({}, { - projection: normalizeSRS(projection), + projection: srs, center: [center.x, center.y], zoom: zoom, minZoom: limits.minZoom, @@ -530,8 +569,10 @@ class OpenlayersMap extends React.Component { // does not allow intermediary zoom levels // we need this at true to set correctly the scale box constrainResolution: true, - resolutions: this.getResolutions(normalizeSRS(projection)) - }, newOptions || {}); + resolutions: this.getResolutions(srs) + }, + newOptions || {} + ); return new View(viewOptions); }; diff --git a/web/client/components/map/openlayers/__tests__/Map-test.jsx b/web/client/components/map/openlayers/__tests__/Map-test.jsx index 7af254ce988..ec40f1c62b8 100644 --- a/web/client/components/map/openlayers/__tests__/Map-test.jsx +++ b/web/client/components/map/openlayers/__tests__/Map-test.jsx @@ -1404,7 +1404,7 @@ describe('OpenlayersMap', () => { , document.getElementById("map") @@ -1460,7 +1460,7 @@ describe('OpenlayersMap', () => { , document.getElementById("map") @@ -1523,4 +1523,41 @@ describe('OpenlayersMap', () => { // center is modified expect(map.map.getView().getCenter()).toEqual([10.3346773790, 43.9323234388]); }); + it('should correctly apply view projection without propagating to zoom changes', () => { + const resolutions = [0.0005, 0.0004, 0.0003, 0.0002]; + const map = ReactDOM.render( + , + document.getElementById("map") + ); + + const view = map.map.getView(); + expect(view.getProjection().getCode()).toBe('EPSG:4326'); + expect(view.getResolutions()).toEqual(resolutions); // Custom resolutions applied + + // Simulate a zoom change + view.setZoom(3); + expect(view.getProjection().getCode()).toBe('EPSG:4326'); + + // Simulate receiving new props with a different projection + ReactDOM.render( + , + document.getElementById("map") + ); + + const updatedView = map.map.getView(); + updatedView.setZoom(5); + expect(updatedView.getProjection().getCode()).toBe('EPSG:3857'); + expect(updatedView.getResolutions()).toNotEqual(resolutions); + }); + }); diff --git a/web/client/components/print/MapPreview.jsx b/web/client/components/print/MapPreview.jsx index 49a5d44b90a..0861f58de16 100644 --- a/web/client/components/print/MapPreview.jsx +++ b/web/client/components/print/MapPreview.jsx @@ -149,7 +149,8 @@ class MapPreview extends React.Component { let mapOptions = !isEmpty(resolutions) || !isNil(this.props.rotation) ? { view: { ...(!isEmpty(resolutions) && {resolutions}), - rotation: !isNil(this.props.rotation) ? Number(this.props.rotation) : 0 + rotation: !isNil(this.props.rotation) ? Number(this.props.rotation) : 0, + projection } } : {};