From f5d8cacc5e5a079544af86bcbdc48c5faefc1ddb Mon Sep 17 00:00:00 2001 From: gchoqueux Date: Mon, 29 Jul 2019 13:41:03 +0200 Subject: [PATCH] refactor(core): tile matrix set and projection/crs handling. --- src/Converter/convertToTile.js | 3 +- src/Converter/textureConverter.js | 3 +- src/Core/Geographic/Crs.js | 52 ++- src/Core/Geographic/Extent.js | 334 +++++++++++------- src/Core/Geographic/Projection.js | 96 ----- src/Core/Prefab/Globe/BuilderEllipsoidTile.js | 40 ++- src/Core/Prefab/Globe/GlobeLayer.js | 18 +- src/Core/Prefab/GlobeView.js | 11 +- src/Core/Prefab/Planar/PlanarLayer.js | 10 + src/Core/Prefab/Planar/PlanarTileBuilder.js | 6 +- src/Core/Prefab/TileBuilder.js | 12 +- src/Core/Prefab/computeBufferTileGeometry.js | 66 ++-- src/Core/TileGeometry.js | 14 +- src/Core/TileMesh.js | 49 +-- src/Core/View.js | 14 +- src/Layer/OrientedImageLayer.js | 1 + src/Layer/TiledGeometryLayer.js | 6 +- src/Parser/VectorTileParser.js | 3 +- src/Process/FeatureProcessing.js | 13 +- src/Process/LayeredMaterialNodeProcessing.js | 30 +- src/Provider/DataSourceProvider.js | 2 +- src/Provider/OGCWebServiceHelper.js | 42 --- src/Renderer/LayeredMaterial.js | 27 +- src/Renderer/MaterialLayer.js | 5 +- src/Renderer/OBB.js | 2 +- src/Renderer/Shader/TileFS.glsl | 10 +- src/Renderer/Shader/TileVS.glsl | 16 +- src/Source/FileSource.js | 3 +- src/Source/TMSSource.js | 14 +- src/Source/WMSSource.js | 4 +- src/Source/WMTSSource.js | 17 +- 31 files changed, 456 insertions(+), 467 deletions(-) delete mode 100644 src/Core/Geographic/Projection.js delete mode 100644 src/Provider/OGCWebServiceHelper.js diff --git a/src/Converter/convertToTile.js b/src/Converter/convertToTile.js index 3bb7a25bf6..0ba4585086 100644 --- a/src/Converter/convertToTile.js +++ b/src/Converter/convertToTile.js @@ -53,7 +53,8 @@ export default { return newTileGeometry(builder, paramsGeometry).then((result) => { // build tile mesh result.geometry._count++; - const material = new LayeredMaterial(layer.materialOptions); + const crsCount = layer.tileMatrixSets.length; + const material = new LayeredMaterial(layer.materialOptions, crsCount); const tile = new TileMesh(result.geometry, material, layer, extent, level); // Commented because layer.threejsLayer is undefined; diff --git a/src/Converter/textureConverter.js b/src/Converter/textureConverter.js index e7e96700c3..60534d0181 100644 --- a/src/Converter/textureConverter.js +++ b/src/Converter/textureConverter.js @@ -1,6 +1,7 @@ import * as THREE from 'three'; import Feature2Texture from 'Converter/Feature2Texture'; import Extent from 'Core/Geographic/Extent'; +import CRS from 'Core/Geographic/Crs'; const extentTexture = new Extent('EPSG:4326', [0, 0, 0, 0]); @@ -25,7 +26,7 @@ export default { new THREE.Color(layer.backgroundLayer.paint['background-color']) : undefined; - extentDestination.as(layer.projection, extentTexture); + extentDestination.as(CRS.formatToEPSG(layer.projection), extentTexture); texture = Feature2Texture.createTextureFromFeature(data, extentTexture, 256, layer.style, backgroundColor); texture.parsedData = data; texture.coords = extentDestination; diff --git a/src/Core/Geographic/Crs.js b/src/Core/Geographic/Crs.js index 576fef2e19..e4d9fb76c1 100644 --- a/src/Core/Geographic/Crs.js +++ b/src/Core/Geographic/Crs.js @@ -2,6 +2,42 @@ import proj4 from 'proj4'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); +const TMS = [ + 'WMTS:WGS84', + 'WMTS:PM', +]; + +const EPSG = [ + 'EPSG:4326', + 'EPSG:3857', +]; + +function formatToTms(crs) { + if (crs) { + if (crs.includes('WMTS')) { + return crs; + } + const i = EPSG.indexOf(crs); + if (i > -1) { + return TMS[i]; + } else if (crs.includes('EPSG')) { + return `WMTS:TMS:${crs.replace('EPSG:', '')}`; + } + } +} + +function formatToEPSG(crs) { + if (crs) { + if (crs.includes('EPSG')) { + return crs; + } else if (EPSG[TMS.indexOf(crs)]) { + return EPSG[TMS.indexOf(crs)]; + } else { + return `EPSG:${crs.match(/\d+/)[0]}`; + } + } +} + const UNIT = { DEGREE: 1, METER: 2, @@ -26,7 +62,7 @@ function toUnit(crs) { case 'EPSG:4326' : return UNIT.DEGREE; case 'EPSG:4978' : return UNIT.METER; default: { - const p = proj4.defs(crs); + const p = proj4.defs(formatToEPSG(crs)); if (!p) { return undefined; } @@ -118,4 +154,18 @@ export default { return 0.001; } }, + /** + * format crs to European Petroleum Survey Group notation : EPSG:XXXX. + * + * @param {string} crs The crs to format + * @return {string} formated crs + */ + formatToEPSG, + /** + * format crs to tile matrix set notation : WMTS:XXXX. + * + * @param {string} crs The crs to format + * @return {string} formated crs + */ + formatToTms, }; diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index 3a53eb5036..f1267b0876 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -1,7 +1,6 @@ import * as THREE from 'three'; import Coordinates from 'Core/Geographic/Coordinates'; import CRS from 'Core/Geographic/Crs'; -import Projection from 'Core/Geographic/Projection'; /** * Extent is a SIG-area (so 2D) @@ -10,6 +9,10 @@ import Projection from 'Core/Geographic/Projection'; const _dim = new THREE.Vector2(); const _dim2 = new THREE.Vector2(); +const _countTiles = new THREE.Vector2(); +const tmsCoord = new THREE.Vector2(); +const dimensionTile = new THREE.Vector2(); +const defaultScheme = new THREE.Vector2(2, 2); const r = { row: 0, col: 0, invDiff: 0 }; function _rowColfromParent(extent, zoom) { @@ -23,23 +26,40 @@ function _rowColfromParent(extent, zoom) { } let _extent; +let _extent2; -const cardinals = []; -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); -cardinals.push(new Coordinates('EPSG:4326', 0, 0, 0, 0)); +const cardinals = new Array(8); +for (var i = cardinals.length - 1; i >= 0; i--) { + cardinals[i] = new Coordinates('EPSG:4326', 0, 0, 0, 0); +} const _c = new Coordinates('EPSG:4326', 0, 0); -// EPSG:3857 -// WGS84 bounds [-20026376.39 -20048966.10 20026376.39 20048966.10] (https://epsg.io/3857) -// Warning, some tiled source don't exactly match the same bound -// It should be taken into account -export const worldDimension3857 = { x: 20026376.39 * 2, y: 20048966.10 * 2 }; + +export const globalExtentTMS = new Map(); +export const schemeTiles = new Map(); + +function getInfoTms(crs) { + const epsg = CRS.formatToEPSG(crs); + const globalExtent = globalExtentTMS.get(epsg); + const globalDimension = globalExtent.dimensions(_dim2); + const tms = CRS.formatToTms(crs); + const sTs = schemeTiles.get(tms) || schemeTiles.get('default'); + // 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. + // See link below for more information + // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/ + // in crs includes ':NI' => tms isn't inverted (NOT INVERTED) + const isInverted = !tms.includes(':NI'); + return { epsg, globalExtent, globalDimension, sTs, isInverted }; +} + +function getCountTiles(crs, zoom) { + const sTs = schemeTiles.get(CRS.formatToTms(crs)) || schemeTiles.get('default'); + const count = 2 ** zoom; + _countTiles.set(count, count).multiply(sTs); + return _countTiles; +} class Extent { /** @@ -57,9 +77,10 @@ class Extent { */ constructor(crs, v0, v1, v2, v3) { this.crs = crs; + // Scale/zoom + this.zoom = 0; if (this.isTiledCrs()) { - this.zoom = 0; this.row = 0; this.col = 0; } else { @@ -89,7 +110,57 @@ class Extent { * @return {boolean} */ isTiledCrs() { - return this.crs.indexOf('WMTS:') == 0 || this.crs == 'TMS'; + return this.crs.indexOf('WMTS:') == 0; + } + /** + * get tiled extents convering this extent + * + * @param {string} crs WMTS, TMS crs + * @return {Array} array of extents covering + */ + tiledCovering(crs) { + if (this.crs == 'EPSG:4326' && crs == 'WMTS:PM') { + const extents_WMTS_PM = []; + const extent = _extent.copy(this).as(CRS.formatToEPSG(crs), _extent2); + const { globalExtent, globalDimension, sTs } = getInfoTms(CRS.formatToEPSG(crs)); + extent.clampByExtent(globalExtent); + extent.dimensions(dimensionTile); + + const zoom = (this.zoom + 1) || Math.floor(Math.log2(Math.round(globalDimension.x / (dimensionTile.x * sTs.x)))); + const countTiles = getCountTiles(crs, zoom); + const center = extent.center(_c); + + tmsCoord.x = center.x - globalExtent.west; + tmsCoord.y = globalExtent.north - extent.north; + tmsCoord.divide(globalDimension).multiply(countTiles).floor(); + + // ]N; N+1] => N + const maxRow = Math.ceil((globalExtent.north - extent.south) / globalDimension.x * countTiles.y) - 1; + + for (let r = maxRow; r >= tmsCoord.y; r--) { + extents_WMTS_PM.push(new Extent(crs, zoom, r, tmsCoord.x)); + } + + return extents_WMTS_PM; + } else { + const target = new Extent('WMTS:PM', 0, 0, 0); + const { globalExtent, globalDimension, sTs, isInverted } = getInfoTms(this.crs); + const center = this.center(_c); + this.dimensions(dimensionTile); + // Each level has 2^n * 2^n tiles... + // ... so we count how many tiles of the same width as tile we can fit in the layer + // ... 2^zoom = tilecount => zoom = log2(tilecount) + const zoom = Math.floor(Math.log2(Math.round(globalDimension.x / (dimensionTile.x * sTs.x)))); + const countTiles = getCountTiles(crs, zoom); + + // Now that we have computed zoom, we can deduce x and y (or row / column) + tmsCoord.x = center.x - globalExtent.west; + tmsCoord.y = isInverted ? globalExtent.north - center.y : center.y - globalExtent.south; + tmsCoord.divide(globalDimension).multiply(countTiles).floor(); + target.crs = crs; + target.set(zoom, tmsCoord.y, tmsCoord.x); + return [target]; + } } /** @@ -100,112 +171,58 @@ class Extent { */ as(crs, target) { CRS.isValid(crs); + target = target || new Extent('EPSG:4326', [0, 0, 0, 0]); if (this.isTiledCrs()) { - if (this.crs == 'WMTS:PM' || this.crs == 'TMS') { - if (!target) { - target = new Extent('EPSG:4326', [0, 0, 0, 0]); - } - // Convert this to the requested crs by using 4326 as an intermediate state. - const nbCol = 2 ** this.zoom; - const nbRow = nbCol; - const sizeRow = 1.0 / nbRow; - // convert row PM to Y PM - const Yn = 1 - sizeRow * (nbRow - this.row); - const Ys = Yn + sizeRow; - - // convert to EPSG:3857 - if (crs == 'EPSG:3857') { - const west = (0.5 - sizeRow * (nbCol - this.col)) * worldDimension3857.x; - const east = west + sizeRow * worldDimension3857.x; - const south = (0.5 - Ys) * worldDimension3857.y; - const north = (0.5 - Yn) * worldDimension3857.y; - target.set(west, east, south, north); - target.crs = 'EPSG:3857'; - return target.as(crs, target); - } else { - const size = 360 / nbCol; - // convert Y PM to latitude EPSG:4326 degree - 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; - - target.set(west, east, south, north); - target.crs = 'EPSG:4326'; - if (crs == 'EPSG:4326') { - return target; - } else { - // convert in new crs - return target.as(crs, target); - } - } - } else if (this.crs == 'WMTS:WGS84G' && crs == 'EPSG:4326') { - if (!target) { - target = new Extent('EPSG:4326', [0, 0, 0, 0]); + const { epsg, globalExtent, globalDimension } = getInfoTms(this.crs); + const countTiles = getCountTiles(this.crs, this.zoom); + + dimensionTile.set(1, 1).divide(countTiles).multiply(globalDimension); + + target.west = globalExtent.west + (globalDimension.x - dimensionTile.x * (countTiles.x - this.col)); + target.east = target.west + dimensionTile.x; + target.south = globalExtent.south + dimensionTile.y * (countTiles.y - this.row - 1); + target.north = target.south + dimensionTile.y; + target.crs = epsg; + target.zoom = this.zoom; + + return crs == epsg ? target : target.as(crs, target); + } else if (!crs.includes('WMTS:')) { + if (this.crs != crs) { + // Compute min/max in x/y by projecting 8 cardinal points, + // and then taking the min/max of each coordinates. + const center = this.center(_c); + cardinals[0].setFromValues(this.west, this.north); + cardinals[1].setFromValues(center.x, this.north); + cardinals[2].setFromValues(this.east, this.north); + cardinals[3].setFromValues(this.east, center.y); + cardinals[4].setFromValues(this.east, this.south); + cardinals[5].setFromValues(center.x, this.south); + cardinals[6].setFromValues(this.west, this.south); + cardinals[7].setFromValues(this.west, center.y); + + target.set(Infinity, -Infinity, Infinity, -Infinity); + + // loop over the coordinates + for (let i = 0; i < cardinals.length; i++) { + // convert the coordinate. + cardinals[i].crs = this.crs; + cardinals[i].as(crs, _c); + target.north = Math.max(target.north, _c.y); + target.south = Math.min(target.south, _c.y); + target.east = Math.max(target.east, _c.x); + target.west = Math.min(target.west, _c.x); } - const nbRow = 2 ** this.zoom; - const size = 180 / nbRow; - const north = size * (nbRow - this.row) - 90; - const south = size * (nbRow - (this.row + 1)) - 90; - const west = 180 - size * (2 * nbRow - this.col); - const east = 180 - size * (2 * nbRow - (this.col + 1)); - - target.set(west, east, south, north); + + target.zoom = this.zoom; target.crs = crs; return target; - } else { - throw new Error('Unsupported yet'); - } - } - - if (!target) { - target = new Extent('EPSG:4326', [0, 0, 0, 0]); - } - if (this.crs != crs && !(CRS.is4326(this.crs) && CRS.is4326(crs))) { - // Compute min/max in x/y by projecting 8 cardinal points, - // and then taking the min/max of each coordinates. - const center = this.center(_c); - cardinals[0].crs = this.crs; - cardinals[0].setFromValues(this.west, this.north); - cardinals[1].crs = this.crs; - cardinals[1].setFromValues(center.x, this.north); - cardinals[2].crs = this.crs; - cardinals[2].setFromValues(this.east, this.north); - cardinals[3].crs = this.crs; - cardinals[3].setFromValues(this.east, center.y); - cardinals[4].crs = this.crs; - cardinals[4].setFromValues(this.east, this.south); - cardinals[5].crs = this.crs; - cardinals[5].setFromValues(center.x, this.south); - cardinals[6].crs = this.crs; - cardinals[6].setFromValues(this.west, this.south); - cardinals[7].crs = this.crs; - cardinals[7].setFromValues(this.west, center.y); - - let north = -Infinity; - let south = Infinity; - let east = -Infinity; - let west = Infinity; - // loop over the coordinates - for (let i = 0; i < cardinals.length; i++) { - // convert the coordinate. - cardinals[i].as(crs, _c); - north = Math.max(north, _c.y); - south = Math.min(south, _c.y); - east = Math.max(east, _c.x); - west = Math.min(west, _c.x); } target.crs = crs; - target.set(west, east, south, north); + target.set(this.west, this.east, this.south, this.north); + return target; } - - target.crs = crs; - target.set(this.west, this.east, this.south, this.north); - - return target; } /** @@ -338,6 +355,7 @@ class Extent { * @returns {Boolean} */ intersectsExtent(extent) { + // TODO don't work when is on limit const other = extent.crs == this.crs ? extent : extent.as(this.crs, _extent); return !(this.west >= other.east || this.east <= other.west || @@ -530,22 +548,29 @@ class Extent { * order of the sections is [NW, NE, SW, SE]. */ subdivision() { - this.center(_c); - - const northWest = new Extent(this.crs, - this.west, _c.x, - _c.y, this.north); - const northEast = new Extent(this.crs, - _c.x, this.east, - _c.y, this.north); - const southWest = new Extent(this.crs, - this.west, _c.x, - this.south, _c.y); - const southEast = new Extent(this.crs, - _c.x, this.east, - this.south, _c.y); - - return [northWest, northEast, southWest, southEast]; + return this.subdivisionByScheme(); + } + /** + * subdivise extent by scheme.x on west-east and scheme.y on south-north. + * + * @param {Vector2} [scheme=Vector2(2,2)] The scheme to subdivise. + * @return {Array} subdivised extents. + */ + subdivisionByScheme(scheme = defaultScheme) { + const subdivisedExtents = []; + const dimSub = this.dimensions(_dim).divide(scheme); + for (let x = scheme.x - 1; x >= 0; x--) { + for (let y = scheme.y - 1; y >= 0; y--) { + const west = this.west + x * dimSub.x; + const south = this.south + y * dimSub.y; + subdivisedExtents.push(new Extent(this.crs, + west, + west + dimSub.x, + south, + south + dimSub.y)); + } + } + return subdivisedExtents; } /** @@ -574,8 +599,57 @@ class Extent { } } } + /** + * clamp south and north values + * + * @param {number} [south=this.south] The min south + * @param {number} [north=this.north] The max north + * @return {Extent} this extent + */ + clampSouthNorth(south = this.south, north = this.north) { + this.south = Math.max(this.south, south); + this.north = Math.min(this.north, north); + return this; + } + + /** + * clamp west and east values + * + * @param {number} [west=this.west] The min west + * @param {number} [east=this.east] The max east + * @return {Extent} this extent + */ + clampWestEast(west = this.west, east = this.east) { + this.west = Math.max(this.west, west); + this.east = Math.min(this.east, east); + return this; + } + /** + * clamp this extent by passed extent + * + * @param {Extent} extent The maximum extent. + * @return {Extent} this extent. + */ + clampByExtent(extent) { + this.clampSouthNorth(extent.south, extent.north); + return this.clampWestEast(extent.west, extent.east); + } } _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); +_extent2 = new Extent('EPSG:4326', [0, 0, 0, 0]); + +globalExtentTMS.set('EPSG:4326', new Extent('EPSG:4326', -180, 180, -90, 90)); + +// Compute global extent of WMTS:PM EPSG:3857 +// It's square whose a side is between -180° to 180°. +// So, west extent, it's 180 convert in EPSG:3857 +const extent3857 = globalExtentTMS.get('EPSG:4326').as('EPSG:3857'); +extent3857.clampSouthNorth(extent3857.west, extent3857.east); +globalExtentTMS.set('EPSG:3857', extent3857); + +schemeTiles.set('default', new THREE.Vector2(1, 1)); +schemeTiles.set('WMTS:PM', schemeTiles.get('default')); +schemeTiles.set('WMTS:WGS84', new THREE.Vector2(2, 1)); export default Extent; diff --git a/src/Core/Geographic/Projection.js b/src/Core/Geographic/Projection.js deleted file mode 100644 index 273b2abaad..0000000000 --- a/src/Core/Geographic/Projection.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Generated On: 2015-10-5 - * Class: Projection - * Description: Outils de projections cartographiques et de convertion - */ -import { Math as MathExt } from 'three'; -import Coordinates from 'Core/Geographic/Coordinates'; -import Extent from 'Core/Geographic/Extent'; - -const PI_OV_FOUR = Math.PI / 4; -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)); -} - -const dim = { x: 0, y: 0 }; -const center = new Coordinates('EPSG:4326', 0, 0, 0); - -const Projection = { - /** - * 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 - */ - 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 pseudo mercator (EPSG:3857) to latitude - * @param {number} y - y coordinate in pseudo mercator - * @return {number} - latitude in degrees (EPSG:4326) - */ - y_PMTolatitude(y) { - return MathExt.radToDeg(2 * (Math.atan(Math.exp(-(y - 0.5) / INV_TWO_PI)) - PI_OV_FOUR)); - }, - - computeWmtsPm(extent_wmtsWgs84g, extent_epsg4326) { - const extents_WMTS_PM = []; - const level = extent_wmtsWgs84g.zoom + 1; - const nbRow = 2 ** level; - - const sizeRow = 1.0 / nbRow; - - const yMin = Projection.latitudeToY_PM(WGS84LatitudeClamp(extent_epsg4326.north)); - const yMax = Projection.latitudeToY_PM(WGS84LatitudeClamp(extent_epsg4326.south)); - - let maxRow; - - const min = yMin / sizeRow; - const max = yMax / sizeRow; - - 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 minCol = extent_wmtsWgs84g.col; - const maxCol = minCol; - - 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)); - } - } - - return extents_WMTS_PM; - }, - - extent_Epsg4326_To_WmtsWgs84g(extent_epsg4326, extent_wmtsWgs84g = new Extent('WMTS:WGS84G', 0, 0, 0)) { - extent_epsg4326.dimensions(dim); - - const zoom = Math.floor(Math.log(Math.PI / MathExt.degToRad(dim.y)) / LOG_TWO + 0.5); - - const nY = 2 ** zoom; - const nX = 2 * nY; - - const uX = Math.PI * 2 / nX; - const uY = Math.PI / nY; - - 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 689c68cbf5..e124c8e22b 100644 --- a/src/Core/Prefab/Globe/BuilderEllipsoidTile.js +++ b/src/Core/Prefab/Globe/BuilderEllipsoidTile.js @@ -1,21 +1,22 @@ import * as THREE from 'three'; import Coordinates from 'Core/Geographic/Coordinates'; -import Projection from 'Core/Geographic/Projection'; import OBB from 'Renderer/OBB'; import Extent from 'Core/Geographic/Extent'; +const PI_OV_FOUR = Math.PI / 4; +const INV_TWO_PI = 1.0 / (Math.PI * 2); const axisZ = new THREE.Vector3(0, 0, 1); const axisY = new THREE.Vector3(0, 1, 0); const quatToAlignLongitude = new THREE.Quaternion(); const quatToAlignLatitude = new THREE.Quaternion(); +const quatNormalToZ = new THREE.Quaternion(); function WGS84ToOneSubY(latitude) { - return 1.0 - Projection.latitudeToY_PM(latitude); + return 1.0 - (0.5 - Math.log(Math.tan(PI_OV_FOUR + THREE.Math.degToRad(latitude) * 0.5)) * INV_TWO_PI); } class BuilderEllipsoidTile { - constructor() { - this.type = 'e'; + constructor(options = {}) { this.tmp = { coords: [ new Coordinates('EPSG:4326', 0, 0), @@ -23,6 +24,22 @@ class BuilderEllipsoidTile { position: new THREE.Vector3(), dimension: new THREE.Vector2(), }; + + this.projection = options.projection; + // Order projection on tiles + this.uvCount = options.uvCount; + + this.computeUvs = [ + // Normalized coordinates (from degree) on the entire tile + // EPSG:4326 + () => {}, + // Float row coordinate from Pseudo mercator coordinates + // EPSG:3857 + (params) => { + const t = WGS84ToOneSubY(params.projected.latitude) * params.nbRow; + return (!isFinite(t) ? 0 : t) - params.deltaUV1; + }, + ]; } // prepare params // init projected object -> params.projected @@ -40,7 +57,7 @@ class BuilderEllipsoidTile { params.deltaUV1 = (st1 - start) * params.nbRow; // transformation to align tile's normal to z axis - params.quatNormalToZ = new THREE.Quaternion().setFromAxisAngle( + params.quatNormalToZ = quatNormalToZ.setFromAxisAngle( axisY, -(Math.PI * 0.5 - THREE.Math.degToRad(params.extent.center().latitude))); @@ -52,7 +69,7 @@ class BuilderEllipsoidTile { // get center tile in cartesian 3D center(extent) { return extent.center(this.tmp.coords[0]) - .as('EPSG:4978', this.tmp.coords[1]).toVector3(); + .as(this.projection, this.tmp.coords[1]).toVector3(); } // get position 3D cartesian @@ -61,7 +78,7 @@ class BuilderEllipsoidTile { params.projected.longitude, params.projected.latitude); - this.tmp.coords[0].as('EPSG:4978', this.tmp.coords[1]).toVector3(this.tmp.position); + this.tmp.coords[0].as(this.projection, this.tmp.coords[1]).toVector3(this.tmp.position); return this.tmp.position; } @@ -80,15 +97,6 @@ class BuilderEllipsoidTile { params.projected.latitude = params.extent.south + v * this.tmp.dimension.y; } - // Compute uv 1, if isn't defined the uv1 isn't computed - getUV_PM(params) { - var t = WGS84ToOneSubY(params.projected.latitude) * params.nbRow; - - if (!isFinite(t)) { t = 0; } - - return t - params.deltaUV1; - } - computeSharableExtent(extent) { // Compute sharable extent to pool the geometries // the geometry in common extent is identical to the existing input diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index d6c915a229..51422583e6 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -1,9 +1,9 @@ import * as THREE from 'three'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; -import Extent from 'Core/Geographic/Extent'; +import { globalExtentTMS, schemeTiles } from 'Core/Geographic/Extent'; import BuilderEllipsoidTile from 'Core/Prefab/Globe/BuilderEllipsoidTile'; -import { SIZE_DIAGONAL_TEXTURE } from 'Provider/OGCWebServiceHelper'; +import { SIZE_DIAGONAL_TEXTURE } from 'Process/LayeredMaterialNodeProcessing'; // matrix to convert sphere to ellipsoid const worldToScaledEllipsoid = new THREE.Matrix4(); @@ -51,10 +51,16 @@ class GlobeLayer extends TiledGeometryLayer { */ constructor(id, object3d, config = {}) { // Configure tiles - const schemeTile = [ - new Extent('EPSG:4326', -180, 0, -90, 90), - new Extent('EPSG:4326', 0, 180, -90, 90)]; - const builder = new BuilderEllipsoidTile(); + const scheme = schemeTiles.get('WMTS:WGS84'); + const schemeTile = globalExtentTMS.get('EPSG:4326').subdivisionByScheme(scheme); + + // Supported tile matrix set for color/elevation layer + config.tileMatrixSets = [ + 'WMTS:WGS84', + 'WMTS:PM', + ]; + const uvCount = config.tileMatrixSets.length; + const builder = new BuilderEllipsoidTile({ projection: 'EPSG:4978', uvCount }); super(id, object3d || new THREE.Group(), schemeTile, builder, config); diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index 4154e7ceaa..fdd9572bef 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -7,6 +7,7 @@ import GlobeLayer from 'Core/Prefab/Globe/GlobeLayer'; import Atmosphere from 'Core/Prefab/Globe/Atmosphere'; import Coordinates from 'Core/Geographic/Coordinates'; +import CRS from 'Core/Geographic/Crs'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; /** @@ -139,14 +140,12 @@ 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'); + if (!this.tileLayer.tileMatrixSets.includes(CRS.formatToTms(layer.source.projection))) { + throw new Error(`Only ${this.tileLayer.tileMatrixSets} tileMatrixSet are currently supported for 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'); + if (CRS.formatToTms(layer.source.projection) !== this.tileLayer.tileMatrixSets[0]) { + throw new Error(`Only ${this.tileLayer.tileMatrixSets[0]} tileMatrixSet is currently supported for elevation layers`); } } diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index 9148a7aa16..80e3ecc040 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -1,6 +1,8 @@ import * as THREE from 'three'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; +import { globalExtentTMS } from 'Core/Geographic/Extent'; +import CRS from 'Core/Geographic/Crs'; import PlanarTileBuilder from './PlanarTileBuilder'; /** @@ -36,6 +38,14 @@ class PlanarLayer extends TiledGeometryLayer { * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ constructor(id, extent, object3d, config = {}) { + const tileMatrixSets = []; + const tms = CRS.formatToTms(extent.crs); + tileMatrixSets.push(tms); + if (tms.includes(':TMS')) { + // Add new global extent for this new projection. + globalExtentTMS.set(extent.crs, extent); + } + config.tileMatrixSets = tileMatrixSets; super(id, object3d || new THREE.Group(), [extent], new PlanarTileBuilder(), config); this.isPlanarLayer = true; this.extent = extent; diff --git a/src/Core/Prefab/Planar/PlanarTileBuilder.js b/src/Core/Prefab/Planar/PlanarTileBuilder.js index f03bc257c6..6789a058ee 100644 --- a/src/Core/Prefab/Planar/PlanarTileBuilder.js +++ b/src/Core/Prefab/Planar/PlanarTileBuilder.js @@ -7,13 +7,15 @@ const quaternion = new THREE.Quaternion(); const center = new THREE.Vector3(); class PlanarTileBuilder { - constructor() { - this.type = 'p'; + constructor(options = {}) { this.tmp = { coords: new Coordinates('EPSG:4326', 0, 0), position: new THREE.Vector3(), normal: new THREE.Vector3(0, 0, 1), }; + + this.projection = options.projection; + this.uvCount = options.uvCount || 1; } // prepare params // init projected object -> params.projected diff --git a/src/Core/Prefab/TileBuilder.js b/src/Core/Prefab/TileBuilder.js index c16bc798c1..921de5e0fa 100644 --- a/src/Core/Prefab/TileBuilder.js +++ b/src/Core/Prefab/TileBuilder.js @@ -7,7 +7,7 @@ const cacheBuffer = new Map(); export default function newTileGeometry(builder, params) { const { sharableExtent, quaternion, position } = builder.computeSharableExtent(params.extent); const south = sharableExtent.south.toFixed(6); - const bufferKey = `${builder.type}_${params.disableSkirt ? 0 : 1}_${params.segment}`; + const bufferKey = `${builder.projection}_${params.disableSkirt ? 0 : 1}_${params.segment}`; const geometryKey = `${bufferKey}_${params.level}_${south}`; let promiseGeometry = Cache.get(geometryKey); @@ -21,23 +21,25 @@ export default function newTileGeometry(builder, params) { params.center = builder.center(params.extent).clone(); // Read previously cached values (index and uv.wgs84 only depend on the # of triangles) let cachedBuffers = cacheBuffer.get(bufferKey); - params.buildIndexAndWGS84 = !cachedBuffers; + params.buildIndexAndUv_0 = !cachedBuffers; params.builder = builder; return Promise.resolve(computeBuffers(params)).then((buffers) => { if (!cachedBuffers) { cachedBuffers = {}; cachedBuffers.index = new THREE.BufferAttribute(buffers.index, 1); - cachedBuffers.uvwgs84 = new THREE.BufferAttribute(buffers.uv.wgs84, 2); + cachedBuffers.uv = new THREE.BufferAttribute(buffers.uvs[0], 2); // Update cacheBuffer cacheBuffer.set(bufferKey, cachedBuffers); } buffers.index = cachedBuffers.index; - buffers.uv.wgs84 = cachedBuffers.uvwgs84; + buffers.uvs[0] = cachedBuffers.uv; buffers.position = new THREE.BufferAttribute(buffers.position, 3); buffers.normal = new THREE.BufferAttribute(buffers.normal, 3); - buffers.uv.pm = new THREE.BufferAttribute(buffers.uv.pm, 1); + if (params.builder.uvCount > 1) { + buffers.uvs[1] = new THREE.BufferAttribute(buffers.uvs[1], 1); + } const geometry = new TileGeometry(params, buffers); geometry.OBB = builder.OBB(geometry.boundingBox); diff --git a/src/Core/Prefab/computeBufferTileGeometry.js b/src/Core/Prefab/computeBufferTileGeometry.js index 99f8087d2b..9158914326 100644 --- a/src/Core/Prefab/computeBufferTileGeometry.js +++ b/src/Core/Prefab/computeBufferTileGeometry.js @@ -6,7 +6,7 @@ export default function computeBuffers(params) { index: null, position: null, normal: null, - // 2 UV set per tile: wgs84 and pm + // 2 UV set per tile: wgs84 (uv_0) and pm (uv_1) // - wgs84: 1 texture per tile because tiles are using wgs84 projection // - pm: use multiple textures per tile. // +-------------------------+ @@ -21,14 +21,11 @@ export default function computeBuffers(params) { // +-------------------------+ // * u = wgs84.u // * v = textureid + v in builder texture - uv: { - wgs84: null, - pm: null, - }, + uvs: [], }; + const computeUvs = []; const builder = params.builder; - const nSeg = params.segment; // segments count : // Tile : (nSeg + 1) * (nSeg + 1) @@ -38,13 +35,21 @@ export default function computeBuffers(params) { outBuffers.position = new Float32Array(nVertex * 3); outBuffers.normal = new Float32Array(nVertex * 3); - outBuffers.uv.pm = new Float32Array(nVertex); - if (params.buildIndexAndWGS84) { - outBuffers.index = new Uint32Array(triangles * 3); - outBuffers.uv.wgs84 = new Float32Array(nVertex * 2); + const uvCount = params.builder.uvCount; + if (uvCount > 1) { + outBuffers.uvs[1] = new Float32Array(nVertex); } + computeUvs[0] = () => {}; + if (params.buildIndexAndUv_0) { + outBuffers.index = new Uint32Array(triangles * 3); + outBuffers.uvs[0] = new Float32Array(nVertex * 2); + computeUvs[0] = (id, u, v) => { + outBuffers.uvs[0][id * 2 + 0] = u; + outBuffers.uvs[0][id * 2 + 1] = v; + }; + } const widthSegments = Math.max(2, Math.floor(nSeg) || 2); const heightSegments = Math.max(2, Math.floor(nSeg) || 2); @@ -55,29 +60,17 @@ export default function computeBuffers(params) { builder.prepare(params); - let UV_WGS84 = function UV_WGS84() { }; - let UV_PM = function UV_PM() { }; - - // Define UV computation functions if needed - if (params.buildIndexAndWGS84) { - UV_WGS84 = function UV_WGS84(out, id, u, v) { - out.uv.wgs84[id * 2 + 0] = u; - out.uv.wgs84[id * 2 + 1] = v; - }; - } - if (builder.getUV_PM) { - UV_PM = function UV_PM(out, id, u) { - out.uv.pm[id] = u; - }; - } - for (let y = 0; y <= heightSegments; y++) { const verticesRow = []; const v = y / heightSegments; builder.vProjecte(v, params); - - const uv_pm = builder.getUV_PM ? builder.getUV_PM(params) : undefined; + if (uvCount > 1) { + const u = builder.computeUvs[1](params); + computeUvs[1] = (id) => { + outBuffers.uvs[1][id] = u; + }; + } for (let x = 0; x <= widthSegments; x++) { const u = x / widthSegments; @@ -100,8 +93,9 @@ export default function computeBuffers(params) { vertex.toArray(outBuffers.position, id_m3); normal.toArray(outBuffers.normal, id_m3); - UV_WGS84(outBuffers, idVertex, u, v); - UV_PM(outBuffers, idVertex, uv_pm); + for (const computeUv of computeUvs) { + computeUv(idVertex, u, v); + } if (!params.disableSkirt) { if (y !== 0 && y !== heightSegments) { @@ -140,7 +134,7 @@ export default function computeBuffers(params) { let idVertex2 = 0; - if (params.buildIndexAndWGS84) { + if (params.buildIndexAndUv_0) { for (let y = 0; y < heightSegments; y++) { for (let x = 0; x < widthSegments; x++) { const v1 = vertices[y][x + 1]; @@ -167,7 +161,7 @@ export default function computeBuffers(params) { let buildIndexSkirt = function buildIndexSkirt() { }; let buildUVSkirt = function buildUVSkirt() { }; - if (params.buildIndexAndWGS84) { + if (params.buildIndexAndUv_0) { buildIndexSkirt = function buildIndexSkirt(id, v1, v2, v3, v4) { id = bufferize(v1, v2, v3, id); id = bufferize(v1, v3, v4, id); @@ -175,8 +169,8 @@ export default function computeBuffers(params) { }; buildUVSkirt = function buildUVSkirt(id) { - outBuffers.uv.wgs84[idVertex * 2 + 0] = outBuffers.uv.wgs84[id * 2 + 0]; - outBuffers.uv.wgs84[idVertex * 2 + 1] = outBuffers.uv.wgs84[id * 2 + 1]; + outBuffers.uvs[0][idVertex * 2 + 0] = outBuffers.uvs[0][id * 2 + 0]; + outBuffers.uvs[0][idVertex * 2 + 1] = outBuffers.uvs[0][id * 2 + 1]; }; } @@ -198,7 +192,9 @@ export default function computeBuffers(params) { buildUVSkirt(id); - outBuffers.uv.pm[idVertex] = outBuffers.uv.pm[id]; + if (uvCount > 1) { + outBuffers.uvs[1][idVertex] = outBuffers.uvs[1][id]; + } const idf = (i + 1) % skirt.length; diff --git a/src/Core/TileGeometry.js b/src/Core/TileGeometry.js index c978eea3b5..156be393ed 100644 --- a/src/Core/TileGeometry.js +++ b/src/Core/TileGeometry.js @@ -2,14 +2,16 @@ import * as THREE from 'three'; import computeBuffers from 'Core/Prefab/computeBufferTileGeometry'; function defaultBuffers(params) { - params.buildIndexAndWGS84 = true; + params.buildIndexAndUv_0 = true; params.center = params.builder.center(params.extent).clone(); const buffers = computeBuffers(params); buffers.index = new THREE.BufferAttribute(buffers.index, 1); - buffers.uv.wgs84 = new THREE.BufferAttribute(buffers.uv.wgs84, 2); + buffers.uvs[0] = new THREE.BufferAttribute(buffers.uvs[0], 2); buffers.position = new THREE.BufferAttribute(buffers.position, 3); buffers.normal = new THREE.BufferAttribute(buffers.normal, 3); - buffers.uv.pm = new THREE.BufferAttribute(buffers.uv.pm, 1); + for (let i = 1; i < params.builder.uvCount; i++) { + buffers.uvs[1] = new THREE.BufferAttribute(buffers.uvs[1], 1); + } return buffers; } @@ -20,10 +22,12 @@ class TileGeometry extends THREE.BufferGeometry { this.extent = params.extent; this.setIndex(buffers.index); - this.addAttribute('uv_wgs84', buffers.uv.wgs84); this.addAttribute('position', buffers.position); this.addAttribute('normal', buffers.normal); - this.addAttribute('uv_pm', buffers.uv.pm); + + for (let i = 0; i < buffers.uvs.length; i++) { + this.addAttribute(`uv_${i}`, buffers.uvs[i]); + } this.computeBoundingBox(); this.OBB = {}; diff --git a/src/Core/TileMesh.js b/src/Core/TileMesh.js index 27245b3543..6c1f750654 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,6 +1,5 @@ import * as THREE from 'three'; -import OGCWebServiceHelper from 'Provider/OGCWebServiceHelper'; -import Projection from 'Core/Geographic/Projection'; +import CRS from 'Core/Geographic/Crs'; /** * A TileMesh is a THREE.Mesh with a geometricError and an OBB @@ -21,25 +20,20 @@ class TileMesh extends THREE.Mesh { } this.layer = layer; this.extent = extent; + this.extent.zoom = level; + this.level = level; - // Set equivalent tiled extent zoom - // Is used to set zoom for each texture fetched with no tiled extent - // It's more simple to set zoom here instead of reverse ingeneer - // Removable with a table pixel/extent.size - if (!this.extent.zoom) { - this.extent.zoom = level; - } this.material.objectId = this.id; this.obb = this.geometry.OBB.clone(); this.boundingSphere = new THREE.Sphere(); this.obb.box3D.getBoundingSphere(this.boundingSphere); - this.tilesetExtents = {}; + this._tms = new Map(); - // 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); + for (const tms of layer.tileMatrixSets) { + this._tms.set(tms, this.extent.tiledCovering(tms)); + } this.frustumCulled = false; this.matrixAutoUpdate = false; @@ -67,22 +61,8 @@ class TileMesh extends THREE.Mesh { } } - 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) { - return [this.extent]; - } else { - return [this.extent.as(source.extent.crs)]; - } - } - - getZoomForLayer(layer) { - return this.getExtentsForSource(layer.source)[0].zoom || this.level; + getExtentsByProjection(projection) { + return this._tms.get(CRS.formatToTms(projection)); } /** @@ -112,17 +92,6 @@ class TileMesh extends THREE.Mesh { } } - findAncestorFromLevel(targetLevel) { - let parentAtLevel = this; - while (parentAtLevel && parentAtLevel.level > targetLevel) { - parentAtLevel = parentAtLevel.parent; - } - if (!parentAtLevel) { - return Promise.reject(`Invalid targetLevel requested ${targetLevel}`); - } - return parentAtLevel; - } - onBeforeRender() { if (this.material.layersNeedUpdate) { this.material.updateLayersUniforms(); diff --git a/src/Core/View.js b/src/Core/View.js index b9a5a756ec..fb949c6441 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -5,6 +5,7 @@ import MainLoop, { MAIN_LOOP_EVENTS, RENDERING_PAUSED } from 'Core/MainLoop'; import { COLOR_LAYERS_ORDER_CHANGED } from 'Renderer/ColorLayersOrdering'; import c3DEngine from 'Renderer/c3DEngine'; import RenderMode from 'Renderer/RenderMode'; +import CRS from 'Core/Geographic/Crs'; import { getMaxColorSamplerUnitsCount } from 'Renderer/LayeredMaterial'; @@ -44,10 +45,11 @@ const _syncGeometryLayerVisibility = function _syncGeometryLayerVisibility(layer }; function _preprocessLayer(view, layer, provider, parentLayer) { + const source = layer.source; if (parentLayer && !layer.extent) { layer.extent = parentLayer.extent; - if (layer.source && !layer.source.extent) { - layer.source.extent = parentLayer.extent; + if (source && !source.extent) { + source.extent = parentLayer.extent; } } @@ -61,8 +63,8 @@ function _preprocessLayer(view, layer, provider, parentLayer) { _syncGeometryLayerVisibility(layer, view); // Find projection layer, this is projection destination layer.projection = view.referenceCrs; - } else if (layer.source.tileMatrixSet === 'PM' || layer.source.projection == 'EPSG:3857') { - layer.projection = 'EPSG:3857'; + } else if (source.tileMatrixSet || parentLayer.tileMatrixSets.includes(CRS.formatToTms(source.projection))) { + layer.projection = source.projection; } else { layer.projection = parentLayer.extent.crs; } @@ -70,8 +72,8 @@ function _preprocessLayer(view, layer, provider, parentLayer) { if (!layer.whenReady) { if (provider && provider.preprocessDataLayer) { layer.whenReady = provider.preprocessDataLayer(layer, view, view.mainLoop.scheduler, parentLayer); - } else if (layer.source && layer.source.whenReady) { - layer.whenReady = layer.source.whenReady; + } else if (source && source.whenReady) { + layer.whenReady = source.whenReady; } else { layer.whenReady = Promise.resolve(); } diff --git a/src/Layer/OrientedImageLayer.js b/src/Layer/OrientedImageLayer.js index eab9cf35f3..f3a29ea9a3 100644 --- a/src/Layer/OrientedImageLayer.js +++ b/src/Layer/OrientedImageLayer.js @@ -28,6 +28,7 @@ function updatePano(context, camera, layer) { const imagesInfo = layer.cameras.map(cam => ({ cameraId: cam.name, panoId: newPano.id, + as: () => {}, toString: (separator = '') => (`${cam.name}${separator}${newPano.id}`), })).filter(info => !panoCameras || panoCameras.includes(info.cameraId)); diff --git a/src/Layer/TiledGeometryLayer.js b/src/Layer/TiledGeometryLayer.js index 6be10fe380..d9d79680d4 100644 --- a/src/Layer/TiledGeometryLayer.js +++ b/src/Layer/TiledGeometryLayer.js @@ -5,7 +5,7 @@ import Picking from 'Core/Picking'; import convertToTile from 'Converter/convertToTile'; import CancelledCommandException from 'Core/Scheduler/CancelledCommandException'; import ObjectRemovalHelper from 'Process/ObjectRemovalHelper'; -import { SIZE_DIAGONAL_TEXTURE } from 'Provider/OGCWebServiceHelper'; +import { SIZE_DIAGONAL_TEXTURE } from 'Process/LayeredMaterialNodeProcessing'; import { ImageryLayers } from 'Layer/Layer'; const subdivisionVector = new THREE.Vector3(); @@ -273,7 +273,7 @@ class TiledGeometryLayer extends GeometryLayer { let nodeLayer = node.material.getElevationLayer(); for (const e of context.elevationLayers) { - const extents = node.getExtentsForSource(e.source); + const extents = node.getExtentsByProjection(e.projection); 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.getExtentsForSource(c.source); + const extents = node.getExtentsByProjection(c.projection); nodeLayer = node.material.getLayer(c.id); if (c.source.extentsInsideLimit(extents) && (!nodeLayer || nodeLayer.level < 0)) { return false; diff --git a/src/Parser/VectorTileParser.js b/src/Parser/VectorTileParser.js index f71c592ac9..baa3ae03b8 100644 --- a/src/Parser/VectorTileParser.js +++ b/src/Parser/VectorTileParser.js @@ -1,11 +1,12 @@ import { Vector2, Vector3 } from 'three'; import Protobuf from 'pbf'; import { VectorTile } from '@mapbox/vector-tile'; -import { worldDimension3857 } from 'Core/Geographic/Extent'; +import { globalExtentTMS } from 'Core/Geographic/Extent'; import { FeatureCollection, FEATURE_TYPES } from 'Core/Feature'; import { featureFilter } from '@mapbox/mapbox-gl-style-spec'; import Style from 'Core/Style'; +const worldDimension3857 = globalExtentTMS.get('EPSG:3857').dimensions(); const globalExtent = new Vector3(worldDimension3857.x, worldDimension3857.y, 1); const lastPoint = new Vector2(); const firstPoint = new Vector2(); diff --git a/src/Process/FeatureProcessing.js b/src/Process/FeatureProcessing.js index 03f42d7abc..5c50dde125 100644 --- a/src/Process/FeatureProcessing.js +++ b/src/Process/FeatureProcessing.js @@ -3,7 +3,9 @@ import LayerUpdateState from 'Layer/LayerUpdateState'; import ObjectRemovalHelper from 'Process/ObjectRemovalHelper'; import handlingError from 'Process/handlerNodeError'; import Coordinates from 'Core/Geographic/Coordinates'; +import Extent from 'Core/Geographic/Extent'; +const _extent = new Extent('EPSG:4326', 0, 0, 0, 0); const coord = new Coordinates('EPSG:4326', 0, 0, 0); const vector = new THREE.Vector3(); const tmp = new THREE.Vector3(); @@ -85,12 +87,15 @@ export default { return features; } - const extentsDestination = node.getExtentsForSource(layer.source); + const extentsDestination = node.getExtentsByProjection(layer.source.projection) || [node.extent]; + extentsDestination.forEach((e) => { e.zoom = node.level; }); const extentsSource = []; for (const extentDest of extentsDestination) { - if (extentInsideSource(extentDest, layer.source)) { + const ext = layer.source.projection == extentDest.crs ? extentDest : extentDest.as(layer.source.projection); + ext.zoom = extentDest.zoom; + if (extentInsideSource(ext, layer.source)) { node.layerUpdateState[layer.id].noMoreUpdatePossible(); return; } @@ -115,7 +120,9 @@ export default { // to attach it to the correct node if (layer.source && layer.source.isFileSource) { for (const extentSrc of extentsSource) { - if (extentInsideSource(extentSrc, layer.source)) { + const ext = extentSrc.crs == layer.source.projection ? extentSrc : extentSrc.as(layer.source.projection, _extent); + ext.zoom = extentSrc.zoom; + if (extentInsideSource(ext, layer.source)) { node.layerUpdateState[layer.id].noMoreUpdatePossible(); return; } diff --git a/src/Process/LayeredMaterialNodeProcessing.js b/src/Process/LayeredMaterialNodeProcessing.js index b0e24170b2..9f0ae30ab5 100644 --- a/src/Process/LayeredMaterialNodeProcessing.js +++ b/src/Process/LayeredMaterialNodeProcessing.js @@ -1,16 +1,10 @@ import { chooseNextLevelToFetch } from 'Layer/LayerUpdateStrategy'; import LayerUpdateState from 'Layer/LayerUpdateState'; -import { SIZE_TEXTURE_TILE } from 'Provider/OGCWebServiceHelper'; import { computeMinMaxElevation } from 'Parser/XbilParser'; import handlingError from 'Process/handlerNodeError'; -function getSourceExtent(node, extent, targetLevel) { - if (extent.isTiledCrs()) { - return extent.tiledExtentParent(targetLevel); - } else { - return node.findAncestorFromLevel(targetLevel).extent; - } -} +export const SIZE_TEXTURE_TILE = 256; +export const SIZE_DIAGONAL_TEXTURE = Math.pow(2 * (SIZE_TEXTURE_TILE * SIZE_TEXTURE_TILE), 0.5); function materialCommandQueuePriorityFunction(material) { // We know that 'node' is visible because commands can only be @@ -58,8 +52,7 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { if (!parent || !material) { return; } - - const extentsDestination = node.getExtentsForSource(layer.source); + const extentsDestination = node.getExtentsByProjection(layer.projection); let nodeLayer = material.getLayer(layer.id); @@ -120,7 +113,7 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { return; } - if (nodeLayer.level >= node.getZoomForLayer(layer)) { + if (nodeLayer.level >= extentsDestination[0].zoom) { // default decision method node.layerUpdateState[layer.id].noMoreUpdatePossible(); return; @@ -134,15 +127,16 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { const failureParams = node.layerUpdateState[layer.id].failureParams; const destinationLevel = extentsDestination[0].zoom || node.level; const targetLevel = chooseNextLevelToFetch(layer.updateStrategy.type, node, destinationLevel, nodeLayer.level, layer, failureParams); - if (targetLevel <= nodeLayer.level) { + + if (targetLevel <= nodeLayer.level || targetLevel > destinationLevel) { return; } // Get equivalent of extent destination in source const extentsSource = []; for (const extentDestination of extentsDestination) { - const extentSource = getSourceExtent(node, extentDestination, targetLevel); - if (extentSource && !layer.source.extentInsideLimit(extentSource)) { + const extentSource = extentDestination.tiledExtentParent(targetLevel); + if (!layer.source.extentInsideLimit(extentSource)) { // Retry extentInsideLimit because you must check with the targetLevel // if the first test extentInsideLimit returns that it is out of limits // and the node inherits from its parent, then it'll still make a command to fetch texture. @@ -179,7 +173,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.getExtentsForSource(layer.source); + const extentsDestination = node.getExtentsByProjection(layer.projection); // Init elevation layer, and inherit from parent if possible let nodeLayer = material.getElevationLayer(); if (!nodeLayer) { @@ -224,15 +218,15 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) const targetLevel = chooseNextLevelToFetch(layer.updateStrategy.type, node, extentsDestination[0].zoom, nodeLayer.level, layer); - if (targetLevel <= nodeLayer.level) { + if (targetLevel <= nodeLayer.level || targetLevel > extentsDestination[0].zoom) { node.layerUpdateState[layer.id].noMoreUpdatePossible(); return Promise.resolve(); } const extentsSource = []; for (const nodeExtent of extentsDestination) { - const extentSource = getSourceExtent(node, nodeExtent, targetLevel); - if (extentSource && !layer.source.extentInsideLimit(extentSource)) { + const extentSource = nodeExtent.tiledExtentParent(targetLevel); + if (!layer.source.extentInsideLimit(extentSource)) { node.layerUpdateState[layer.id].noMoreUpdatePossible(); return; } diff --git a/src/Provider/DataSourceProvider.js b/src/Provider/DataSourceProvider.js index 707c4936c0..ab96dbf790 100644 --- a/src/Provider/DataSourceProvider.js +++ b/src/Provider/DataSourceProvider.js @@ -42,7 +42,7 @@ function parseSourceData(data, extSrc, extDest, layer) { crsOut: layer.projection, // TODO FIXME: error in filtering vector tile // filteringExtent: extentDestination.as(layer.projection), - filteringExtent: !source.isFileSource && layer.isGeometryLayer ? extDest : undefined, + filteringExtent: !source.isFileSource && layer.isGeometryLayer ? extDest.as(source.projection) : undefined, overrideAltitudeInToZero: layer.overrideAltitudeInToZero, filter: layer.filter, isInverted: source.isInverted, diff --git a/src/Provider/OGCWebServiceHelper.js b/src/Provider/OGCWebServiceHelper.js deleted file mode 100644 index 952cadf2b5..0000000000 --- a/src/Provider/OGCWebServiceHelper.js +++ /dev/null @@ -1,42 +0,0 @@ -import * as THREE from 'three'; -import Extent from 'Core/Geographic/Extent'; -import Coordinates from 'Core/Geographic/Coordinates'; - -const c = new Coordinates('EPSG:4326', 180, 85.06); -const layerDimension = new THREE.Vector2(); -const tileDimension = new THREE.Vector2(); - -// Size in pixel -export const SIZE_TEXTURE_TILE = 256; -export const SIZE_DIAGONAL_TEXTURE = Math.pow(2 * (SIZE_TEXTURE_TILE * SIZE_TEXTURE_TILE), 0.5); - -export default { - // 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. - // See link below for more information - // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/ - computeTMSCoordinates(tile, extent, isInverted) { - extent = tile.extent.crs == extent.crs ? extent : extent.as(tile.extent.crs, extent); - tile.extent.center(c); - extent.dimensions(layerDimension); - tile.extent.dimensions(tileDimension); - - // Each level has 2^n * 2^n tiles... - // ... so we count how many tiles of the same width as tile we can fit in the layer - const tileCount = Math.round(layerDimension.x / tileDimension.x); - // ... 2^zoom = tilecount => zoom = log2(tilecount) - const zoom = Math.floor(Math.log2(tileCount)); - - // Now that we have computed zoom, we can deduce x and y (or row / column) - const x = (c.x - extent.west) / layerDimension.x; - let y; - if (isInverted) { - y = (extent.north - c.y) / layerDimension.y; - } else { - y = (c.y - extent.south) / layerDimension.y; - } - - return [new Extent('TMS', zoom, Math.floor(y * tileCount), Math.floor(x * tileCount))]; - }, -}; diff --git a/src/Renderer/LayeredMaterial.js b/src/Renderer/LayeredMaterial.js index 116fba24ea..20b54c89fb 100644 --- a/src/Renderer/LayeredMaterial.js +++ b/src/Renderer/LayeredMaterial.js @@ -20,11 +20,6 @@ export function unpack1K(color, factor) { return factor ? bitSh.dot(color) * factor : bitSh.dot(color); } -export const CRS_DEFINES = [ - ['WGS84', 'WGS84G', 'TMS', 'EPSG:3946', 'EPSG:4326', 'WMTS:WGS84G'], - ['PM', 'WMTS:PM', 'EPSG:3857'], -]; - // Max sampler color count to LayeredMaterial // Because there's a statement limitation to unroll, in getColorAtIdUv method const maxSamplersColorCount = 15; @@ -98,9 +93,9 @@ export const ELEVATION_MODES = { }; let nbSamplers; -let fragmentShader; +const fragmentShader = []; class LayeredMaterial extends THREE.RawShaderMaterial { - constructor(options = {}) { + constructor(options = {}, crsCount) { super(options); nbSamplers = nbSamplers || [samplersElevationCount, getMaxColorSamplerUnitsCount()]; @@ -109,11 +104,7 @@ class LayeredMaterial extends THREE.RawShaderMaterial { this.defines.NUM_FS_TEXTURES = nbSamplers[1]; this.defines.USE_FOG = 1; this.defines.EPSILON = 1e-6; - - for (let i = 0, il = CRS_DEFINES.length; i < il; ++i) { - this.defines[`CRS_${CRS_DEFINES[i][0]}`] = i; - } - this.defines.NUM_CRS = CRS_DEFINES.length; + this.defines.NUM_CRS = crsCount; setDefineMapping(this, 'ELEVATION', ELEVATION_MODES); setDefineMapping(this, 'MODE', RenderMode.MODES); @@ -121,10 +112,10 @@ class LayeredMaterial extends THREE.RawShaderMaterial { if (__DEBUG__) { this.defines.DEBUG = 1; - const outlineColors = [ - new THREE.Vector3(1.0, 0.0, 0.0), - new THREE.Vector3(1.0, 0.5, 0.0), - ]; + const outlineColors = [new THREE.Vector3(1.0, 0.0, 0.0)]; + if (crsCount > 1) { + outlineColors.push(new THREE.Vector3(1.0, 0.5, 0.0)); + } setUniformProperty(this, 'showOutline', true); setUniformProperty(this, 'outlineWidth', 0.008); setUniformProperty(this, 'outlineColors', outlineColors); @@ -136,8 +127,8 @@ class LayeredMaterial extends THREE.RawShaderMaterial { } this.vertexShader = TileVS; - fragmentShader = fragmentShader || ShaderUtils.unrollLoops(TileFS, this.defines); - this.fragmentShader = fragmentShader; + fragmentShader[crsCount] = fragmentShader[crsCount] || ShaderUtils.unrollLoops(TileFS, this.defines); + this.fragmentShader = fragmentShader[crsCount]; // Color uniforms setUniformProperty(this, 'diffuse', new THREE.Color(0.04, 0.23, 0.35)); diff --git a/src/Renderer/MaterialLayer.js b/src/Renderer/MaterialLayer.js index 1989109844..a709949e4c 100644 --- a/src/Renderer/MaterialLayer.js +++ b/src/Renderer/MaterialLayer.js @@ -1,6 +1,7 @@ import * as THREE from 'three'; -import { CRS_DEFINES, ELEVATION_MODES } from 'Renderer/LayeredMaterial'; +import { ELEVATION_MODES } from 'Renderer/LayeredMaterial'; import { checkNodeElevationTextureValidity, insertSignificantValuesFromParent } from 'Parser/XbilParser'; +import CRS from 'Core/Geographic/Crs'; export const EMPTY_TEXTURE_ZOOM = -1; @@ -22,7 +23,7 @@ class MaterialLayer { constructor(material, layer) { this.id = layer.id; this.textureOffset = 0; // will be updated in updateUniforms() - this.crs = CRS_DEFINES.findIndex(crs => crs.includes(layer.projection || 'WGS84')); + this.crs = layer.parent.tileMatrixSets.indexOf(CRS.formatToTms(layer.projection)); if (this.crs == -1) { console.error('Unknown crs:', layer.projection); } diff --git a/src/Renderer/OBB.js b/src/Renderer/OBB.js index 15f1730af4..cf77cbda57 100644 --- a/src/Renderer/OBB.js +++ b/src/Renderer/OBB.js @@ -5,7 +5,7 @@ import Coordinates from 'Core/Geographic/Coordinates'; import CRS from 'Core/Geographic/Crs'; // get oriented bounding box of tile -const builder = new BuilderEllipsoidTile(); +const builder = new BuilderEllipsoidTile({ projection: 'EPSG:4978', uvCount: 1 }); const size = new THREE.Vector3(); const dimension = new THREE.Vector2(); const center = new THREE.Vector3(); diff --git a/src/Renderer/Shader/TileFS.glsl b/src/Renderer/Shader/TileFS.glsl index 3b8608f474..c4da5d73b6 100644 --- a/src/Renderer/Shader/TileFS.glsl +++ b/src/Renderer/Shader/TileFS.glsl @@ -11,7 +11,7 @@ uniform vec3 diffuse; uniform float opacity; -varying vec3 vUv; // WGS84.x/PM.x, WGS84.y, PM.y +varying vec3 vUv; // uv_0.x/uv_1.x, uv_0.y, uv_1.y void main() { #include @@ -28,8 +28,12 @@ void main() { gl_FragColor = vec4(diffuse, opacity); - uvs[CRS_WGS84] = vec3(vUv.xy, 0.); - uvs[CRS_PM] = vec3(vUv.x, fract(vUv.z), floor(vUv.z)); + uvs[0] = vec3(vUv.xy, 0.); + +#if NUM_CRS > 1 + uvs[1] = vec3(vUv.x, fract(vUv.z), floor(vUv.z)); +#endif + vec4 color; #pragma unroll_loop diff --git a/src/Renderer/Shader/TileVS.glsl b/src/Renderer/Shader/TileVS.glsl index 9d7351184e..d159ab6c18 100644 --- a/src/Renderer/Shader/TileVS.glsl +++ b/src/Renderer/Shader/TileVS.glsl @@ -2,8 +2,10 @@ #include #include #include -attribute float uv_pm; -attribute vec2 uv_wgs84; +attribute vec2 uv_0; +#if NUM_CRS > 1 +attribute float uv_1; +#endif attribute vec3 normal; uniform mat4 modelMatrix; @@ -11,11 +13,11 @@ uniform bool lightingEnabled; #if MODE == MODE_FINAL #include -varying vec3 vUv; +varying vec3 vUv; varying vec3 vNormal; #endif void main() { - vec2 uv = vec2(uv_wgs84.x, 1.0 - uv_wgs84.y); + vec2 uv = vec2(uv_0.x, 1.0 - uv_0.y); #include #include @@ -23,7 +25,11 @@ void main() { #include #if MODE == MODE_FINAL #include - vUv = vec3(uv_wgs84, (uv_pm > 0.) ? uv_pm : uv_wgs84.y); // set pm=wgs84 if pm=0 (not computed) + #if NUM_CRS > 1 + vUv = vec3(uv_0, (uv_1 > 0.) ? uv_1 : uv_0.y); // set uv_1 = uv_0 if uv_1 is undefined + #else + vUv = vec3(uv_0, 0.0); + #endif vNormal = normalize ( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal ); #endif } diff --git a/src/Source/FileSource.js b/src/Source/FileSource.js index e7484e980e..2f26f34752 100644 --- a/src/Source/FileSource.js +++ b/src/Source/FileSource.js @@ -137,7 +137,8 @@ class FileSource extends Source { } extentInsideLimit(extent) { - const localExtent = this.projection == extent.crs ? extent : extent.as(this.projection, ext); + // Fix me => may be not + const localExtent = this.extent.crs == extent.crs ? extent : extent.as(this.extent.crs, ext); return (extent.zoom == undefined || !(extent.zoom < this.zoom.min || extent.zoom > this.zoom.max)) && this.extent.intersectsExtent(localExtent); } diff --git a/src/Source/TMSSource.js b/src/Source/TMSSource.js index 68f5317d08..0b79b7a0a4 100644 --- a/src/Source/TMSSource.js +++ b/src/Source/TMSSource.js @@ -1,6 +1,7 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; +import CRS from 'Core/Geographic/Crs'; /** * @classdesc @@ -56,6 +57,9 @@ class TMSSource extends Source { * @constructor */ constructor(source) { + if (!source.projection) { + throw new Error('New TMSSource: projection is required'); + } super(source); this.isTMSSource = true; @@ -73,13 +77,9 @@ class TMSSource extends Source { this.format = this.format || 'image/png'; this.url = source.url; - if (source.tileMatrixSet) { - this.tileMatrixSet = source.tileMatrixSet; - } else if (this.projection == 'EPSG:3857') { - this.tileMatrixSet = 'PM'; - } else { - this.tileMatrixSet = 'WGS84'; - } + this.tileMatrixSet = source.tileMatrixSet; + + this.projection = CRS.formatToTms(source.projection); } urlFromExtent(extent) { diff --git a/src/Source/WMSSource.js b/src/Source/WMSSource.js index a0084a49d6..9adbbc5a59 100644 --- a/src/Source/WMSSource.js +++ b/src/Source/WMSSource.js @@ -1,6 +1,8 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; +import Extent from 'Core/Geographic/Extent'; +const _extent = new Extent('EPSG:4326', 0, 0, 0, 0); /** * @classdesc * An object defining the source of images to get from a @@ -134,7 +136,7 @@ class WMSSource extends Source { } extentInsideLimit(extent) { - const localExtent = this.projection == extent.crs ? extent : extent.as(this.projection); + const localExtent = this.projection == extent.crs ? extent : extent.as(this.projection, _extent); return (extent.zoom == undefined || !(extent.zoom < this.zoom.min || extent.zoom > this.zoom.max)) && this.extent.intersectsExtent(localExtent); } diff --git a/src/Source/WMTSSource.js b/src/Source/WMTSSource.js index e18d10dd2b..9169f446b7 100644 --- a/src/Source/WMTSSource.js +++ b/src/Source/WMTSSource.js @@ -1,5 +1,6 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; +import CRS from 'Core/Geographic/Crs'; /** * @classdesc @@ -70,6 +71,10 @@ class WMTSSource extends Source { throw new Error('New WMTSSource: name is required'); } + if (!source.projection) { + throw new Error('New WMTSSource: projection is required'); + } + super(source); this.isWMTSSource = true; @@ -91,17 +96,7 @@ class WMTSSource extends Source { this.zoom = source.zoom; this.tileMatrixSetLimits = source.tileMatrixSetLimits; - - // If the projection is undefined, - // It is deduced from the tileMatrixSet, - // The projection is coherent with the projection - if (!this.projection) { - if (this.tileMatrixSet === 'WGS84' || this.tileMatrixSet === 'WGS84G') { - this.projection = 'EPSG:4326'; - } else { - this.projection = 'EPSG:3857'; - } - } + this.projection = CRS.formatToTms(source.projection); if (!this.zoom) { if (this.tileMatrixSetLimits) {