From 3d9ae120e19802785f3bdb089df4ce05d8934291 Mon Sep 17 00:00:00 2001 From: Adrien Berthet Date: Fri, 17 May 2019 10:53:21 +0200 Subject: [PATCH] fix(tms): return the correct coordinates for a TMS source When using a GlobeView and a TMSSource, the coordinates returned for a tile were only Pseudo-Mercator; it can be WGS84G now. --- src/Core/Geographic/Extent.js | 4 +- src/Core/Geographic/Projection.js | 120 ++++++++---------- src/Core/Prefab/Globe/BuilderEllipsoidTile.js | 8 +- src/Core/Prefab/GlobeView.js | 5 + src/Core/TileMesh.js | 41 ++---- src/Layer/TiledGeometryLayer.js | 4 +- src/Process/FeatureProcessing.js | 2 +- src/Process/LayeredMaterialNodeProcessing.js | 4 +- src/Provider/OGCWebServiceHelper.js | 20 --- test/unit/layeredmaterial.js | 2 +- 10 files changed, 82 insertions(+), 128 deletions(-) diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index 449fa65b49..3a53eb5036 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -125,8 +125,8 @@ class Extent { } else { const size = 360 / nbCol; // convert Y PM to latitude EPSG:4326 degree - const north = Projection.YToWGS84(Yn); - const south = Projection.YToWGS84(Ys); + const north = Projection.y_PMTolatitude(Yn); + const south = Projection.y_PMTolatitude(Ys); // convert column PM to longitude EPSG:4326 degree const west = 180 - size * (nbCol - this.col); const east = west + size; diff --git a/src/Core/Geographic/Projection.js b/src/Core/Geographic/Projection.js index 92db718474..273b2abaad 100644 --- a/src/Core/Geographic/Projection.js +++ b/src/Core/Geographic/Projection.js @@ -12,6 +12,10 @@ const PI_OV_TWO = Math.PI / 2; const INV_TWO_PI = 1.0 / (Math.PI * 2); const LOG_TWO = Math.log(2.0); +// TODO: Clamp to 85.0511288° because: +// longitude 180° to X EPSG:3857 = 20037508.34m +// To get a square, we convert Y 20037508.34m to latitude, we get 85.0511288° +// Change it when we will change worldDimension3857 in Extent.js function WGS84LatitudeClamp(latitude) { return Math.min(84, Math.max(-86, latitude)); } @@ -21,96 +25,72 @@ const center = new Coordinates('EPSG:4326', 0, 0, 0); const Projection = { /** - * Convert latitude to y coordinate in TileMatrixSet - * @param {number} latitude - latitude in degrees - * @return {number} + * Convert latitude to y coordinate in pseudo mercator (EPSG:3857) + * @param {number} latitude - latitude in degrees (EPSG:4326) + * @return {number} y coordinate in pseudo mercator */ - WGS84ToY(latitude) { + latitudeToY_PM(latitude) { return 0.5 - Math.log(Math.tan(PI_OV_FOUR + MathExt.degToRad(latitude) * 0.5)) * INV_TWO_PI; }, /** - * Convert from y coordinate in TileMatrixSet to WGS84 latitude - * @param {number} y - coords in TileMatrixSet - * @return {number} - latitude in degrees + * Convert from y coordinate pseudo mercator (EPSG:3857) to latitude + * @param {number} y - y coordinate in pseudo mercator + * @return {number} - latitude in degrees (EPSG:4326) */ - YToWGS84(y) { - return MathExt.radToDeg( - 2 * (Math.atan(Math.exp(-(y - 0.5) / INV_TWO_PI)) - PI_OV_FOUR)); + y_PMTolatitude(y) { + return MathExt.radToDeg(2 * (Math.atan(Math.exp(-(y - 0.5) / INV_TWO_PI)) - PI_OV_FOUR)); }, - getCoordWMTS_WGS84(tileCoord, bbox, tileMatrixSet) { - // TODO: PM, WGS84G are hard-coded reference to IGN's TileMatrixSet - if (tileMatrixSet === 'PM') { - return WMTS_WGS84ToWMTS_PM(tileCoord, bbox); - } else if (tileMatrixSet === 'WGS84G') { - return [tileCoord.clone()]; - } else { - throw new Error(`Unsupported TileMatrixSet '${tileMatrixSet}'`); - } - }, - - WGS84toWMTS(bbox, target = new Extent('WMTS:WGS84G', 0, 0, 0)) { - bbox.dimensions(dim); - - var zoom = Math.floor( - Math.log(Math.PI / MathExt.degToRad(dim.y)) / LOG_TWO + 0.5); + computeWmtsPm(extent_wmtsWgs84g, extent_epsg4326) { + const extents_WMTS_PM = []; + const level = extent_wmtsWgs84g.zoom + 1; + const nbRow = 2 ** level; - var nY = 2 ** zoom; - var nX = 2 * nY; + const sizeRow = 1.0 / nbRow; - var uX = Math.PI * 2 / nX; - var uY = Math.PI / nY; - - bbox.center(center); - var col = Math.floor((Math.PI + MathExt.degToRad(center.longitude)) / uX); - var row = Math.floor(nY - (PI_OV_TWO + MathExt.degToRad(center.latitude)) / uY); - return target.set(zoom, row, col); - }, - - UnitaryToLongitudeWGS84(u, bbox) { - bbox.dimensions(dim); - return bbox.west + u * dim.x; - }, + const yMin = Projection.latitudeToY_PM(WGS84LatitudeClamp(extent_epsg4326.north)); + const yMax = Projection.latitudeToY_PM(WGS84LatitudeClamp(extent_epsg4326.south)); - UnitaryToLatitudeWGS84(v, bbox) { - bbox.dimensions(dim); - return bbox.south + v * dim.y; - }, -}; + let maxRow; + const min = yMin / sizeRow; + const max = yMax / sizeRow; -function WMTS_WGS84ToWMTS_PM(cWMTS, bbox) { - var wmtsBox = []; - var level = cWMTS.zoom + 1; - var nbRow = 2 ** level; + const minRow = Math.floor(min); + // ]N; N+1] => N + maxRow = Math.ceil(max) - 1; + // make sure we don't exceed boundaries + maxRow = Math.min(maxRow, nbRow - 1); - var sizeRow = 1.0 / nbRow; + const minCol = extent_wmtsWgs84g.col; + const maxCol = minCol; - var yMin = Projection.WGS84ToY(WGS84LatitudeClamp(bbox.north)); - var yMax = Projection.WGS84ToY(WGS84LatitudeClamp(bbox.south)); + for (let r = maxRow; r >= minRow; r--) { + for (let c = minCol; c <= maxCol; c++) { + extents_WMTS_PM.push(new Extent('WMTS:PM', level, r, c)); + } + } - let maxRow; + return extents_WMTS_PM; + }, - const min = yMin / sizeRow; - const max = yMax / sizeRow; + extent_Epsg4326_To_WmtsWgs84g(extent_epsg4326, extent_wmtsWgs84g = new Extent('WMTS:WGS84G', 0, 0, 0)) { + extent_epsg4326.dimensions(dim); - const minRow = Math.floor(min); - // ]N; N+1] => N - maxRow = Math.ceil(max) - 1; - // make sure we don't exceed boundaries - maxRow = Math.min(maxRow, nbRow - 1); + const zoom = Math.floor(Math.log(Math.PI / MathExt.degToRad(dim.y)) / LOG_TWO + 0.5); - var minCol = cWMTS.col; - var maxCol = minCol; + const nY = 2 ** zoom; + const nX = 2 * nY; - for (let r = maxRow; r >= minRow; r--) { - for (let c = minCol; c <= maxCol; c++) { - wmtsBox.push(new Extent('WMTS:PM', level, r, c)); - } - } + const uX = Math.PI * 2 / nX; + const uY = Math.PI / nY; - return wmtsBox; -} + extent_epsg4326.center(center); + const col = Math.floor((Math.PI + MathExt.degToRad(center.longitude)) / uX); + const row = Math.floor(nY - (PI_OV_TWO + MathExt.degToRad(center.latitude)) / uY); + return extent_wmtsWgs84g.set(zoom, row, col); + }, +}; export default Projection; diff --git a/src/Core/Prefab/Globe/BuilderEllipsoidTile.js b/src/Core/Prefab/Globe/BuilderEllipsoidTile.js index 777e8d787c..689c68cbf5 100644 --- a/src/Core/Prefab/Globe/BuilderEllipsoidTile.js +++ b/src/Core/Prefab/Globe/BuilderEllipsoidTile.js @@ -10,7 +10,7 @@ const quatToAlignLongitude = new THREE.Quaternion(); const quatToAlignLatitude = new THREE.Quaternion(); function WGS84ToOneSubY(latitude) { - return 1.0 - Projection.WGS84ToY(latitude); + return 1.0 - Projection.latitudeToY_PM(latitude); } class BuilderEllipsoidTile { @@ -21,6 +21,7 @@ class BuilderEllipsoidTile { new Coordinates('EPSG:4326', 0, 0), new Coordinates('EPSG:4326', 0, 0)], position: new THREE.Vector3(), + dimension: new THREE.Vector2(), }; } // prepare params @@ -45,6 +46,7 @@ class BuilderEllipsoidTile { // let's avoid building too much temp objects params.projected = { longitude: 0, latitude: 0 }; + params.extent.dimensions(this.tmp.dimension); } // get center tile in cartesian 3D @@ -70,12 +72,12 @@ class BuilderEllipsoidTile { // coord u tile to projected uProjecte(u, params) { - params.projected.longitude = Projection.UnitaryToLongitudeWGS84(u, params.extent); + params.projected.longitude = params.extent.west + u * this.tmp.dimension.x; } // coord v tile to projected vProjecte(v, params) { - params.projected.latitude = Projection.UnitaryToLatitudeWGS84(v, params.extent); + params.projected.latitude = params.extent.south + v * this.tmp.dimension.y; } // Compute uv 1, if isn't defined the uv1 isn't computed diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index c310cf3556..b077914a53 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -157,6 +157,11 @@ class GlobeView extends View { if (layer.isColorLayer) { const colorLayerCount = this.getLayers(l => l.isColorLayer).length; layer.sequence = colorLayerCount; + if ((layer.source.isWMTSSource || layer.source.isTMSSource) + && layer.source.tileMatrixSet !== 'WGS84G' + && layer.source.tileMatrixSet !== 'PM') { + throw new Error('Only WGS84G and PM tileMatrixSet are currently supported for WMTS/TMS color layers'); + } } else if (layer.isElevationLayer) { if (layer.source.isWMTSSource && layer.source.tileMatrixSet !== 'WGS84G') { throw new Error('Only WGS84G tileMatrixSet is currently supported for WMTS elevation layers'); diff --git a/src/Core/TileMesh.js b/src/Core/TileMesh.js index f21cf3f3b2..27245b3543 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; import OGCWebServiceHelper from 'Provider/OGCWebServiceHelper'; -import CRS from 'Core/Geographic/Crs'; +import Projection from 'Core/Geographic/Projection'; /** * A TileMesh is a THREE.Mesh with a geometricError and an OBB @@ -35,7 +35,11 @@ class TileMesh extends THREE.Mesh { this.obb = this.geometry.OBB.clone(); this.boundingSphere = new THREE.Sphere(); this.obb.box3D.getBoundingSphere(this.boundingSphere); - this.wmtsCoords = {}; + this.tilesetExtents = {}; + + // Compute it one time only + this.tilesetExtents.WGS84G = [Projection.extent_Epsg4326_To_WmtsWgs84g(this.extent)]; + this.tilesetExtents.PM = Projection.computeWmtsPm(this.tilesetExtents.WGS84G[0], this.extent); this.frustumCulled = false; this.matrixAutoUpdate = false; @@ -63,31 +67,14 @@ class TileMesh extends THREE.Mesh { } } - getCoordsForSource(source) { - if (source.isWMTSSource) { - OGCWebServiceHelper.computeTileMatrixSetCoordinates(this, source.tileMatrixSet); - return this.wmtsCoords[source.tileMatrixSet]; - } else if (source.isWMSSource && this.extent.crs != source.projection) { - if (source.projection == 'EPSG:3857') { - const tilematrixset = 'PM'; - OGCWebServiceHelper.computeTileMatrixSetCoordinates(this, tilematrixset); - return this.wmtsCoords[tilematrixset]; - } else { - throw new Error('unsupported projection wms for this viewer'); - } - } else if (source.isTMSSource) { - // Special globe case: use the P(seudo)M(ercator) coordinates - if (CRS.is4326(this.extent.crs) && - (source.extent.crs == 'EPSG:3857' || CRS.is4326(source.extent.crs))) { - OGCWebServiceHelper.computeTileMatrixSetCoordinates(this, 'PM'); - return this.wmtsCoords.PM; - } else { - return OGCWebServiceHelper.computeTMSCoordinates(this, source.extent, source.isInverted); - } + getExtentsForSource(source) { + // TODO: The only case that needs a dependency on the source, an + // alternative may be found to have only the CRS as a parameter + if (source.isTMSSource && !this.layer.isGlobeLayer) { + return OGCWebServiceHelper.computeTMSCoordinates(this, source.extent, source.isInverted); + } else if (Array.isArray(this.tilesetExtents[source.tileMatrixSet])) { + return this.tilesetExtents[source.tileMatrixSet]; } else if (source.extent.crs == this.extent.crs) { - // Currently extent.as() always clone the extent, even if the output - // crs is the same. - // So we avoid using it if both crs are the same. return [this.extent]; } else { return [this.extent.as(source.extent.crs)]; @@ -95,7 +82,7 @@ class TileMesh extends THREE.Mesh { } getZoomForLayer(layer) { - return this.getCoordsForSource(layer.source)[0].zoom || this.level; + return this.getExtentsForSource(layer.source)[0].zoom || this.level; } /** diff --git a/src/Layer/TiledGeometryLayer.js b/src/Layer/TiledGeometryLayer.js index ee7db49585..6be10fe380 100644 --- a/src/Layer/TiledGeometryLayer.js +++ b/src/Layer/TiledGeometryLayer.js @@ -273,7 +273,7 @@ class TiledGeometryLayer extends GeometryLayer { let nodeLayer = node.material.getElevationLayer(); for (const e of context.elevationLayers) { - const extents = node.getCoordsForSource(e.source); + const extents = node.getExtentsForSource(e.source); if (!e.frozen && e.ready && e.source.extentsInsideLimit(extents) && (!nodeLayer || nodeLayer.level < 0)) { // no stop subdivision in the case of a loading error if (layerUpdateState[e.id] && layerUpdateState[e.id].inError()) { @@ -291,7 +291,7 @@ class TiledGeometryLayer extends GeometryLayer { if (layerUpdateState[c.id] && layerUpdateState[c.id].inError()) { continue; } - const extents = node.getCoordsForSource(c.source); + const extents = node.getExtentsForSource(c.source); nodeLayer = node.material.getLayer(c.id); if (c.source.extentsInsideLimit(extents) && (!nodeLayer || nodeLayer.level < 0)) { return false; diff --git a/src/Process/FeatureProcessing.js b/src/Process/FeatureProcessing.js index 78cf2c95b4..03f42d7abc 100644 --- a/src/Process/FeatureProcessing.js +++ b/src/Process/FeatureProcessing.js @@ -85,7 +85,7 @@ export default { return features; } - const extentsDestination = node.getCoordsForSource(layer.source); + const extentsDestination = node.getExtentsForSource(layer.source); extentsDestination.forEach((e) => { e.zoom = node.level; }); const extentsSource = []; diff --git a/src/Process/LayeredMaterialNodeProcessing.js b/src/Process/LayeredMaterialNodeProcessing.js index ef8c4f1681..b0e24170b2 100644 --- a/src/Process/LayeredMaterialNodeProcessing.js +++ b/src/Process/LayeredMaterialNodeProcessing.js @@ -59,7 +59,7 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { return; } - const extentsDestination = node.getCoordsForSource(layer.source); + const extentsDestination = node.getExtentsForSource(layer.source); let nodeLayer = material.getLayer(layer.id); @@ -179,7 +179,7 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) // Elevation is currently handled differently from color layers. // This is caused by a LayeredMaterial limitation: only 1 elevation texture // can be used (where a tile can have N textures x M layers) - const extentsDestination = node.getCoordsForSource(layer.source); + const extentsDestination = node.getExtentsForSource(layer.source); // Init elevation layer, and inherit from parent if possible let nodeLayer = material.getElevationLayer(); if (!nodeLayer) { diff --git a/src/Provider/OGCWebServiceHelper.js b/src/Provider/OGCWebServiceHelper.js index 109430d548..952cadf2b5 100644 --- a/src/Provider/OGCWebServiceHelper.js +++ b/src/Provider/OGCWebServiceHelper.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import Projection from 'Core/Geographic/Projection'; import Extent from 'Core/Geographic/Extent'; import Coordinates from 'Core/Geographic/Coordinates'; @@ -11,26 +10,7 @@ const tileDimension = new THREE.Vector2(); export const SIZE_TEXTURE_TILE = 256; export const SIZE_DIAGONAL_TEXTURE = Math.pow(2 * (SIZE_TEXTURE_TILE * SIZE_TEXTURE_TILE), 0.5); -const tileCoord = new Extent('WMTS:WGS84G', 0, 0, 0); - export default { - computeTileMatrixSetCoordinates(tile, tileMatrixSet) { - tileMatrixSet = tileMatrixSet || 'WGS84G'; - if (!(tileMatrixSet in tile.wmtsCoords)) { - if (tile.wmtsCoords.WGS84G) { - const c = tile.wmtsCoords.WGS84G[0]; - tileCoord.zoom = c.zoom; - tileCoord.col = c.col; - tileCoord.row = c.row; - } else { - Projection.WGS84toWMTS(tile.extent, tileCoord); - tile.wmtsCoords.WGS84G = [tileCoord.clone()]; - } - - tile.wmtsCoords[tileMatrixSet] = - Projection.getCoordWMTS_WGS84(tileCoord, tile.extent, tileMatrixSet); - } - }, // The isInverted parameter is to be set to the correct value, true or false // (default being false) if the computation of the coordinates needs to be // inverted to match the same scheme as OSM, Google Maps or other system. diff --git a/test/unit/layeredmaterial.js b/test/unit/layeredmaterial.js index 08dd9b235f..5d844a2b63 100644 --- a/test/unit/layeredmaterial.js +++ b/test/unit/layeredmaterial.js @@ -13,7 +13,7 @@ describe('material state vs layer state', function () { getLayer: () => nodeLayer, visible: true, }, - getCoordsForSource: () => 0, + getExtentsForSource: () => 0, }; const layer = { id: 'test',