Skip to content

Commit 936e3b8

Browse files
authored
Merge pull request #2 from mug-jp/offscreen
use webworker to convert png
2 parents 411c5aa + f6f12bf commit 936e3b8

7 files changed

+217
-157
lines changed

package-lock.json

+108-62
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "maplibre-gl-gsi-terrain",
3-
"version": "1.0.0",
3+
"version": "1.1.0-pre.0",
44
"type": "module",
55
"main": "./dist/terrain.js",
66
"types": "./dist/terrain.d.ts",
@@ -15,11 +15,11 @@
1515
"prepare": "npm run build"
1616
},
1717
"devDependencies": {
18-
"typescript": "^4.6.4",
18+
"maplibre-gl": "^4.2.0",
19+
"typescript": "^5.4.5",
1920
"vite": "^3.2.3"
2021
},
2122
"dependencies": {
22-
"fast-png": "^6.1.0",
23-
"maplibre-gl": "^4.1.2"
23+
"workerpool": "^9.1.1"
2424
}
2525
}

src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import maplibreGl, { Map } from 'maplibre-gl';
22
import 'maplibre-gl/dist/maplibre-gl.css';
33

4-
import { useGsiTerrainSource } from './terrain';
4+
import { useGsiTerrainSource } from '../dist/terrain';
55

66
const gsiTerrainSource = useGsiTerrainSource(maplibreGl.addProtocol, {
77
tileUrl: 'https://tiles.gsj.jp/tiles/elev/mixed/{z}/{y}/{x}.png',

src/terrain.ts

+5-70
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,7 @@
11
import type { RasterDEMSourceSpecification } from 'maplibre-gl';
22
import maplibregl from 'maplibre-gl';
3-
import { encode } from 'fast-png';
4-
5-
function gsidem2terrainrgb(
6-
r: number,
7-
g: number,
8-
b: number,
9-
): [number, number, number] {
10-
// https://qiita.com/frogcat/items/d12bed4e930b83eb3544
11-
let rgb = (r << 16) + (g << 8) + b;
12-
let h = 0;
13-
14-
if (rgb < 0x800000) h = rgb * 0.01;
15-
else if (rgb > 0x800000) h = (rgb - Math.pow(2, 24)) * 0.01;
16-
17-
rgb = Math.floor((h + 10000) / 0.1);
18-
const tR = (rgb & 0xff0000) >> 16;
19-
const tG = (rgb & 0x00ff00) >> 8;
20-
const tB = rgb & 0x0000ff;
21-
return [tR, tG, tB];
22-
}
3+
import WorkerPool from 'workerpool';
4+
const pool = WorkerPool.pool(new URL('worker.js', import.meta.url).href);
235

246
type Options = {
257
attribution?: string;
@@ -28,51 +10,6 @@ type Options = {
2810
tileUrl?: string;
2911
};
3012

31-
function loadPng(url: string): Promise<Uint8Array> {
32-
return new Promise((resolve, reject) => {
33-
const image = new Image();
34-
image.crossOrigin = '';
35-
image.onload = () => {
36-
const canvas = document.createElement('canvas');
37-
canvas.width = image.width;
38-
canvas.height = image.height;
39-
40-
const context = canvas.getContext('2d', {
41-
willReadFrequently: true,
42-
})!;
43-
44-
// 地理院標高タイルを採用している一部のタイルは無効値が透過されていることがある
45-
// 透過されている場合に無効値にフォールバックさせる=rgb(128,0,0)で塗りつぶす
46-
context.fillStyle = 'rgb(128,0,0)';
47-
context.fillRect(0, 0, canvas.width, canvas.height);
48-
49-
context.drawImage(image, 0, 0);
50-
const imageData = context.getImageData(
51-
0,
52-
0,
53-
canvas.width,
54-
canvas.height,
55-
);
56-
for (let i = 0; i < imageData.data.length / 4; i++) {
57-
const tRGB = gsidem2terrainrgb(
58-
imageData.data[i * 4],
59-
imageData.data[i * 4 + 1],
60-
imageData.data[i * 4 + 2],
61-
);
62-
imageData.data[i * 4] = tRGB[0];
63-
imageData.data[i * 4 + 1] = tRGB[1];
64-
imageData.data[i * 4 + 2] = tRGB[2];
65-
}
66-
const png = encode(imageData);
67-
resolve(png);
68-
};
69-
image.onerror = (e) => {
70-
reject(e);
71-
};
72-
image.src = url;
73-
});
74-
}
75-
7613
/**
7714
* 地理院標高タイルを利用したtype=raster-demのsourceを返す
7815
* @param addProtocol
@@ -105,12 +42,10 @@ export const useGsiTerrainSource = (
10542
addProtocol: typeof maplibregl.addProtocol,
10643
options: Options = {},
10744
): RasterDEMSourceSpecification => {
108-
addProtocol('gsidem', async (params, abortController) => {
45+
addProtocol('gsidem', async (params, _) => {
10946
const imageUrl = params.url.replace('gsidem://', '');
110-
const png = await loadPng(imageUrl).catch((e) => {
111-
abortController.abort();
112-
throw e.message;
113-
});
47+
const p = await pool.proxy();
48+
const png = await p.loadPng(imageUrl);
11449
return { data: png };
11550
});
11651
const tileUrl =

src/worker.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// @ts-ignore
2+
importScripts('workerpool.js');
3+
// @ts-ignore
4+
const pool = workerpool.worker({
5+
loadPng,
6+
});
7+
8+
function gsidem2terrainrgb(
9+
r: number,
10+
g: number,
11+
b: number,
12+
): [number, number, number] {
13+
// https://qiita.com/frogcat/items/d12bed4e930b83eb3544
14+
let rgb = (r << 16) + (g << 8) + b;
15+
let h = 0;
16+
17+
if (rgb < 0x800000) h = rgb * 0.01;
18+
else if (rgb > 0x800000) h = (rgb - Math.pow(2, 24)) * 0.01;
19+
20+
rgb = Math.floor((h + 10000) / 0.1);
21+
const tR = (rgb & 0xff0000) >> 16;
22+
const tG = (rgb & 0x00ff00) >> 8;
23+
const tB = rgb & 0x0000ff;
24+
return [tR, tG, tB];
25+
}
26+
27+
async function loadPng(url: string): Promise<Uint8Array | null> {
28+
let res: Response;
29+
try {
30+
res = await fetch(url);
31+
} catch (e) {
32+
return null;
33+
}
34+
35+
if (!res.ok) {
36+
return null;
37+
}
38+
39+
const blob = await res.blob();
40+
const bitmap = await createImageBitmap(blob);
41+
42+
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
43+
const context = canvas.getContext('2d', {
44+
willReadFrequently: true,
45+
})!;
46+
47+
// 地理院標高タイルを採用している一部のタイルは無効値が透過されていることがある
48+
// 透過されている場合に無効値にフォールバックさせる=rgb(128,0,0)で塗りつぶす
49+
context.fillStyle = 'rgb(128,0,0)';
50+
context.fillRect(0, 0, canvas.width, canvas.height);
51+
52+
context.drawImage(bitmap, 0, 0);
53+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
54+
for (let i = 0; i < imageData.data.length / 4; i++) {
55+
const tRGB = gsidem2terrainrgb(
56+
imageData.data[i * 4],
57+
imageData.data[i * 4 + 1],
58+
imageData.data[i * 4 + 2],
59+
);
60+
imageData.data[i * 4] = tRGB[0];
61+
imageData.data[i * 4 + 1] = tRGB[1];
62+
imageData.data[i * 4 + 2] = tRGB[2];
63+
}
64+
65+
const canvas2 = new OffscreenCanvas(imageData.width, imageData.height);
66+
const context2 = canvas2.getContext('2d', {
67+
willReadFrequently: true,
68+
})!;
69+
context2.putImageData(imageData, 0, 0);
70+
71+
// blob to typedarray
72+
const b2 = await canvas2.convertToBlob();
73+
const ab = await b2!.arrayBuffer();
74+
return new Uint8Array(ab);
75+
}

src/workerpool.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tsconfig.json

+21-20
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
{
2-
"compilerOptions": {
3-
"target": "ESNext",
4-
"useDefineForClassFields": true,
5-
"module": "ESNext",
6-
"lib": ["ESNext", "DOM"],
7-
"moduleResolution": "Node",
8-
"strict": true,
9-
"resolveJsonModule": true,
10-
"isolatedModules": true,
11-
"esModuleInterop": true,
12-
"declaration": true,
13-
"declarationMap": true,
14-
"declarationDir": "dist",
15-
"noUnusedLocals": true,
16-
"noUnusedParameters": true,
17-
"noImplicitReturns": true,
18-
"skipLibCheck": true,
19-
"outDir": "dist"
20-
},
21-
"include": ["src/terrain.ts"]
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"useDefineForClassFields": true,
5+
"module": "ESNext",
6+
"lib": ["ESNext", "DOM"],
7+
"moduleResolution": "Node",
8+
"strict": true,
9+
"resolveJsonModule": true,
10+
"isolatedModules": true,
11+
"esModuleInterop": true,
12+
"declaration": true,
13+
"declarationMap": true,
14+
"declarationDir": "dist",
15+
"noUnusedLocals": true,
16+
"noUnusedParameters": true,
17+
"noImplicitReturns": true,
18+
"skipLibCheck": true,
19+
"outDir": "dist",
20+
"allowJs": true
21+
},
22+
"include": ["src/terrain.ts", "src/worker.ts", "src/workerpool.js"]
2223
}

0 commit comments

Comments
 (0)