diff --git a/web/client/utils/MapUtils.js b/web/client/utils/MapUtils.js index c949e0cd4c..f0d3d716f8 100644 --- a/web/client/utils/MapUtils.js +++ b/web/client/utils/MapUtils.js @@ -8,6 +8,13 @@ const DEFAULT_SCREEN_DPI = 96; +const METERS_PER_UNIT = { + 'm': 1, + 'degrees': 111194.87428468118, + 'ft': 0.3048, + 'us-ft': 1200 / 3937 +}; + const GOOGLE_MERCATOR = { RADIUS: 6378137, TILE_WIDTH: 256, @@ -51,6 +58,16 @@ function dpi2dpm(dpi) { return dpi * (100 / 2.54); } +/** + * @param dpi {number} screen resolution in dots per inch. + * @param projection {string} map projection. + * @return {number} dots per map unit. + */ +function dpi2dpu(dpi, projection) { + const units = CoordinatesUtils.getUnits(projection || "EPSG:3857"); + return METERS_PER_UNIT[units] * dpi2dpm(dpi || DEFAULT_SCREEN_DPI); +} + /** * @param radius {number} Earth's radius of the model in meters. * @param tileWidth {number} width of the tiles used to draw the map. @@ -114,14 +131,22 @@ function getGoogleMercatorScales(minZoom, maxZoom, dpi) { ); } -function getResolutionsFromScales(scales, dpi) { - const dpm = dpi2dpm((dpi || DEFAULT_SCREEN_DPI)); - - return scales.map((scale) => scale / dpm); +/** + * @param scales {array} list of scales. + * @param projection {string} map projection. + * @param dpi {number} screen resolution in dots per inch. + * @return {array} a list of resolutions corresponding to the given scales, projection and dpi. + */ +function getResolutionsForScales(scales, projection, dpi) { + const dpu = dpi2dpu(dpi, projection); + const resolutions = scales.map((scale) => { + return scale / dpu; + }); + return resolutions; } function getGoogleMercatorResolutions(minZoom, maxZoom, dpi) { - return getResolutionsFromScales(getGoogleMercatorScales(minZoom, maxZoom, dpi), dpi); + return getResolutionsForScales(getGoogleMercatorScales(minZoom, maxZoom, dpi), "EPSG:3857", dpi); } function getResolutions() { @@ -132,9 +157,8 @@ function getResolutions() { } function getScales(projection, dpi) { - const units = CoordinatesUtils.getUnits(projection); - const dpm = dpi2dpm((dpi || DEFAULT_SCREEN_DPI)); - return getResolutions().map((resolution) => resolution * dpm * (units === 'degrees' ? 111194.87428468118 : 1)); + const dpu = dpi2dpu(dpi, projection); + return getResolutions().map((resolution) => resolution * dpu); } function defaultGetZoomForExtent(extent, mapSize, minZoom, maxZoom, dpi, mapResolutions) { @@ -145,8 +169,8 @@ function defaultGetZoomForExtent(extent, mapSize, minZoom, maxZoom, dpi, mapReso const yResolution = Math.abs(hExtent / mapSize.height); const extentResolution = Math.max(xResolution, yResolution); - const resolutions = mapResolutions || getResolutionsFromScales(getGoogleMercatorScales( - minZoom, maxZoom, (dpi || DEFAULT_SCREEN_DPI))); + const resolutions = mapResolutions || getResolutionsForScales(getGoogleMercatorScales( + minZoom, maxZoom, (dpi || DEFAULT_SCREEN_DPI)), "EPSG:3857", dpi); const {zoom, ...other} = resolutions.reduce((previous, resolution, index) => { const diff = Math.abs(resolution - extentResolution); @@ -258,6 +282,7 @@ module.exports = { getGoogleMercatorScales, getGoogleMercatorResolutions, getGoogleMercatorScale, + getResolutionsForScales, getZoomForExtent, defaultGetZoomForExtent, getCenterForExtent, diff --git a/web/client/utils/__tests__/MapUtils-test.js b/web/client/utils/__tests__/MapUtils-test.js index eba1c3d9b0..bb77951adf 100644 --- a/web/client/utils/__tests__/MapUtils-test.js +++ b/web/client/utils/__tests__/MapUtils-test.js @@ -19,6 +19,7 @@ var { getGoogleMercatorScales, getGoogleMercatorResolutions, getGoogleMercatorScale, + getResolutionsForScales, getZoomForExtent, getCenterForExtent, getBbox, @@ -46,6 +47,41 @@ describe('Test the MapUtils', () => { it('getGoogleMercatorScales', () => { expect(getGoogleMercatorScales(1, 1, 1).length).toBe(1); }); + it('getResolutionsForScales', () => { + // generate test scales for resolutions + function testScales(resolutions, dpu) { + return resolutions.map((res) => { + return res * dpu; + }); + } + + function dotsPerUnit(dpi, metersPerUnit) { + return metersPerUnit * dpi * 100 / 2.54; + } + + function resolutionsEqual(arrayA, arrayB) { + if (arrayA.length === arrayB.length) { + for (let i in arrayA) { + // check if absolute difference is within epsilon + if (Math.abs(arrayA[i] - arrayB[i]) > 1E-6) { + return false; + } + } + return true; + } + return false; + } + + const mPerDegree = 111194.87428468118; + let resolutions = [10000, 1000, 100, 10, 1]; + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(96, 1)), "EPSG:3857", 96), resolutions)).toBe(true); + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(96, mPerDegree)), "EPSG:4326", 96), resolutions)).toBe(true); + resolutions = [32000, 16000, 8000, 4000, 2000, 1000, 500, 250]; + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(96, 1)), "EPSG:3857", 96), resolutions)).toBe(true); + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(96, mPerDegree)), "EPSG:4326", 96), resolutions)).toBe(true); + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(120, 1)), "EPSG:3857", 120), resolutions)).toBe(true); + expect(resolutionsEqual(getResolutionsForScales(testScales(resolutions, dotsPerUnit(120, mPerDegree)), "EPSG:4326", 120), resolutions)).toBe(true); + }); it('getZoomForExtent without hook', () => { var extent = [1880758.3574092742, 6084533.340409827, 1291887.4915002766, 5606954.787684047]; var mapSize = {height: 781, width: 963};