diff --git a/rendering/cases/reproj-azimuthal-equal-area/expected.png b/rendering/cases/reproj-azimuthal-equal-area/expected.png new file mode 100644 index 0000000000..c41abcff6e Binary files /dev/null and b/rendering/cases/reproj-azimuthal-equal-area/expected.png differ diff --git a/rendering/cases/reproj-azimuthal-equal-area/main.js b/rendering/cases/reproj-azimuthal-equal-area/main.js new file mode 100644 index 0000000000..1dd05f2452 --- /dev/null +++ b/rendering/cases/reproj-azimuthal-equal-area/main.js @@ -0,0 +1,57 @@ +import ImageCanvas from '../../../src/ol/source/ImageCanvas.js'; +import ImageLayer from '../../../src/ol/layer/Image.js'; +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import proj4 from 'proj4'; +import {get as getProjection, transform} from '../../../src/ol/proj.js'; +import {register} from '../../../src/ol/proj/proj4.js'; + +const llpos = [-72, 40]; + +proj4.defs( + 'az', + '+proj=aeqd +lat_0=' + + llpos[1] + + ' +lon_0=' + + llpos[0] + + ' +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m' +); +register(proj4); +const aeqd = getProjection('az'); + +function canvasFunction(extent, resolution, pixelRatio, size) { + size = [Math.round(size[0]), Math.round(size[1])]; + const canvas = document.createElement('canvas'); + canvas.width = size[0]; + canvas.height = size[1]; + + // fill the canvas with blue + const ctx = canvas.getContext('2d'); + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + return canvas; +} + +const canvasLayer = new ImageLayer({ + source: new ImageCanvas({ + projection: 'EPSG:4326', + canvasFunction: canvasFunction, + }), +}); + +const center = transform(llpos, 'EPSG:4326', aeqd); + +new Map({ + pixelRatio: 1, + target: 'map', + layers: [canvasLayer], + view: new View({ + center: center, + projection: aeqd, + zoom: 0, + }), +}); + +render({ + tolerance: 0.001, +}); diff --git a/src/ol/proj.js b/src/ol/proj.js index 347d4fa017..9d59af3dc0 100644 --- a/src/ol/proj.js +++ b/src/ol/proj.js @@ -71,14 +71,7 @@ import { clear as clearTransformFuncs, get as getTransformFunc, } from './proj/transforms.js'; -import { - applyTransform, - getBottomLeft, - getBottomRight, - getTopLeft, - getTopRight, - getWidth, -} from './extent.js'; +import {applyTransform, getWidth} from './extent.js'; import {clamp, modulo} from './math.js'; import {getDistance} from './sphere.js'; import {getWorldsAway} from './coordinate.js'; @@ -636,56 +629,35 @@ export function fromUserExtent(extent, destProjection) { /** * Creates a safe coordinate transform function from a coordinate transform function. * "Safe" means that it can handle wrapping of x-coordinates for global projections, - * and that coordinates exceeding the projection validity extent's range will be + * and that coordinates exceeding the source projection validity extent's range will be * clamped to the validity range. * @param {Projection} sourceProj Source projection. * @param {Projection} destProj Destination projection. - * @param {function(import("./coordinate.js").Coordinate): import("./coordinate.js").Coordinate} forward Transform function (source to destiation). - * @param {function(import("./coordinate.js").Coordinate): import("./coordinate.js").Coordinate} inverse Transform function (destiation to source). + * @param {function(import("./coordinate.js").Coordinate): import("./coordinate.js").Coordinate} transform Transform function (source to destiation). * @return {function(import("./coordinate.js").Coordinate): import("./coordinate.js").Coordinate} Safe transform function (source to destiation). */ -export function createSafeCoordinateTransform( - sourceProj, - destProj, - forward, - inverse -) { +export function createSafeCoordinateTransform(sourceProj, destProj, transform) { return function (coord) { - let x, y, worldsAway; + let sourceX = coord[0]; + let sourceY = coord[1]; + let transformed, worldsAway; if (sourceProj.canWrapX()) { const sourceExtent = sourceProj.getExtent(); const sourceExtentWidth = getWidth(sourceExtent); worldsAway = getWorldsAway(coord, sourceProj, sourceExtentWidth); if (worldsAway) { // Move x to the real world - x = coord[0] - worldsAway * sourceExtentWidth; - } - } - let transformed = forward(x === undefined ? coord : [x, coord[1]]); - const destExtent = destProj.getExtent(); - if (!isFinite(transformed[0]) || !isFinite(transformed[1])) { - // Try to recover from out-of-bounds transform - if (destExtent) { - const corner1 = inverse(getBottomLeft(destExtent)); - const corner2 = inverse(getBottomRight(destExtent)); - const corner3 = inverse(getTopLeft(destExtent)); - const corner4 = inverse(getTopRight(destExtent)); - const minX = Math.min(corner1[0], corner2[0], corner3[0], corner4[0]); - const maxX = Math.max(corner1[0], corner2[0], corner3[0], corner4[0]); - const minY = Math.min(corner1[1], corner2[1], corner3[1], corner4[1]); - const maxY = Math.max(corner1[1], corner2[1], corner3[1], corner4[1]); - if (isFinite(minX) && isFinite(maxX)) { - x = clamp(x == undefined ? coord[1] : x, minX, maxX); - } - if (isFinite(minY) && isFinite(maxY)) { - y = clamp(coord[1], minY, maxY); - } - transformed = forward([x, y]); + sourceX = sourceX - worldsAway * sourceExtentWidth; } + sourceX = clamp(sourceX, sourceExtent[0], sourceExtent[2]); + sourceY = clamp(sourceY, sourceExtent[1], sourceExtent[3]); + transformed = transform([sourceX, sourceY]); + } else { + transformed = transform(coord); } if (worldsAway && destProj.canWrapX()) { // Move transformed coordinate back to the offset world - transformed[0] += worldsAway * getWidth(destExtent); + transformed[0] += worldsAway * getWidth(destProj.getExtent()); } return transformed; }; diff --git a/src/ol/proj/proj4.js b/src/ol/proj/proj4.js index f4fb255074..1726c5d7ce 100644 --- a/src/ol/proj/proj4.js +++ b/src/ol/proj/proj4.js @@ -61,18 +61,8 @@ export function register(proj4) { addCoordinateTransforms( proj1, proj2, - createSafeCoordinateTransform( - proj1, - proj2, - transform.forward, - transform.inverse - ), - createSafeCoordinateTransform( - proj2, - proj1, - transform.inverse, - transform.forward - ) + createSafeCoordinateTransform(proj1, proj2, transform.forward), + createSafeCoordinateTransform(proj2, proj1, transform.inverse) ); } } diff --git a/test/spec/ol/proj.test.js b/test/spec/ol/proj.test.js index bc5990256b..be4cd588e9 100644 --- a/test/spec/ol/proj.test.js +++ b/test/spec/ol/proj.test.js @@ -571,13 +571,18 @@ describe('ol.proj', function () { const epsg3857 = getProjection('EPSG:3857'); google.setExtent(epsg3857.getExtent()); google.setGlobal(true); - const coord = [-190, 90]; - const transformed = transform(coord, wgs84, google); - expect(transformed).to.eql(transform(coord, epsg4326, epsg3857)); - const got = transform(transformed, google, wgs84); - const expected = transform(transformed, epsg3857, epsg4326); - expect(got[0]).to.roughlyEqual(expected[0], 1e-9); - expect(got[1]).to.roughlyEqual(expected[1], 1e-9); + + const coord = [-190, 85]; + + let expected = transform(coord, wgs84, google); + let got = transform(coord, epsg4326, epsg3857); + expect(got[0]).to.roughlyEqual(expected[0], 1e-7); + expect(got[1]).to.roughlyEqual(expected[1], 1e-7); + + expected = transform(expected, google, wgs84); + got = transform(got, epsg3857, epsg4326); + expect(got[0]).to.roughlyEqual(expected[0], 1e-7); + expect(got[1]).to.roughlyEqual(expected[1], 1e-7); }); });