diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index 9580dcfb77..46213ec261 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -4,6 +4,12 @@ import type { ProjectionDefinition } from 'proj4'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); +// Redefining proj4 global projections to match epsg.org database axis order. +// See https://github.com/iTowns/itowns/pull/2465#issuecomment-2517024859 +proj4.defs('EPSG:4326').axis = 'neu'; +proj4.defs('EPSG:4269').axis = 'neu'; +proj4.defs('WGS84').axis = 'neu'; + /** * A projection as a CRS identifier string. This identifier references a * projection definition previously defined with @@ -139,6 +145,19 @@ export function reasonableEpsilon(crs: ProjectionLike) { } } +/** + * Returns the axis parameter defined in proj4 for the provided crs. + * Might be undefined depending on crs definition. + * + * @param crs - The CRS to get axis from. + * @returns the matching proj4 axis string, 'enu' for instance (east, north, up) + */ +export function axisOrder(crs: ProjectionLike) { + mustBeString(crs); + const projection = proj4.defs(crs); + return !projection ? undefined : projection.axis; +} + /** * Defines a proj4 projection as a named alias. * This function is a specialized wrapper over the diff --git a/src/Source/WMSSource.js b/src/Source/WMSSource.js index 7434e0bba6..41dab478b1 100644 --- a/src/Source/WMSSource.js +++ b/src/Source/WMSSource.js @@ -1,9 +1,38 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; +import * as CRS from 'Core/Geographic/Crs'; const _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); +/** + * Proj provides an optional param to define axis order and orientation for a + * given projection. 'enu' for instance stands for east, north, up. + * Elevation is not needed here. The two first characters are sufficient to map + * proj axis to iTowns bbox order formalism. + * 'enu' corresponds to 'wsen' because bbox starts by lower value coordinates + * and preserves axis ordering, here long/lat. + */ +const projAxisToBboxMappings = { + en: 'wsen', + es: 'wnes', + wn: 'eswn', + ws: 'enws', + ne: 'swne', + se: 'nwse', + nw: 'senw', + sw: 'nesw', +}; + +/** + * Provides the bbox axis order matching provided proj4 axis + * @param {string} projAxis the CRS axis order as defined in proj4 + * @returns {string} the corresponding bbox axis order to use for WMS 1.3.0 + */ +function projAxisToWmsBbox(projAxis) { + return projAxis && projAxisToBboxMappings[projAxis.slice(0, 2)] || 'wsen'; +} + /** * An object defining the source of images to get from a * [WMS](http://www.opengeospatial.org/standards/wms) server. It inherits @@ -104,13 +133,11 @@ class WMSSource extends Source { if (source.axisOrder) { this.axisOrder = source.axisOrder; - } else if (this.crs == 'EPSG:4326') { - // 4326 (lat/long) axis order depends on the WMS version used - // EPSG 4326 x = lat, long = y - // version 1.X.X long/lat while version 1.3.0 mandates xy (so lat,long) - this.axisOrder = this.version === '1.3.0' ? 'swne' : 'wsen'; + } else if (this.version === '1.3.0') { // If not set, axis order depends on WMS version + // Version 1.3.0 depends on CRS axis order as defined in epsg.org database + this.axisOrder = projAxisToWmsBbox(CRS.axisOrder(this.crs)); } else { - // xy,xy order + // Versions 1.X.X mandate long/lat order, east-north orientation this.axisOrder = 'wsen'; } diff --git a/test/unit/crs.js b/test/unit/crs.js index 4f9a1caff1..cf3e64b201 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -53,4 +53,10 @@ describe('CRS assertions', function () { assert.strictEqual(CRS.reasonableEpsilon('EPSG:4326'), 0.01); assert.strictEqual(CRS.reasonableEpsilon('EPSG:3857'), 0.001); }); + + it('should return neu axis order', function () { + assert.equal(CRS.axisOrder('WGS84'), 'neu'); + assert.equal(CRS.axisOrder('WGS84'), 'neu'); + assert.equal(CRS.axisOrder('EPSG:4269'), 'neu'); + }); });