Skip to content

Commit f77e412

Browse files
author
Mr Martian
committed
add developement guide
1 parent 2918a05 commit f77e412

File tree

13 files changed

+241
-160
lines changed

13 files changed

+241
-160
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ S2 Maps GPU is currently in **alpha**. We are actively working on it and would l
5555

5656
Although it's still a young product, we are using [playwright](https://playwright.dev/) to ensure features that do exist work as intended across all major browsers. Feel free to [open an issue](https://github.com/Open-S2/s2maps-gpu/issues/new) if you find a bug or have a feature request.
5757

58+
59+
## Development
60+
61+
This project is developed using [bun](https://bun.sh/).
62+
63+
### Usage
64+
65+
To test locally, run `bun i` and then `bun dev`
66+
67+
You can test using the following URL system:
68+
69+
```bash
70+
http://localhost:3000/{react|vue|svelte}.html?projection={s2|wm}&context={webgl|webgl2|webgpu}&style={background|fill|etc.}
71+
```
72+
73+
For example:
74+
75+
```bash
76+
http://localhost:3000/vue.html?projection=s2&context=webgpu&style=fill
77+
```
78+
79+
List of styles you can try are found in `/styles/examples/{s2|wm}/*`. The name of the folder is the name of the style.
80+
5881
---
5982

6083
<div align="center">

config/glyphs/glyphs-v1.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import Database from 'better-sqlite3';
2+
import { parse } from 'url';
3+
4+
import type { Plugin } from 'vite';
5+
6+
/**
7+
* # Glyph API Vite Plugin
8+
*
9+
* Exposes a lightweight SQLite glyph API at `/api/glyphs/:fontName`
10+
* @returns Vite plugin
11+
*/
12+
export function glyphApi(): Plugin {
13+
return {
14+
name: 'glyph-api',
15+
/**
16+
* Modify the server middleware
17+
* @param server - Vite server
18+
*/
19+
configureServer(server): void {
20+
server.middlewares.use((req, res, next) => {
21+
if (req.url === undefined) {
22+
next();
23+
return;
24+
}
25+
26+
const { pathname, query } = parse(req.url, true);
27+
28+
if (pathname === null || !pathname.startsWith('/api/glyphs/')) {
29+
next();
30+
return;
31+
}
32+
33+
const fontName = pathname.replace('/api/glyphs/', '').split('.')[0];
34+
const { type, codes } = query as { type?: string; codes?: string };
35+
36+
if (fontName.length === 0 || (type !== 'metadata' && type !== 'glyph')) {
37+
res.statusCode = 400;
38+
res.end(JSON.stringify({ err: 'Invalid parameters' }));
39+
return;
40+
}
41+
42+
const db = new Database('./public/glyphs/GLYPHS.sqlite3', { readonly: true });
43+
44+
try {
45+
if (type === 'metadata') {
46+
const row = db.prepare('SELECT data FROM metadata WHERE name = ?').get(fontName) as
47+
| { data: string }
48+
| undefined;
49+
if (row === undefined) {
50+
res.statusCode = 404;
51+
res.end(JSON.stringify({ err: 'Metadata not found' }));
52+
return;
53+
}
54+
res.setHeader('Content-Type', 'application/x-protobuf');
55+
res.end(Buffer.from(row.data, 'base64'));
56+
return;
57+
}
58+
59+
if (type === 'glyph') {
60+
const pieces = codes?.split(',') ?? [];
61+
const stmt = db.prepare('SELECT data FROM glyph_multi WHERE name = ? AND code = ?');
62+
const buffers: Buffer[] = [];
63+
64+
/**
65+
* Convert a base36 string to a number
66+
* @param num - base36 string
67+
* @returns number
68+
*/
69+
const base36 = (num: string) => parseInt(num, 36);
70+
71+
for (const piece of pieces) {
72+
if (piece.includes('-')) {
73+
const [from, to] = piece.split('-').map(base36);
74+
for (let i = from; i <= to; i++) {
75+
const row = stmt.get(fontName, String(i)) as { data: string } | undefined;
76+
if (row !== undefined) {
77+
buffers.push(Buffer.from(row.data, 'base64'));
78+
}
79+
}
80+
} else {
81+
const code = piece.includes('.') ? piece : String(base36(piece));
82+
const row = stmt.get(fontName, code) as { data: string } | undefined;
83+
if (row !== undefined) {
84+
buffers.push(Buffer.from(row.data, 'base64'));
85+
}
86+
}
87+
}
88+
89+
res.setHeader('Content-Type', 'application/x-protobuf');
90+
res.end(Buffer.concat(buffers));
91+
return;
92+
}
93+
94+
res.statusCode = 404;
95+
res.end(JSON.stringify({ err: 'Unknown request' }));
96+
} catch (_) {
97+
res.statusCode = 500;
98+
res.end(JSON.stringify({ err: 'Internal error' }));
99+
} finally {
100+
db.close();
101+
}
102+
});
103+
},
104+
};
105+
}

config/glyphs/glyphs-v2.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import Database from 'better-sqlite3';
2+
import { parse } from 'url';
3+
4+
import type { Plugin } from 'vite';
5+
6+
/**
7+
* # Glyph V2 API Vite Plugin
8+
*
9+
* Exposes SQLite V2 glyph API at `/api/glyphs-v2/:fontName`
10+
* @returns Vite plugin
11+
*/
12+
export function glyphApiV2(): Plugin {
13+
return {
14+
name: 'glyph-api-v2',
15+
/**
16+
* Modify the server middleware
17+
* @param server - Vite server
18+
*/
19+
configureServer(server) {
20+
server.middlewares.use((req, res, next) => {
21+
if (req.url === undefined) {
22+
next();
23+
return;
24+
}
25+
26+
const { pathname, query } = parse(req.url, true);
27+
28+
if (pathname === null || !pathname.startsWith('/api/glyphs-v2/')) {
29+
next();
30+
return;
31+
}
32+
33+
const fontName = pathname.replace('/api/glyphs-v2/', '').split('.')[0];
34+
const { type, codes } = query as { type?: string; codes?: string };
35+
36+
if (fontName.length === 0 || (type !== 'metadata' && type !== 'glyph')) {
37+
res.statusCode = 400;
38+
res.end(JSON.stringify({ err: 'Invalid parameters' }));
39+
return;
40+
}
41+
42+
const db = new Database('./public/glyphs/GLYPHS_V2.sqlite', { readonly: true });
43+
44+
try {
45+
if (type === 'metadata') {
46+
const row = db.prepare('SELECT data FROM metadata WHERE name = ?').get(fontName) as
47+
| { data: string }
48+
| undefined;
49+
if (row === undefined) {
50+
res.statusCode = 404;
51+
res.end(JSON.stringify({ err: 'Metadata not found' }));
52+
return;
53+
}
54+
res.setHeader('Content-Type', 'application/x-protobuf');
55+
res.end(Buffer.from(row.data, 'base64'));
56+
return;
57+
}
58+
59+
if (type === 'glyph') {
60+
const pieces = codes?.split(',') ?? [];
61+
const stmt = db.prepare('SELECT data FROM glyph_multi WHERE name = ? AND code = ?');
62+
const buffers: Buffer[] = [];
63+
64+
/**
65+
* Convert a base36 string to a number
66+
* @param num - base36 string
67+
* @returns number
68+
*/
69+
const base36 = (num: string) => parseInt(num, 36);
70+
71+
for (const piece of pieces) {
72+
if (piece.includes('-')) {
73+
const [from, to] = piece.split('-').map(base36);
74+
for (let i = from; i <= to; i++) {
75+
const row = stmt.get(fontName, String(i)) as { data: string } | undefined;
76+
if (row !== undefined) {
77+
buffers.push(Buffer.from(row.data, 'base64'));
78+
}
79+
}
80+
} else {
81+
const code = piece.includes('.') ? piece : String(base36(piece));
82+
const row = stmt.get(fontName, code) as { data: string } | undefined;
83+
if (row !== undefined) {
84+
buffers.push(Buffer.from(row.data, 'base64'));
85+
}
86+
}
87+
}
88+
89+
res.setHeader('Content-Type', 'application/x-protobuf');
90+
res.end(Buffer.concat(buffers));
91+
return;
92+
}
93+
94+
res.statusCode = 404;
95+
res.end(JSON.stringify({ err: 'Unknown request' }));
96+
} catch (_) {
97+
res.statusCode = 500;
98+
res.end(JSON.stringify({ err: 'Internal error' }));
99+
} finally {
100+
db.close();
101+
}
102+
});
103+
},
104+
};
105+
}

playground/components/S2MapGPU.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
urlMap: {
2424
baseURL: 'http://localhost:3000',
2525
dataURL: 'http://localhost:3000',
26+
apiURL: 'http://localhost:3000/api',
2627
},
2728
attributionOff: true,
2829
watermarkOff: true,

playground/components/S2MapGPU.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const S2MapGPU: React.FC<S2MapReactComponentProps> = (props: S2MapReactCo
3737
urlMap: {
3838
baseURL: 'http://localhost:3000',
3939
dataURL: 'http://localhost:3000',
40+
apiURL: 'http://localhost:3000/api',
4041
},
4142
attributionOff: true,
4243
watermarkOff: true,

playground/components/S2MapGPU.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ export default {
4646
urlMap: {
4747
baseURL: 'http://localhost:3000',
4848
dataURL: 'http://localhost:3000',
49-
// TODO: Setup apiURL for pulling glyphs
50-
// apiURL: config.public.dataURL as string,
49+
apiURL: 'http://localhost:3000/api',
5150
},
5251
attributionOff: true,
5352
watermarkOff: true,

server/api/glyphs-v2/[...fontName].ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)