@@ -29,6 +29,10 @@ import type {
2929import { Tile2DHeader , urlType , getURLFromTemplate , URLTemplate } from '../tileset-2d/index' ;
3030
3131const DUMMY_DATA = [ 1 ] ;
32+ const TILE_OVERLAP_PIXELS = 1 ;
33+ const MIN_TERRAIN_MESH_MAX_ERROR = 1 ;
34+ const MAX_LATITUDE = 90 ;
35+ const MAX_LONGITUDE = 180 ;
3236
3337const defaultProps : DefaultProps < TerrainLayerProps > = {
3438 ...TileLayer . defaultProps ,
@@ -69,6 +73,35 @@ function urlTemplateToUpdateTrigger(template: URLTemplate): string {
6973 return template || '' ;
7074}
7175
76+ function getOverlappedBounds ( bounds : Bounds , tileSize : number , clampLngLat : boolean ) : Bounds {
77+ const xPad = ( ( bounds [ 2 ] - bounds [ 0 ] ) / tileSize ) * TILE_OVERLAP_PIXELS ;
78+ const yPad = ( ( bounds [ 3 ] - bounds [ 1 ] ) / tileSize ) * TILE_OVERLAP_PIXELS ;
79+ const overlappedBounds : Bounds = [
80+ bounds [ 0 ] - xPad ,
81+ bounds [ 1 ] - yPad ,
82+ bounds [ 2 ] + xPad ,
83+ bounds [ 3 ] + yPad
84+ ] ;
85+
86+ if ( ! clampLngLat ) {
87+ return overlappedBounds ;
88+ }
89+
90+ return [
91+ Math . max ( overlappedBounds [ 0 ] , - MAX_LONGITUDE ) ,
92+ Math . max ( overlappedBounds [ 1 ] , - MAX_LATITUDE ) ,
93+ Math . min ( overlappedBounds [ 2 ] , MAX_LONGITUDE ) ,
94+ Math . min ( overlappedBounds [ 3 ] , MAX_LATITUDE )
95+ ] ;
96+ }
97+
98+ function getEffectiveMeshMaxError ( meshMaxError : number ) : number {
99+ if ( ! Number . isFinite ( meshMaxError ) || meshMaxError <= 0 ) {
100+ return MIN_TERRAIN_MESH_MAX_ERROR ;
101+ }
102+ return Math . max ( meshMaxError , MIN_TERRAIN_MESH_MAX_ERROR ) ;
103+ }
104+
72105type ElevationDecoder = { rScaler : number ; gScaler : number ; bScaler : number ; offset : number } ;
73106type TerrainLoadProps = {
74107 bounds : Bounds ;
@@ -79,6 +112,12 @@ type TerrainLoadProps = {
79112} ;
80113
81114type MeshAndTexture = [ MeshAttributes | null , TextureSource | null ] ;
115+ type MeshBoundingBox = [ min : number [ ] , max : number [ ] ] ;
116+ type MeshWithBoundingBox = MeshAttributes & {
117+ header ?: {
118+ boundingBox ?: MeshBoundingBox ;
119+ } ;
120+ } ;
82121
83122/** All properties supported by TerrainLayer */
84123export type TerrainLayerProps = _TerrainLayerProps &
@@ -169,14 +208,15 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
169208 if ( ! elevationData ) {
170209 return null ;
171210 }
211+ const effectiveMeshMaxError = getEffectiveMeshMaxError ( meshMaxError ) ;
172212 let loadOptions = this . getLoadOptions ( ) ;
173213 loadOptions = {
174214 ...loadOptions ,
175215 terrain : {
176- skirtHeight : this . state . isTiled ? meshMaxError * 2 : 0 ,
216+ skirtHeight : this . state . isTiled ? effectiveMeshMaxError * 2 : 0 ,
177217 ...loadOptions ?. terrain ,
178218 bounds,
179- meshMaxError,
219+ meshMaxError : effectiveMeshMaxError ,
180220 elevationDecoder
181221 }
182222 } ;
@@ -203,10 +243,15 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
203243 topRight = [ bbox . right , bbox . top ] ;
204244 }
205245 const bounds : Bounds = [ bottomLeft [ 0 ] , bottomLeft [ 1 ] , topRight [ 0 ] , topRight [ 1 ] ] ;
246+ const overlappedBounds = getOverlappedBounds (
247+ bounds ,
248+ this . props . tileSize ,
249+ Boolean ( viewport . resolution && viewport . resolution > 0 )
250+ ) ;
206251
207252 const terrain = this . loadTerrain ( {
208253 elevationData : dataUrl ,
209- bounds,
254+ bounds : overlappedBounds ,
210255 elevationDecoder,
211256 meshMaxError,
212257 signal
@@ -237,12 +282,27 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
237282
238283 const [ mesh , texture ] = data ;
239284
285+ const { viewport} = this . context ;
286+ // Bounds are baked with projectFlat. In GlobeView projectFlat is identity,
287+ // so tiled terrain meshes are in lng/lat degrees instead of common-space
288+ // web-mercator units.
289+ const isGlobe = Boolean ( viewport . resolution && viewport . resolution > 0 ) ;
290+ const boundingBox = ( mesh as MeshWithBoundingBox | null ) ?. header ?. boundingBox ;
291+ const hasLngLatBounds =
292+ boundingBox &&
293+ boundingBox . every (
294+ ( [ x , y ] ) =>
295+ x >= - MAX_LONGITUDE && x <= MAX_LONGITUDE && y >= - MAX_LATITUDE && y <= MAX_LATITUDE
296+ ) ;
297+ const coordinateSystem =
298+ isGlobe && hasLngLatBounds ? COORDINATE_SYSTEM . LNGLAT : COORDINATE_SYSTEM . CARTESIAN ;
299+
240300 return new SubLayerClass ( props , {
241301 data : DUMMY_DATA ,
242302 mesh,
243303 texture,
244304 _instanced : false ,
245- coordinateSystem : COORDINATE_SYSTEM . CARTESIAN ,
305+ coordinateSystem,
246306 getPosition : d => [ 0 , 0 , 0 ] ,
247307 getColor : color ,
248308 wireframe,
@@ -311,7 +371,8 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
311371 elevationData : urlTemplateToUpdateTrigger ( elevationData ) ,
312372 texture : urlTemplateToUpdateTrigger ( texture ) ,
313373 meshMaxError,
314- elevationDecoder
374+ elevationDecoder,
375+ projectionMode : this . context . viewport . projectionMode
315376 }
316377 } ,
317378 onViewportLoad : this . onViewportLoad . bind ( this ) ,
0 commit comments