@@ -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 ,
@@ -255,7 +257,9 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
255257 elevationDecoder,
256258 meshMaxError,
257259 signal
258- } ) ;
260+ } ) ?. then ( mesh =>
261+ viewport . resolution && mesh ? remapMeshToWebMercatorTile ( mesh , overlappedBounds ) : mesh
262+ ) ;
259263 const surface = textureUrl
260264 ? // If surface image fails to load, the tile should still be displayed
261265 fetch ( textureUrl , { propName : 'texture' , layer : this , loaders : [ ] , signal} ) . catch ( _ => null )
@@ -417,3 +421,43 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
417421
418422const isTileSetURL = ( url : string ) : boolean =>
419423 url . includes ( '{x}' ) && ( url . includes ( '{y}' ) || url . includes ( '{-y}' ) ) ;
424+
425+ function remapMeshToWebMercatorTile ( mesh : MeshAttributes , bounds : Bounds ) : MeshAttributes {
426+ const positions = mesh . attributes . POSITION ?. value ;
427+ const texCoords = mesh . attributes . TEXCOORD_0 ?. value ;
428+ if ( ! positions || ! texCoords ) {
429+ return mesh ;
430+ }
431+
432+ const [ , south , , north ] = bounds ;
433+ const northY = lngLatToMercatorY ( north ) ;
434+ const southY = lngLatToMercatorY ( south ) ;
435+ const remappedPositions = new Float32Array ( positions ) ;
436+
437+ for ( let i = 0 ; i < texCoords . length / 2 ; i ++ ) {
438+ const v = texCoords [ i * 2 + 1 ] ;
439+ const mercatorY = northY + ( southY - northY ) * v ;
440+ remappedPositions [ i * 3 + 1 ] = mercatorYToLat ( mercatorY ) ;
441+ }
442+
443+ return {
444+ ...mesh ,
445+ attributes : {
446+ ...mesh . attributes ,
447+ POSITION : {
448+ ...mesh . attributes . POSITION ,
449+ value : remappedPositions
450+ }
451+ }
452+ } ;
453+ }
454+
455+ function lngLatToMercatorY ( latitude : number ) : number {
456+ const clampedLatitude = Math . max ( - 85.051129 , Math . min ( 85.051129 , latitude ) ) ;
457+ const sin = Math . sin ( clampedLatitude * DEGREES_TO_RADIANS ) ;
458+ return 0.5 - Math . log ( ( 1 + sin ) / ( 1 - sin ) ) / ( 4 * Math . PI ) ;
459+ }
460+
461+ function mercatorYToLat ( y : number ) : number {
462+ return Math . atan ( Math . sinh ( Math . PI * ( 1 - 2 * y ) ) ) * RADIANS_TO_DEGREES ;
463+ }
0 commit comments