@@ -4,6 +4,7 @@ import { GeoTIFF, GeoTIFFImage, ReadRasterResult, fromUrl } from 'geotiff';
44// @ts -ignore - @mapbox/martini doesn't have TypeScript declarations
55import Martini from '@mapbox/martini' ;
66import { Document , NodeIO } from '@gltf-transform/core' ;
7+ import { encode } from '@cf-wasm/png' ;
78
89// Define environment variable types
910type Bindings = {
@@ -24,6 +25,7 @@ glb.get('/:z/:x/:y.glb', async (c) => {
2425 const y = yWithExt ? yWithExt . replace ( '.glb' , '' ) : '0' ;
2526
2627 let elevation = 'swissalti3d/swissalti3d_web_mercator.tif' ;
28+ let texture = 'swissimage-dop10/swissimage_web_mercator.tif' ;
2729
2830 const levelNum = parseInt ( z ) ;
2931 const xNum = parseInt ( x ) ;
@@ -135,6 +137,58 @@ glb.get('/:z/:x/:y.glb', async (c) => {
135137 return c . json ( { error : 'No valid elevation data in tile' } , 404 ) ;
136138 }
137139
140+ // Load texture GeoTIFF URL
141+ const textureUrl = `${ c . env . R2_PUBLIC_ARPENTRY_ENDPOINT } /${ texture } ` ;
142+
143+ // Load and resample texture GeoTIFF with same parameters as elevation
144+ let texturePngBuffer : Uint8Array | null = null ;
145+ try {
146+ const textureTiff : GeoTIFF = await fromUrl ( textureUrl ) ;
147+
148+ const textureRaster : ReadRasterResult = await textureTiff . readRasters ( {
149+ bbox : tileBbox ,
150+ width : tileSize ,
151+ height : tileSize ,
152+ fillValue : 0
153+ } ) ;
154+
155+ // Convert raster data to PNG using UPNG
156+ if ( textureRaster && Array . isArray ( textureRaster ) ) {
157+ const imageData = new Uint8Array ( tileSize * tileSize * 4 ) ;
158+ const numBands = textureRaster . length ;
159+
160+ for ( let i = 0 ; i < tileSize * tileSize ; i ++ ) {
161+ const pixelIndex = i * 4 ;
162+
163+ if ( numBands >= 3 ) {
164+ // RGB or RGBA
165+ const r = textureRaster [ 0 ] as TypedArray ;
166+ const g = textureRaster [ 1 ] as TypedArray ;
167+ const b = textureRaster [ 2 ] as TypedArray ;
168+
169+ imageData [ pixelIndex ] = Number ( r [ i ] ) || 0 ; // R
170+ imageData [ pixelIndex + 1 ] = Number ( g [ i ] ) || 0 ; // G
171+ imageData [ pixelIndex + 2 ] = Number ( b [ i ] ) || 0 ; // B
172+ imageData [ pixelIndex + 3 ] = 255 ; // A
173+ } else if ( numBands === 1 ) {
174+ // Grayscale
175+ const gray = textureRaster [ 0 ] as TypedArray ;
176+ const value = Number ( gray [ i ] ) || 0 ;
177+
178+ imageData [ pixelIndex ] = value ; // R
179+ imageData [ pixelIndex + 1 ] = value ; // G
180+ imageData [ pixelIndex + 2 ] = value ; // B
181+ imageData [ pixelIndex + 3 ] = 255 ; // A
182+ }
183+ }
184+
185+ // Create PNG buffer using @cf -wasm/png
186+ texturePngBuffer = encode ( imageData , tileSize , tileSize ) ;
187+ }
188+ } catch ( error ) {
189+ console . warn ( 'Failed to load texture GeoTIFF:' , error ) ;
190+ }
191+
138192 const document = new Document ( ) ;
139193 const buffer = document . createBuffer ( ) ;
140194
@@ -145,6 +199,7 @@ glb.get('/:z/:x/:y.glb', async (c) => {
145199 const scaleY = tileHeight / tileSize ;
146200
147201 const positionArray = [ ] ;
202+ const uvArray = [ ] ;
148203 for ( let i = 0 ; i < validVertices . length ; i += 2 ) {
149204 const x = validVertices [ i ] ;
150205 const y = validVertices [ i + 1 ] ;
@@ -156,6 +211,9 @@ glb.get('/:z/:x/:y.glb', async (c) => {
156211 const worldY = minY + y * scaleY ;
157212
158213 positionArray . push ( worldX , z , worldY ) ;
214+
215+ // Create UV coordinates (normalized to 0-1 range)
216+ uvArray . push ( x / tileSize , y / tileSize ) ;
159217 }
160218
161219 const positionBuffer = new Float32Array ( positionArray ) ;
@@ -164,6 +222,12 @@ glb.get('/:z/:x/:y.glb', async (c) => {
164222 . setArray ( positionBuffer )
165223 . setBuffer ( buffer ) ;
166224
225+ const uvBuffer = new Float32Array ( uvArray ) ;
226+ const uvAccessor = document . createAccessor ( )
227+ . setType ( 'VEC2' )
228+ . setArray ( uvBuffer )
229+ . setBuffer ( buffer ) ;
230+
167231 const indexBuffer = new Uint16Array ( validTriangles ) ;
168232 const indexAccessor = document . createAccessor ( )
169233 . setType ( 'SCALAR' )
@@ -172,8 +236,23 @@ glb.get('/:z/:x/:y.glb', async (c) => {
172236
173237 const primitive = document . createPrimitive ( )
174238 . setAttribute ( 'POSITION' , positionAccessor )
239+ . setAttribute ( 'TEXCOORD_0' , uvAccessor )
175240 . setIndices ( indexAccessor ) ;
176241
242+ // Create material with resampled texture if available
243+ if ( texturePngBuffer ) {
244+ const baseColorTexture = document . createTexture ( "BaseColorTexture" )
245+ . setImage ( texturePngBuffer )
246+ . setMimeType ( 'image/png' ) ;
247+
248+ const material = document . createMaterial ( )
249+ . setBaseColorTexture ( baseColorTexture )
250+ . setBaseColorFactor ( [ 1.0 , 1.0 , 1.0 , 1.0 ] )
251+ . setDoubleSided ( true ) ;
252+
253+ primitive . setMaterial ( material ) ;
254+ }
255+
177256 const mesh = document . createMesh ( 'terrainMesh' )
178257 . addPrimitive ( primitive ) ;
179258
0 commit comments