@@ -16,7 +16,7 @@ import {
1616} from '@deck.gl/core' ;
1717import { SimpleMeshLayer } from '@deck.gl/mesh-layers' ;
1818import { COORDINATE_SYSTEM } from '@deck.gl/core' ;
19- import type { MeshAttributes } from '@loaders.gl/schema' ;
19+ import type { Mesh } from '@loaders.gl/schema' ;
2020import { TerrainWorkerLoader } from '@loaders.gl/terrain' ;
2121import TileLayer , { TileLayerProps } from '../tile-layer/tile-layer' ;
2222import type {
@@ -33,6 +33,8 @@ const TILE_OVERLAP_PIXELS = 1;
3333const MIN_TERRAIN_MESH_MAX_ERROR = 1 ;
3434const MAX_LATITUDE = 90 ;
3535const MAX_LONGITUDE = 180 ;
36+ const DEGREES_TO_RADIANS = Math . PI / 180 ;
37+ const RADIANS_TO_DEGREES = 180 / Math . PI ;
3638
3739const defaultProps : DefaultProps < TerrainLayerProps > = {
3840 ...TileLayer . defaultProps ,
@@ -111,9 +113,9 @@ type TerrainLoadProps = {
111113 signal ?: AbortSignal ;
112114} ;
113115
114- type MeshAndTexture = [ MeshAttributes | null , TextureSource | null ] ;
116+ type MeshAndTexture = [ Mesh | null , TextureSource | null ] ;
115117type MeshBoundingBox = [ min : number [ ] , max : number [ ] ] ;
116- type MeshWithBoundingBox = MeshAttributes & {
118+ type MeshWithBoundingBox = Mesh & {
117119 header ?: {
118120 boundingBox ?: MeshBoundingBox ;
119121 } ;
@@ -165,7 +167,7 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
165167
166168 state ! : {
167169 isTiled ?: boolean ;
168- terrain ?: MeshAttributes ;
170+ terrain ?: Mesh ;
169171 zRange ?: ZRange | null ;
170172 } ;
171173
@@ -204,7 +206,7 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
204206 elevationDecoder,
205207 meshMaxError,
206208 signal
207- } : TerrainLoadProps ) : Promise < MeshAttributes > | null {
209+ } : TerrainLoadProps ) : Promise < Mesh > | null {
208210 if ( ! elevationData ) {
209211 return null ;
210212 }
@@ -249,13 +251,16 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
249251 Boolean ( viewport . resolution && viewport . resolution > 0 )
250252 ) ;
251253
252- const terrain = this . loadTerrain ( {
253- elevationData : dataUrl ,
254- bounds : overlappedBounds ,
255- elevationDecoder,
256- meshMaxError,
257- signal
258- } ) ;
254+ const terrain =
255+ this . loadTerrain ( {
256+ elevationData : dataUrl ,
257+ bounds : overlappedBounds ,
258+ elevationDecoder,
259+ meshMaxError,
260+ signal
261+ } ) ?. then ( mesh =>
262+ viewport . resolution && mesh ? remapMeshToWebMercatorTile ( mesh , overlappedBounds ) : mesh
263+ ) ?? Promise . resolve ( null ) ;
259264 const surface = textureUrl
260265 ? // If surface image fails to load, the tile should still be displayed
261266 fetch ( textureUrl , { propName : 'texture' , layer : this , loaders : [ ] , signal} ) . catch ( _ => null )
@@ -319,11 +324,9 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
319324 const { zRange} = this . state ;
320325 const ranges = tiles
321326 . map ( tile => tile . content )
322- . filter ( Boolean )
323- . map ( arr => {
324- // @ts -ignore
325- const bounds = arr [ 0 ] . header . boundingBox ;
326- return bounds . map ( bound => bound [ 2 ] ) ;
327+ . flatMap ( arr => {
328+ const bounds = arr ?. [ 0 ] ?. header ?. boundingBox ;
329+ return bounds ? [ bounds . map ( bound => bound [ 2 ] ) ] : [ ] ;
327330 } ) ;
328331 if ( ranges . length === 0 ) {
329332 return ;
@@ -417,3 +420,45 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
417420
418421const isTileSetURL = ( url : string ) : boolean =>
419422 url . includes ( '{x}' ) && ( url . includes ( '{y}' ) || url . includes ( '{-y}' ) ) ;
423+
424+ function remapMeshToWebMercatorTile ( mesh : Mesh , bounds : Bounds ) : Mesh {
425+ const positionAttribute = mesh . attributes . POSITION ;
426+ const texCoordAttribute = mesh . attributes . TEXCOORD_0 ;
427+ const positions = positionAttribute ?. value ;
428+ const texCoords = texCoordAttribute ?. value ;
429+ if ( ! positions || ! texCoords ) {
430+ return mesh ;
431+ }
432+
433+ const [ , south , , north ] = bounds ;
434+ const northY = lngLatToMercatorY ( north ) ;
435+ const southY = lngLatToMercatorY ( south ) ;
436+ const remappedPositions = new Float32Array ( positions ) ;
437+
438+ for ( let i = 0 ; i < texCoords . length / 2 ; i ++ ) {
439+ const v = texCoords [ i * 2 + 1 ] ;
440+ const mercatorY = northY + ( southY - northY ) * v ;
441+ remappedPositions [ i * 3 + 1 ] = mercatorYToLat ( mercatorY ) ;
442+ }
443+
444+ return {
445+ ...mesh ,
446+ attributes : {
447+ ...mesh . attributes ,
448+ POSITION : {
449+ ...positionAttribute ,
450+ value : remappedPositions
451+ }
452+ }
453+ } ;
454+ }
455+
456+ function lngLatToMercatorY ( latitude : number ) : number {
457+ const clampedLatitude = Math . max ( - 85.051129 , Math . min ( 85.051129 , latitude ) ) ;
458+ const sin = Math . sin ( clampedLatitude * DEGREES_TO_RADIANS ) ;
459+ return 0.5 - Math . log ( ( 1 + sin ) / ( 1 - sin ) ) / ( 4 * Math . PI ) ;
460+ }
461+
462+ function mercatorYToLat ( y : number ) : number {
463+ return Math . atan ( Math . sinh ( Math . PI * ( 1 - 2 * y ) ) ) * RADIANS_TO_DEGREES ;
464+ }
0 commit comments