Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #10739 Changing correctly resolutions limits when switching map CRS #10746

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions web/client/actions/__tests__/map-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
clickOnMap,
changeMousePointer,
changeZoomLevel,
changeCRS,
changeMapCrs,
changeMapScales,
changeMapStyle,
Expand All @@ -54,7 +55,7 @@ import {
updateMapOptions,
UPDATE_MAP_OPTIONS
} from '../map';

import {updateNode} from '../layers';

describe('Test correctness of the map actions', () => {

Expand Down Expand Up @@ -127,12 +128,50 @@ describe('Test correctness of the map actions', () => {

it('changes map crs', () => {
const testVal = 'EPSG:4326';
const retval = changeMapCrs(testVal);
const retval = changeCRS(testVal);

expect(retval).toExist();
expect(retval.type).toBe(CHANGE_MAP_CRS);
expect(retval.crs).toBe(testVal);
});
it('changes map crs and update resoolutions', () => {
const crs = 'EPSG:4326';
const thunk = changeMapCrs(crs);
expect(thunk).toExist();
const dispatchedActions = [];
const dispatch = (action) => {
dispatchedActions.push(action);
};
thunk(dispatch,
() => ({
layers: [ {
id: 'layer1',
minResolution: 2000,
maxResolution: 4000
},
{
id: 'layer2',
minResolution: 5000
}],
map: {present: {projection: "EPSG:3857"}}
}));
const expectedActions = [
updateNode('layer1', 'layer', { minResolution: 0.02197265625, maxResolution: 0.0439453125 }),
updateNode('layer2', 'layer', { minResolution: 0.0439453125 }),
changeCRS(crs)
];
for (let i = 0; i < expectedActions.length; i++) {
const expected = expectedActions[i];
const actual = dispatchedActions[i];
if (JSON.stringify(expected) !== JSON.stringify(actual)) {
throw new Error(
`Dispatched action at index ${i} does not match expected. \nExpected: ${JSON.stringify(
expected
)}\nActual: ${JSON.stringify(actual)}`
);
}
}
});

it('changeMapScales', () => {
const testScales = [100000, 50000, 25000, 10000, 5000];
Expand Down
48 changes: 45 additions & 3 deletions web/client/actions/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/

import { getResolutions, convertResolution } from '../utils/MapUtils';
import { layersSelector } from '../selectors/layers';
import { projectionSelector } from '../selectors/map';
import { updateNode } from '../actions/layers';
import minBy from 'lodash/minBy';

export const CHANGE_MAP_VIEW = 'CHANGE_MAP_VIEW';
export const CLICK_ON_MAP = 'CLICK_ON_MAP';
Expand Down Expand Up @@ -81,10 +86,47 @@ export function changeMapView(center, zoom, bbox, size, mapStateSource, projecti
};
}

export const changeCRS = (crs) => ({
type: CHANGE_MAP_CRS,
crs: crs
});
export function changeMapCrs(crs) {
return {
type: CHANGE_MAP_CRS,
crs: crs
return (dispatch, getState = () => {}) => {
const state = getState();
const sourceCRS = projectionSelector(state);
const layersWithLimits = layersSelector(state).filter(l => l.minResolution || l.maxResolution);
layersWithLimits.forEach(layer => {
const options = {};
const newResolutions = getResolutions(crs);
if (layer.minResolution) {
options.minResolution = convertResolution(sourceCRS, crs, layer.minResolution).transformedResolution;
const diffs = newResolutions.map((resolution, zoom) => ({ diff: Math.abs(resolution - options.minResolution), zoom }));
const { zoom } = minBy(diffs, 'diff');
options.minResolution = newResolutions[zoom];
}
if (layer.maxResolution) {
options.maxResolution = convertResolution(sourceCRS, crs, layer.maxResolution).transformedResolution;
const diffs = newResolutions.map((resolution, zoom) => ({ diff: Math.abs(resolution - options.maxResolution), zoom }));
const { zoom } = minBy(diffs, 'diff');
// check if min and max resolutions are not the same
options.maxResolution = newResolutions[zoom];
if (options.minResolution === options.maxResolution) {
if ((zoom - 1) >= 0) {
// increase max res if possible
options.maxResolution = newResolutions[zoom - 1];
} else if (zoom + 1 < newResolutions.length) {
// decrease max res if possible
options.minResolution = newResolutions[zoom + 1];
} else {
// keep only min res if none of the previous is happening
options.maxResolution = undefined;
}
}
}
// the minimum difference represents the nearest zoom to the target resolution
dispatch(updateNode(layer.id, "layer", options));
});
dispatch(changeCRS(crs));
};
}

Expand Down
3 changes: 2 additions & 1 deletion web/client/components/map/openlayers/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,8 @@ class OpenlayersMap extends React.Component {
multiWorld: true,
// does not allow intermediary zoom levels
// we need this at true to set correctly the scale box
constrainResolution: true
constrainResolution: true,
resolutions: this.getResolutions(normalizeSRS(projection))
}, newOptions || {});
return new View(viewOptions);
};
Expand Down
4 changes: 2 additions & 2 deletions web/client/epics/__tests__/map-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import expect from 'expect';

import { resetLimitsOnInit, zoomToExtentEpic, checkMapPermissions } from '../map';
import { CHANGE_MAP_VIEW, zoomToExtent, CHANGE_MAP_LIMITS, changeMapCrs } from '../../actions/map';
import { CHANGE_MAP_VIEW, zoomToExtent, CHANGE_MAP_LIMITS, changeCRS } from '../../actions/map';
import { LOAD_MAP_INFO, configureMap } from '../../actions/config';
import { testEpic, addTimeoutEpic, TEST_TIMEOUT } from './epicTestUtils';
import MapUtils from '../../utils/MapUtils';
Expand Down Expand Up @@ -226,7 +226,7 @@ describe('map epics', () => {
}
}
};
testEpic(resetLimitsOnInit, 1, changeMapCrs("EPSG:1234"), ([action]) => {
testEpic(resetLimitsOnInit, 1, changeCRS("EPSG:1234"), ([action]) => {
const { restrictedExtent, type, minZoom } = action;
expect(restrictedExtent.length).toBe(4);
expect(restrictedExtent).toEqual([1, 1, 1, 1]);
Expand Down
62 changes: 62 additions & 0 deletions web/client/utils/MapUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ import {
minBy,
omit
} from 'lodash';
import { get as getProjectionOL, getPointResolution, transform } from 'ol/proj';
import { get as getExtent } from 'ol/proj/projections';

import uuidv1 from 'uuid/v1';

import { getUnits, normalizeSRS, reproject } from './CoordinatesUtils';

import { getProjection } from './ProjectionUtils';

import { set } from './ImmutableUtils';
import {
saveLayer,
Expand Down Expand Up @@ -339,6 +343,64 @@ export function getScales(projection, dpi) {
const dpu = dpi2dpu(dpi, projection);
return getResolutions(projection).map((resolution) => resolution * dpu);
}

export function getScale(projection, dpi, resolution) {
const dpu = dpi2dpu(dpi, projection);
return resolution * dpu;
}
/**
* get random coordinates within CRS extent
* @param {string} crs the code of the projection for example EPSG:4346
* @returns {number[]} the point in [x,y] [lon,lat]
*/
export function getRandomPointInCRS(crs) {
const extent = getExtent(crs); // Get the projection's extent
if (!extent) {
throw new Error(`Extent not available for CRS: ${crs}`);
}
const [minX, minY, maxX, maxY] = extent.extent_;

// Check if the equator (latitude = 0) is within the CRS extent
const isEquatorWithinExtent = minY <= 0 && maxY >= 0;

// Generate a random X coordinate within the valid longitude range
const randomX = Math.random() * (maxX - minX) + minX;

// Set Y to 0 if the equator is within the extent, otherwise generate a random Y
const randomY = isEquatorWithinExtent ? 0 : Math.random() * (maxY - minY) + minY;

return [randomX, randomY];
}

/**
* convert resolution between CRSs
* @param {string} sourceCRS the code of a projection
* @param {string} targetCRS the code of a projection
* @param {number} sourceResolution the resolution to convert
* @returns the converted resolution
*/
export function convertResolution(sourceCRS, targetCRS, sourceResolution) {
const sourceProjection = getProjectionOL(sourceCRS);
const targetProjection = getProjectionOL(targetCRS);

if (!sourceProjection || !targetProjection) {
throw new Error(`Invalid CRS: ${sourceCRS} or ${targetCRS}`);
}

// Get a random point in the extent of the source CRS
const randomPoint = getRandomPointInCRS(sourceCRS);

// Transform the resolution
const transformedResolution = getPointResolution(
sourceProjection,
sourceResolution,
transform(randomPoint, sourceCRS, targetCRS),
targetProjection.getUnits()
);

return { randomPoint, transformedResolution };
}

/**
* Convert a resolution to the nearest zoom
* @param {number} targetResolution resolution to be converted in zoom
Expand Down
12 changes: 11 additions & 1 deletion web/client/utils/__tests__/MapUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ import {
mapUpdated,
getZoomFromResolution,
getResolutionObject,
reprojectZoom
reprojectZoom,
getRandomPointInCRS,
convertResolution
} from '../MapUtils';
import { VisualizationModes } from '../MapTypeUtils';

Expand Down Expand Up @@ -2416,6 +2418,7 @@ describe('Test the MapUtils', () => {
const resolution = 1000; // ~zoom 7 in Web Mercator
expect(getZoomFromResolution(resolution)).toBe(7);
});

it('reprojectZoom', () => {
expect(reprojectZoom(5, 'EPSG:3857', 'EPSG:4326')).toBe(4);
expect(reprojectZoom(5.2, 'EPSG:3857', 'EPSG:4326')).toBe(4);
Expand All @@ -2431,4 +2434,11 @@ describe('Test the MapUtils', () => {
.toEqual({ resolution: 9028, scale: 34121574.80314961, zoom: 4 });
});
});
it('getRandomPointInCRS', () => {
expect(getRandomPointInCRS('EPSG:3857').length).toBe(2);
expect(getRandomPointInCRS('EPSG:4326').length).toBe(2);
});
it('convertResolution', () => {
expect(convertResolution('EPSG:3857', 'EPSG:4326', 2000).transformedResolution).toBe(0.017986440587896155);
});
});
Loading