Skip to content

Commit baf46f2

Browse files
committed
added caching and state saving for tileset data
1 parent 5b83b3f commit baf46f2

File tree

6 files changed

+124
-26
lines changed

6 files changed

+124
-26
lines changed

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@
2727
"@cloudflare/workers-types": "^4.20250807.0",
2828
"prettier": "^3.6.2",
2929
"vitest": "^3.2.4",
30-
"wrangler": "^4.28.0"
30+
"wrangler": "^4.28.1"
3131
}
3232
}

apps/api/src/index.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@ import { cors } from 'hono/cors';
44
import tms from './routes/tms';
55
import glb from './routes/glb';
66

7-
// Define environment variable types
8-
type Bindings = {
9-
R2_PUBLIC_ARPENTRY_ENDPOINT: string;
10-
};
11-
127
/**
138
* Server
149
*/
15-
const app = new Hono<{ Bindings: Bindings }>();
10+
const app = new Hono();
1611

1712
app.use(
1813
'*',
@@ -21,6 +16,19 @@ app.use(
2116
}),
2217
);
2318

19+
// const getR2ObjectETag = async (key: string): Promise<string> => {
20+
// const response = await fetch(`${c.env.R2_PUBLIC_ARPENTRY_ENDPOINT}/${key}`, {
21+
// method: 'HEAD', // Only get headers, not content
22+
// });
23+
24+
// return response.headers.get('ETag') || '';
25+
// };
26+
27+
// const invalidateCache = async (c: Context) => {
28+
// await c.env.KV_ARPENTRY.delete('global_bounds');
29+
// await c.env.KV_ARPENTRY.delete('tileset_center');
30+
// };
31+
2432
// Mount TMS routes
2533
app.route('/tms', tms);
2634
app.route('/', glb);

apps/api/src/routes/glb.ts

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,35 @@ import {
1414
} from '../services/mesh';
1515
import { buildGltfDocument } from '../services/gltf';
1616
import { calculateTileBounds, createRootTile } from '../services/tiles';
17+
import { getR2ObjectETag } from '../services/r2';
1718

1819
type Bindings = {
19-
R2_ACCOUNT_ID: string;
20-
R2_ACCESS_KEY_ID: string;
21-
R2_SECRET_ACCESS_KEY: string;
22-
R2_BUCKET_NAME: string;
23-
R2_ENDPOINT: string;
2420
R2_PUBLIC_ARPENTRY_ENDPOINT: string;
21+
KV_ARPENTRY: KVNamespace;
2522
};
2623

2724
const glb = new Hono<{ Bindings: Bindings }>();
2825

26+
type Bounds = [number, number, number, number];
27+
type Coordinate = [number, number];
28+
2929
// Configuration
3030
const TILE_SIZE = 512;
3131
const QUADTREE_MAX_LEVEL = 5;
3232

3333
// Global coordinate system reference
34-
let GLOBAL_BOUNDS: [number, number, number, number] | null = null;
35-
let TILESET_CENTER: [number, number] | null = null;
34+
let GLOBAL_BOUNDS: Bounds | null = null;
35+
let TILESET_CENTER: Coordinate | null = null;
3636

3737
/**
3838
* Tileset JSON endpoint - provides 3D Tiles structure
3939
*/
4040
glb.get(
4141
'/tileset.json',
42-
cache({
43-
cacheName: 'tileset',
44-
cacheControl: 'max-age=3600',
45-
}),
42+
// cache({
43+
// cacheName: 'tileset',
44+
// cacheControl: 'max-age=3600',
45+
// }),
4646
async (c) => {
4747
console.log('🌍 Tileset JSON endpoint');
4848
try {
@@ -56,9 +56,7 @@ glb.get(
5656
console.log('🌍 Bounding box:', bbox);
5757

5858
console.log('🌍 Creating square bounds...');
59-
const square = createSquareBounds(
60-
bbox as [number, number, number, number],
61-
);
59+
const square = createSquareBounds(bbox as Bounds);
6260
console.log('🌍 Square bounds created');
6361
console.log('🌍 Square bounds:', square);
6462

@@ -69,6 +67,18 @@ glb.get(
6967
(square[1] + square[3]) / 2,
7068
];
7169

70+
const globalBounds = await c.env.KV_ARPENTRY.put(
71+
'global_bounds',
72+
JSON.stringify(square),
73+
);
74+
const tilesetCenter = await c.env.KV_ARPENTRY.put(
75+
'tileset_center',
76+
JSON.stringify(TILESET_CENTER),
77+
);
78+
79+
console.log('🌍 Global bounds stored in KV', globalBounds);
80+
console.log('🌍 Tileset center stored in KV', tilesetCenter);
81+
7282
const minH = 0;
7383
const maxH = 4500; // Swiss terrain height range
7484

@@ -121,9 +131,31 @@ glb.get(
121131
return c.json({ error: 'Invalid tile coordinates' }, 400);
122132
}
123133

134+
// Fetch and initialize global bounds and tileset center from KV if not initialized yet
124135
if (!GLOBAL_BOUNDS || !TILESET_CENTER) {
125-
console.error('❌ Global bounds not initialized');
126-
return c.json({ error: 'Global bounds not available' }, 500);
136+
console.error(
137+
'❌ Global bounds and/or tileset center not initialized, fetching from KV...',
138+
);
139+
try {
140+
const globalBounds = (await c.env.KV_ARPENTRY.get(
141+
'global_bounds',
142+
)) as string;
143+
const tilesetCenter = (await c.env.KV_ARPENTRY.get(
144+
'tileset_center',
145+
)) as string;
146+
147+
GLOBAL_BOUNDS = JSON.parse(globalBounds) as Bounds;
148+
TILESET_CENTER = JSON.parse(tilesetCenter) as Coordinate;
149+
150+
console.log(
151+
'🌍 KV fetched, global bounds and tileset center initialized',
152+
);
153+
} catch (err) {
154+
const errorMessage =
155+
'Failed to fetch from KV, global bounds and tileset center cannot be initialized';
156+
console.error('❌ ', errorMessage, err);
157+
return c.json({ error: errorMessage }, 500);
158+
}
127159
}
128160

129161
const elevKey = 'swissalti3d/swissalti3d_web_mercator.tif';

apps/api/src/services/r2.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Context } from 'hono';
2+
3+
const getR2ObjectETag = async (c: Context, key: string): Promise<string> => {
4+
const response = await fetch(`${c.env.R2_PUBLIC_ARPENTRY_ENDPOINT}/${key}`, {
5+
method: 'HEAD', // Header only
6+
});
7+
8+
return response.headers.get('ETag') || '';
9+
};
10+
11+
// TODO : Call this function when starting the server ?
12+
const invalidateCache = async (c: Context, r2_keys: string[]) => {
13+
for (const r2_key of r2_keys) {
14+
const r2_ETag = await getR2ObjectETag(c, r2_key);
15+
const kv_ETag = await c.env.KV_ARPENTRY.get(r2_key);
16+
if (kv_ETag !== r2_ETag) {
17+
console.log(`Invalidating cache for ${r2_key}`);
18+
// TODO : Purge cache
19+
await c.env.KV_ARPENTRY.put(r2_key, r2_ETag);
20+
}
21+
}
22+
};
23+
24+
export { getR2ObjectETag, invalidateCache };

apps/api/wrangler.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,12 @@
1616
},
1717
"limits": {
1818
"cpu_ms": 600000
19-
}
19+
},
20+
"kv_namespaces": [
21+
{
22+
"binding": "KV_ARPENTRY",
23+
"id": "60a82765032f436890cac83cd0777665",
24+
"preview_id": "f44e6cf002d34c68996d9d65e5ecbeaa"
25+
}
26+
]
2027
}

pnpm-lock.yaml

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)