Skip to content

Commit

Permalink
fix(tms): return the correct coordinates for a TMS source
Browse files Browse the repository at this point in the history
When using a GlobeView and a TMSSource, the coordinates returned for a
tile were only Pseudo-Mercator; it can be WGS84G now.
  • Loading branch information
zarov committed Jun 19, 2019
1 parent 0f8e3c7 commit 3d9ae12
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 128 deletions.
4 changes: 2 additions & 2 deletions src/Core/Geographic/Extent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
120 changes: 50 additions & 70 deletions src/Core/Geographic/Projection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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;
8 changes: 5 additions & 3 deletions src/Core/Prefab/Globe/BuilderEllipsoidTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/Core/Prefab/GlobeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
41 changes: 14 additions & 27 deletions src/Core/TileMesh.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -63,39 +67,22 @@ 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)];
}
}

getZoomForLayer(layer) {
return this.getCoordsForSource(layer.source)[0].zoom || this.level;
return this.getExtentsForSource(layer.source)[0].zoom || this.level;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Layer/TiledGeometryLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Process/FeatureProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down
4 changes: 2 additions & 2 deletions src/Process/LayeredMaterialNodeProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 0 additions & 20 deletions src/Provider/OGCWebServiceHelper.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion test/unit/layeredmaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('material state vs layer state', function () {
getLayer: () => nodeLayer,
visible: true,
},
getCoordsForSource: () => 0,
getExtentsForSource: () => 0,
};
const layer = {
id: 'test',
Expand Down

0 comments on commit 3d9ae12

Please sign in to comment.