@caoguo/maplibre 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DNEF4DHG.js +48 -0
- package/dist/chunk-DNEF4DHG.js.map +1 -0
- package/dist/chunk-EZOV5O2M.js +96 -0
- package/dist/chunk-EZOV5O2M.js.map +1 -0
- package/dist/chunk-MNUQZUQA.js +372 -0
- package/dist/chunk-MNUQZUQA.js.map +1 -0
- package/dist/index.cjs +1154 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +459 -0
- package/dist/index.d.ts +459 -0
- package/dist/index.js +587 -0
- package/dist/index.js.map +1 -0
- package/dist/offline/index.cjs +397 -0
- package/dist/offline/index.cjs.map +1 -0
- package/dist/offline/index.d.cts +300 -0
- package/dist/offline/index.d.ts +300 -0
- package/dist/offline/index.js +3 -0
- package/dist/offline/index.js.map +1 -0
- package/dist/sources/index.cjs +102 -0
- package/dist/sources/index.cjs.map +1 -0
- package/dist/sources/index.d.cts +83 -0
- package/dist/sources/index.d.ts +83 -0
- package/dist/sources/index.js +3 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/styles.cjs +53 -0
- package/dist/styles.cjs.map +1 -0
- package/dist/styles.d.cts +19 -0
- package/dist/styles.d.ts +19 -0
- package/dist/styles.js +3 -0
- package/dist/styles.js.map +1 -0
- package/package.json +52 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var maplibregl = require('maplibre-gl');
|
|
6
|
+
require('maplibre-gl/dist/maplibre-gl.css');
|
|
7
|
+
var theme = require('@caoguo/theme');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var maplibregl__default = /*#__PURE__*/_interopDefault(maplibregl);
|
|
12
|
+
|
|
13
|
+
// src/index.ts
|
|
14
|
+
var WUHAN_CENTER = [114.3055, 30.5928];
|
|
15
|
+
var WUHAN_ZOOM = 11;
|
|
16
|
+
function caoguoStyle(theme$1 = "caoguo-dark") {
|
|
17
|
+
return theme.buildStyle(theme$1);
|
|
18
|
+
}
|
|
19
|
+
function osmRasterStyle() {
|
|
20
|
+
return {
|
|
21
|
+
version: 8,
|
|
22
|
+
glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
|
|
23
|
+
sources: {
|
|
24
|
+
"osm-raster": {
|
|
25
|
+
type: "raster",
|
|
26
|
+
tiles: [
|
|
27
|
+
"https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
28
|
+
"https://b.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
29
|
+
"https://c.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
30
|
+
],
|
|
31
|
+
tileSize: 256,
|
|
32
|
+
attribution: "\xA9 OpenStreetMap contributors"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
layers: [
|
|
36
|
+
{
|
|
37
|
+
id: "background",
|
|
38
|
+
type: "background",
|
|
39
|
+
paint: { "background-color": "#0a0f1e" }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "osm-raster",
|
|
43
|
+
type: "raster",
|
|
44
|
+
source: "osm-raster",
|
|
45
|
+
paint: {
|
|
46
|
+
"raster-opacity": 0.85,
|
|
47
|
+
"raster-saturation": -0.6,
|
|
48
|
+
"raster-brightness-max": 0.7,
|
|
49
|
+
"raster-contrast": 0.1
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/crs/gcj02.ts
|
|
57
|
+
var PI = Math.PI;
|
|
58
|
+
var A = 6378245;
|
|
59
|
+
var EE = 0.006693421622965943;
|
|
60
|
+
function outOfChina(lng, lat) {
|
|
61
|
+
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
|
62
|
+
}
|
|
63
|
+
function transformLat(lng, lat) {
|
|
64
|
+
let ret = -100 + 2 * lng + 3 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
|
|
65
|
+
ret += (20 * Math.sin(6 * lng * PI) + 20 * Math.sin(2 * lng * PI)) * 2 / 3;
|
|
66
|
+
ret += (20 * Math.sin(lat * PI) + 40 * Math.sin(lat / 3 * PI)) * 2 / 3;
|
|
67
|
+
ret += (160 * Math.sin(lat / 12 * PI) + 320 * Math.sin(lat * PI / 30)) * 2 / 3;
|
|
68
|
+
return ret;
|
|
69
|
+
}
|
|
70
|
+
function transformLng(lng, lat) {
|
|
71
|
+
let ret = 300 + lng + 2 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
|
|
72
|
+
ret += (20 * Math.sin(6 * lng * PI) + 20 * Math.sin(2 * lng * PI)) * 2 / 3;
|
|
73
|
+
ret += (20 * Math.sin(lng * PI) + 40 * Math.sin(lng / 3 * PI)) * 2 / 3;
|
|
74
|
+
ret += (150 * Math.sin(lng / 12 * PI) + 300 * Math.sin(lng / 30 * PI)) * 2 / 3;
|
|
75
|
+
return ret;
|
|
76
|
+
}
|
|
77
|
+
function wgs84ToGcj02(lng, lat) {
|
|
78
|
+
if (outOfChina(lng, lat)) return [lng, lat];
|
|
79
|
+
let dLat = transformLat(lng - 105, lat - 35);
|
|
80
|
+
let dLng = transformLng(lng - 105, lat - 35);
|
|
81
|
+
const radLat = lat / 180 * PI;
|
|
82
|
+
let magic = Math.sin(radLat);
|
|
83
|
+
magic = 1 - EE * magic * magic;
|
|
84
|
+
const sqrtMagic = Math.sqrt(magic);
|
|
85
|
+
dLat = dLat * 180 / (A * (1 - EE) / (magic * sqrtMagic) * PI);
|
|
86
|
+
dLng = dLng * 180 / (A / sqrtMagic * Math.cos(radLat) * PI);
|
|
87
|
+
return [lng + dLng, lat + dLat];
|
|
88
|
+
}
|
|
89
|
+
function gcj02ToWgs84(lng, lat) {
|
|
90
|
+
if (outOfChina(lng, lat)) return [lng, lat];
|
|
91
|
+
let [mlng, mlat] = wgs84ToGcj02(lng, lat);
|
|
92
|
+
let dlng = mlng - lng;
|
|
93
|
+
let dlat = mlat - lat;
|
|
94
|
+
[mlng, mlat] = wgs84ToGcj02(lng - dlng, lat - dlat);
|
|
95
|
+
dlng = mlng - lng;
|
|
96
|
+
dlat = mlat - lat;
|
|
97
|
+
[mlng, mlat] = wgs84ToGcj02(lng - dlng, lat - dlat);
|
|
98
|
+
dlng = mlng - lng;
|
|
99
|
+
dlat = mlat - lat;
|
|
100
|
+
return [lng - dlng, lat - dlat];
|
|
101
|
+
}
|
|
102
|
+
function isInChina(lng, lat) {
|
|
103
|
+
return !outOfChina(lng, lat);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/crs/cgcs2000.ts
|
|
107
|
+
var gridShift = null;
|
|
108
|
+
function setCgcs2000GridShift(provider) {
|
|
109
|
+
gridShift = provider;
|
|
110
|
+
}
|
|
111
|
+
function wgs84ToCgcs2000(lng, lat) {
|
|
112
|
+
if (!gridShift) return [lng, lat];
|
|
113
|
+
const [dLng, dLat] = gridShift.shift(lng, lat);
|
|
114
|
+
return [lng + dLng, lat + dLat];
|
|
115
|
+
}
|
|
116
|
+
function cgcs2000ToWgs84(lng, lat) {
|
|
117
|
+
if (!gridShift) return [lng, lat];
|
|
118
|
+
const [dLng, dLat] = gridShift.shift(lng, lat);
|
|
119
|
+
return [lng - dLng, lat - dLat];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/crs/transform.ts
|
|
123
|
+
function toWgs84(crs, lng, lat) {
|
|
124
|
+
switch (crs) {
|
|
125
|
+
case "WGS84":
|
|
126
|
+
return [lng, lat];
|
|
127
|
+
case "GCJ02":
|
|
128
|
+
return gcj02ToWgs84(lng, lat);
|
|
129
|
+
case "CGCS2000":
|
|
130
|
+
return cgcs2000ToWgs84(lng, lat);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function fromWgs84(crs, lng, lat) {
|
|
134
|
+
switch (crs) {
|
|
135
|
+
case "WGS84":
|
|
136
|
+
return [lng, lat];
|
|
137
|
+
case "GCJ02":
|
|
138
|
+
return wgs84ToGcj02(lng, lat);
|
|
139
|
+
case "CGCS2000":
|
|
140
|
+
return wgs84ToCgcs2000(lng, lat);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function createTransformer(from, to) {
|
|
144
|
+
if (from === to) {
|
|
145
|
+
const identity = (lng, lat) => [lng, lat];
|
|
146
|
+
return { forward: identity, inverse: identity };
|
|
147
|
+
}
|
|
148
|
+
const forward = (lng, lat) => {
|
|
149
|
+
const [wLng, wLat] = toWgs84(from, lng, lat);
|
|
150
|
+
return fromWgs84(to, wLng, wLat);
|
|
151
|
+
};
|
|
152
|
+
const inverse = (lng, lat) => {
|
|
153
|
+
const [wLng, wLat] = toWgs84(to, lng, lat);
|
|
154
|
+
return fromWgs84(from, wLng, wLat);
|
|
155
|
+
};
|
|
156
|
+
return { forward, inverse };
|
|
157
|
+
}
|
|
158
|
+
function transformPoint(lng, lat, from, to) {
|
|
159
|
+
return createTransformer(from, to).forward(lng, lat);
|
|
160
|
+
}
|
|
161
|
+
function transformBounds(b, from, to) {
|
|
162
|
+
const t = createTransformer(from, to);
|
|
163
|
+
const xs = [];
|
|
164
|
+
const ys = [];
|
|
165
|
+
for (const [x, y] of [
|
|
166
|
+
[b[0], b[1]],
|
|
167
|
+
[b[2], b[1]],
|
|
168
|
+
[b[0], b[3]],
|
|
169
|
+
[b[2], b[3]]
|
|
170
|
+
]) {
|
|
171
|
+
const [nx, ny] = t.forward(x, y);
|
|
172
|
+
xs.push(nx);
|
|
173
|
+
ys.push(ny);
|
|
174
|
+
}
|
|
175
|
+
return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/sources/tianditu.ts
|
|
179
|
+
var LAYER_MAP = {
|
|
180
|
+
vector: { base: "vec", label: "cva" },
|
|
181
|
+
satellite: { base: "img", label: "cia" },
|
|
182
|
+
terrain: { base: "ter", label: "cta" }
|
|
183
|
+
};
|
|
184
|
+
var LAYER_LABEL_EN = {
|
|
185
|
+
cva: "eva",
|
|
186
|
+
cia: "eia",
|
|
187
|
+
cta: "eta"
|
|
188
|
+
};
|
|
189
|
+
function tiandituTileUrls(layer, opts) {
|
|
190
|
+
const sub = opts.subdomains ?? [0, 1, 2, 3, 4, 5, 6, 7];
|
|
191
|
+
const labelLayer = opts.lang === "en" ? LAYER_LABEL_EN[layer] ?? layer : layer;
|
|
192
|
+
return sub.map(
|
|
193
|
+
(s) => `https://t${s}.tianditu.gov.cn/${labelLayer}_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${labelLayer}&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${encodeURIComponent(opts.token)}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function buildTiandituSources(type, opts) {
|
|
197
|
+
const { base, label } = LAYER_MAP[type];
|
|
198
|
+
const tileSize = opts.tileSize ?? 256;
|
|
199
|
+
const maxzoom = opts.maxzoom ?? 18;
|
|
200
|
+
const attr = "\xA9 \u5929\u5730\u56FE GS(2023)3295\u53F7";
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
id: `tianditu-${base}`,
|
|
204
|
+
isLabel: false,
|
|
205
|
+
source: {
|
|
206
|
+
type: "raster",
|
|
207
|
+
tiles: tiandituTileUrls(base, opts),
|
|
208
|
+
tileSize,
|
|
209
|
+
maxzoom,
|
|
210
|
+
attribution: attr
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: `tianditu-${label}`,
|
|
215
|
+
isLabel: true,
|
|
216
|
+
source: {
|
|
217
|
+
type: "raster",
|
|
218
|
+
tiles: tiandituTileUrls(label, opts),
|
|
219
|
+
tileSize,
|
|
220
|
+
maxzoom,
|
|
221
|
+
attribution: attr
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
var MissingTokenError = class extends Error {
|
|
227
|
+
constructor() {
|
|
228
|
+
super("\u5929\u5730\u56FE token \u7F3A\u5931\uFF1A\u8C03\u7528 addTiandituBaseMap \u5FC5\u987B\u4F20\u5165 tianditu: { token } \u9009\u9879\u3002");
|
|
229
|
+
this.name = "MissingTokenError";
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/sources/addTianditu.ts
|
|
234
|
+
function tiandituStyle(type, opts) {
|
|
235
|
+
if (!opts.token) throw new MissingTokenError();
|
|
236
|
+
const sources = buildTiandituSources(type, opts);
|
|
237
|
+
return {
|
|
238
|
+
version: 8,
|
|
239
|
+
glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
|
|
240
|
+
sources: Object.fromEntries(
|
|
241
|
+
sources.map((s) => [s.id, s.source])
|
|
242
|
+
),
|
|
243
|
+
layers: sources.map((s) => ({
|
|
244
|
+
id: s.id,
|
|
245
|
+
type: "raster",
|
|
246
|
+
source: s.id,
|
|
247
|
+
// 注记层置于底图之上
|
|
248
|
+
...s.isLabel ? { minzoom: 0 } : {}
|
|
249
|
+
}))
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function addTiandituBaseMap(opts) {
|
|
253
|
+
if (!opts.token) throw new MissingTokenError();
|
|
254
|
+
const type = opts.type ?? "vector";
|
|
255
|
+
const sources = buildTiandituSources(type, opts);
|
|
256
|
+
const map = opts.map;
|
|
257
|
+
for (const s of sources) {
|
|
258
|
+
if (!map.getSource(s.id)) {
|
|
259
|
+
map.addSource(s.id, s.source);
|
|
260
|
+
}
|
|
261
|
+
if (!map.getLayer(s.id)) {
|
|
262
|
+
map.addLayer({
|
|
263
|
+
id: s.id,
|
|
264
|
+
type: "raster",
|
|
265
|
+
source: s.id
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/offline/storage.ts
|
|
272
|
+
var MemoryTileStore = class {
|
|
273
|
+
constructor() {
|
|
274
|
+
this.map = /* @__PURE__ */ new Map();
|
|
275
|
+
}
|
|
276
|
+
async get(key) {
|
|
277
|
+
const t = this.map.get(key);
|
|
278
|
+
if (!t) return void 0;
|
|
279
|
+
if (t.expires && t.expires < Date.now()) {
|
|
280
|
+
this.map.delete(key);
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
return t;
|
|
284
|
+
}
|
|
285
|
+
async put(key, tile) {
|
|
286
|
+
this.map.set(key, tile);
|
|
287
|
+
}
|
|
288
|
+
async has(key) {
|
|
289
|
+
return await this.get(key) !== void 0;
|
|
290
|
+
}
|
|
291
|
+
async delete(key) {
|
|
292
|
+
this.map.delete(key);
|
|
293
|
+
}
|
|
294
|
+
async clear(prefix) {
|
|
295
|
+
if (!prefix) {
|
|
296
|
+
this.map.clear();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
for (const k of [...this.map.keys()]) {
|
|
300
|
+
if (k.startsWith(prefix)) this.map.delete(k);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async size() {
|
|
304
|
+
return this.map.size;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
function tileKey(sourceId, z, x, y) {
|
|
308
|
+
return `${sourceId}:${z}:${x}:${y}`;
|
|
309
|
+
}
|
|
310
|
+
var OFFLINE_PROTOCOL = "caoguo-offline";
|
|
311
|
+
function parseOfflineUrl(url) {
|
|
312
|
+
const m = /^caoguo-offline:\/\/([^/]+)\/(\d+)\/(\d+)\/(\d+)$/.exec(url);
|
|
313
|
+
if (!m) return null;
|
|
314
|
+
return {
|
|
315
|
+
sourceId: decodeURIComponent(m[1]),
|
|
316
|
+
z: Number(m[2]),
|
|
317
|
+
x: Number(m[3]),
|
|
318
|
+
y: Number(m[4])
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/offline/protocol.ts
|
|
323
|
+
function offlineTileUrl(sourceId, z, x, y) {
|
|
324
|
+
return `${OFFLINE_PROTOCOL}://${encodeURIComponent(sourceId)}/${z}/${x}/${y}`;
|
|
325
|
+
}
|
|
326
|
+
function offlineSourceTiles(sourceId) {
|
|
327
|
+
return [`${OFFLINE_PROTOCOL}://${encodeURIComponent(sourceId)}/{z}/{x}/{y}`];
|
|
328
|
+
}
|
|
329
|
+
function toArrayBuffer(data) {
|
|
330
|
+
if (data instanceof Uint8Array) {
|
|
331
|
+
const out2 = new ArrayBuffer(data.byteLength);
|
|
332
|
+
new Uint8Array(out2).set(data);
|
|
333
|
+
return out2;
|
|
334
|
+
}
|
|
335
|
+
const src = data;
|
|
336
|
+
const out = new ArrayBuffer(src.byteLength);
|
|
337
|
+
new Uint8Array(out).set(new Uint8Array(src));
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function createOfflineLoader(ctx) {
|
|
341
|
+
return (params) => {
|
|
342
|
+
const url = params.url;
|
|
343
|
+
const parsed = parseOfflineUrl(url);
|
|
344
|
+
if (!parsed) {
|
|
345
|
+
return Promise.reject(new Error(`\u975E\u6CD5\u79BB\u7EBF URL: ${url}`));
|
|
346
|
+
}
|
|
347
|
+
const key = tileKey(parsed.sourceId, parsed.z, parsed.x, parsed.y);
|
|
348
|
+
return ctx.store.get(key).then((tile) => {
|
|
349
|
+
if (!tile) {
|
|
350
|
+
throw new Error(`\u79BB\u7EBF\u74E6\u7247\u7F3A\u5931: ${key}`);
|
|
351
|
+
}
|
|
352
|
+
return { data: toArrayBuffer(tile.data) };
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function registerOfflineProtocol(maplibregl2, ctx) {
|
|
357
|
+
maplibregl2.addProtocol(OFFLINE_PROTOCOL, createOfflineLoader(ctx));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/offline/packer.ts
|
|
361
|
+
var MAX_ZOOM_DEFAULT = 14;
|
|
362
|
+
function lon2x(lon, z) {
|
|
363
|
+
return Math.floor((lon + 180) / 360 * Math.pow(2, z));
|
|
364
|
+
}
|
|
365
|
+
function lat2y(lat, z) {
|
|
366
|
+
const rad = lat * Math.PI / 180;
|
|
367
|
+
return Math.floor(
|
|
368
|
+
(1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2 * Math.pow(2, z)
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
function tileBounds(z, x, y) {
|
|
372
|
+
const n = Math.pow(2, z);
|
|
373
|
+
const lonWest = x / n * 360 - 180;
|
|
374
|
+
const lonEast = (x + 1) / n * 360 - 180;
|
|
375
|
+
const latNorth = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n))) * 180 / Math.PI;
|
|
376
|
+
const latSouth = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1) / n))) * 180 / Math.PI;
|
|
377
|
+
return [lonWest, latSouth, lonEast, latNorth];
|
|
378
|
+
}
|
|
379
|
+
function coordInTile(lon, lat, z, x, y) {
|
|
380
|
+
const [w, s, e, n] = tileBounds(z, x, y);
|
|
381
|
+
return lon >= w && lon <= e && lat >= s && lat <= n;
|
|
382
|
+
}
|
|
383
|
+
async function packGeoJSONToStore(store, opts) {
|
|
384
|
+
const maxZoom = opts.maxZoom ?? MAX_ZOOM_DEFAULT;
|
|
385
|
+
const expires = opts.expires ?? 0;
|
|
386
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
387
|
+
for (const f of opts.geojson.features) {
|
|
388
|
+
const g = f.geometry;
|
|
389
|
+
const coords = [];
|
|
390
|
+
if (g.type === "Point" || g.type === "Circle") {
|
|
391
|
+
coords.push(g.coordinates);
|
|
392
|
+
} else if (g.type === "LineString" || g.type === "MultiPoint") {
|
|
393
|
+
coords.push(...g.coordinates);
|
|
394
|
+
} else if (g.type === "Polygon" || g.type === "MultiLineString") {
|
|
395
|
+
for (const ring of g.coordinates) coords.push(...ring);
|
|
396
|
+
} else if (g.type === "MultiPolygon") {
|
|
397
|
+
for (const poly of g.coordinates) {
|
|
398
|
+
for (const ring of poly) coords.push(...ring);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const [lon, lat] of coords) {
|
|
402
|
+
for (let z = 0; z <= maxZoom; z++) {
|
|
403
|
+
const x = lon2x(lon, z);
|
|
404
|
+
const y = lat2y(lat, z);
|
|
405
|
+
if (coordInTile(lon, lat, z, x, y)) {
|
|
406
|
+
const k = tileKey(opts.sourceId, z, x, y);
|
|
407
|
+
if (!buckets.has(k)) buckets.set(k, []);
|
|
408
|
+
buckets.get(k).push(f);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const total = buckets.size;
|
|
414
|
+
let done = 0;
|
|
415
|
+
for (const [k, features] of buckets) {
|
|
416
|
+
const fc = { type: "FeatureCollection", features };
|
|
417
|
+
const tile = {
|
|
418
|
+
data: new TextEncoder().encode(JSON.stringify(fc)),
|
|
419
|
+
format: "geojson",
|
|
420
|
+
expires
|
|
421
|
+
};
|
|
422
|
+
await store.put(k, tile);
|
|
423
|
+
done++;
|
|
424
|
+
opts.onProgress?.(done, total);
|
|
425
|
+
}
|
|
426
|
+
return total;
|
|
427
|
+
}
|
|
428
|
+
function offlineGeoJSONSource(sourceId) {
|
|
429
|
+
return {
|
|
430
|
+
type: "geojson",
|
|
431
|
+
data: void 0,
|
|
432
|
+
tiles: [`caoguo-offline://${encodeURIComponent(sourceId)}/{z}/{x}/{y}`],
|
|
433
|
+
_caoguoOffline: true,
|
|
434
|
+
_sourceId: sourceId
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/offline/serviceWorker.ts
|
|
439
|
+
var CACHEABLE_HOSTS = ["t0.tianditu.gov.cn", "t7.tianditu.gov.cn", "tianditu.gov.cn"];
|
|
440
|
+
var CACHE_NAME = "caoguo-offline-tiles-v1";
|
|
441
|
+
var MSG_AIRGAP = "caoguo:airgap";
|
|
442
|
+
function shouldCache(request) {
|
|
443
|
+
if (request.method !== "GET") return false;
|
|
444
|
+
try {
|
|
445
|
+
const u = new URL(request.url);
|
|
446
|
+
if (u.protocol === `${OFFLINE_PROTOCOL}:`) return false;
|
|
447
|
+
return CACHEABLE_HOSTS.some((h) => u.host === h || u.host.endsWith(`.${h}`));
|
|
448
|
+
} catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function resolveCacheKey(request) {
|
|
453
|
+
return request.url;
|
|
454
|
+
}
|
|
455
|
+
async function resolveFromStore(store, request) {
|
|
456
|
+
if (!store) return void 0;
|
|
457
|
+
const z = /TILEMATRIX=(\d+)/.exec(request.url)?.[1];
|
|
458
|
+
const x = /TILECOL=(\d+)/.exec(request.url)?.[1];
|
|
459
|
+
const y = /TILEROW=(\d+)/.exec(request.url)?.[1];
|
|
460
|
+
if (!z || !x || !y) return void 0;
|
|
461
|
+
const layer = /LAYER=([a-z]+)/.exec(request.url)?.[1] ?? "tianditu";
|
|
462
|
+
const key = tileKey(`td-${layer}`, Number(z), Number(x), Number(y));
|
|
463
|
+
const tile = await store.get(key);
|
|
464
|
+
if (!tile) return void 0;
|
|
465
|
+
const buf = tile.data instanceof Uint8Array ? tile.data : new Uint8Array(tile.data);
|
|
466
|
+
return { data: buf.buffer, contentType: "image/png" };
|
|
467
|
+
}
|
|
468
|
+
async function resolveResponse(request, deps) {
|
|
469
|
+
if (!shouldCache(request)) return null;
|
|
470
|
+
const key = resolveCacheKey(request);
|
|
471
|
+
const cached = await deps.cache.match(key);
|
|
472
|
+
if (cached && cached.ok) {
|
|
473
|
+
return {
|
|
474
|
+
data: await cached.arrayBuffer(),
|
|
475
|
+
contentType: cached.headers.get("content-type") ?? "image/png",
|
|
476
|
+
source: "cache",
|
|
477
|
+
cacheable: false
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (deps.airgap.enabled) {
|
|
481
|
+
const fromStore = await resolveFromStore(deps.store, request);
|
|
482
|
+
if (fromStore) {
|
|
483
|
+
return { ...fromStore, source: "store", cacheable: false };
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const resp = await deps.fetchImpl(request.url);
|
|
489
|
+
if (resp.ok) {
|
|
490
|
+
const data = await resp.arrayBuffer();
|
|
491
|
+
const contentType = resp.headers.get("content-type") ?? "image/png";
|
|
492
|
+
void deps.cache.put(key, {
|
|
493
|
+
ok: true,
|
|
494
|
+
status: resp.status,
|
|
495
|
+
arrayBuffer: async () => data,
|
|
496
|
+
headers: { get: (k) => k.toLowerCase() === "content-type" ? contentType : null }
|
|
497
|
+
}).catch(() => void 0);
|
|
498
|
+
return { data, contentType, source: "network", cacheable: true };
|
|
499
|
+
}
|
|
500
|
+
} catch {
|
|
501
|
+
const fromStore = await resolveFromStore(deps.store, request);
|
|
502
|
+
if (fromStore) {
|
|
503
|
+
return { ...fromStore, source: "store", cacheable: false };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
function createFetchHandler(opts) {
|
|
509
|
+
const airgap = opts.airgap ?? { enabled: false };
|
|
510
|
+
return (event) => {
|
|
511
|
+
const request = event.request;
|
|
512
|
+
event.respondWith(
|
|
513
|
+
(async () => {
|
|
514
|
+
const deps = {
|
|
515
|
+
fetchImpl: (u) => fetch(u),
|
|
516
|
+
cache: caches,
|
|
517
|
+
airgap,
|
|
518
|
+
store: opts.store
|
|
519
|
+
};
|
|
520
|
+
const res = await resolveResponse(request, deps);
|
|
521
|
+
if (res) {
|
|
522
|
+
return new Response(res.data, {
|
|
523
|
+
status: 200,
|
|
524
|
+
headers: { "content-type": res.contentType, "x-caoguo-source": res.source }
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return fetch(request.url);
|
|
528
|
+
})()
|
|
529
|
+
);
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function installServiceWorker(sw) {
|
|
533
|
+
const airgap = { enabled: false };
|
|
534
|
+
sw.addEventListener("install", () => {
|
|
535
|
+
sw.skipWaiting?.();
|
|
536
|
+
});
|
|
537
|
+
sw.addEventListener("activate", () => {
|
|
538
|
+
sw.clients?.claim?.();
|
|
539
|
+
});
|
|
540
|
+
sw.addEventListener("message", (ev) => {
|
|
541
|
+
const msg = ev;
|
|
542
|
+
if (msg.data?.type === MSG_AIRGAP) {
|
|
543
|
+
airgap.enabled = !!msg.data.enabled;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
sw.addEventListener("fetch", createFetchHandler({ store: sw.store, airgap }));
|
|
547
|
+
return { airgap };
|
|
548
|
+
}
|
|
549
|
+
async function registerOfflineServiceWorker(scriptUrl) {
|
|
550
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
|
|
551
|
+
try {
|
|
552
|
+
return await navigator.serviceWorker.register(scriptUrl);
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function setAirgap(registration, enabled) {
|
|
558
|
+
const ctrl = registration.active ?? registration.installing ?? registration.waiting;
|
|
559
|
+
ctrl?.postMessage({ type: MSG_AIRGAP, enabled });
|
|
560
|
+
}
|
|
561
|
+
var SW_TEMPLATE_HINT = "importScripts('caoguo-offline-sw.bundle.js'); installServiceWorker(self);";
|
|
562
|
+
|
|
563
|
+
// src/offline/idbStore.ts
|
|
564
|
+
var dbPromise = null;
|
|
565
|
+
function getDb() {
|
|
566
|
+
if (!dbPromise) {
|
|
567
|
+
dbPromise = (async () => {
|
|
568
|
+
const { openDB } = await import('idb');
|
|
569
|
+
return openDB("caoguo-offline", 1, {
|
|
570
|
+
upgrade(db) {
|
|
571
|
+
if (!db.objectStoreNames.contains("tiles")) {
|
|
572
|
+
const store = db.createObjectStore("tiles", { keyPath: "key" });
|
|
573
|
+
store.createIndex("sourceId", "sourceId");
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
})();
|
|
578
|
+
}
|
|
579
|
+
return dbPromise;
|
|
580
|
+
}
|
|
581
|
+
var IdbTileStore = class {
|
|
582
|
+
async get(key) {
|
|
583
|
+
const db = await getDb();
|
|
584
|
+
const row = await db.get("tiles", key);
|
|
585
|
+
if (!row) return void 0;
|
|
586
|
+
if (row.expires && row.expires < Date.now()) {
|
|
587
|
+
await this.delete(key);
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
return { data: row.data, format: row.format, expires: row.expires };
|
|
591
|
+
}
|
|
592
|
+
async put(key, tile) {
|
|
593
|
+
const db = await getDb();
|
|
594
|
+
const sourceId = key.split(":")[0];
|
|
595
|
+
await db.put("tiles", {
|
|
596
|
+
key,
|
|
597
|
+
sourceId,
|
|
598
|
+
data: tile.data,
|
|
599
|
+
format: tile.format,
|
|
600
|
+
expires: tile.expires
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async has(key) {
|
|
604
|
+
return await this.get(key) !== void 0;
|
|
605
|
+
}
|
|
606
|
+
async delete(key) {
|
|
607
|
+
const db = await getDb();
|
|
608
|
+
await db.delete("tiles", key);
|
|
609
|
+
}
|
|
610
|
+
async clear(prefix) {
|
|
611
|
+
const db = await getDb();
|
|
612
|
+
if (!prefix) {
|
|
613
|
+
await db.clear("tiles");
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const tx = db.transaction("tiles", "readwrite");
|
|
617
|
+
const idx = tx.store.index("sourceId");
|
|
618
|
+
let cursor = await idx.openCursor(prefix);
|
|
619
|
+
while (cursor) {
|
|
620
|
+
await cursor.delete();
|
|
621
|
+
cursor = await cursor.continue();
|
|
622
|
+
}
|
|
623
|
+
await tx.done;
|
|
624
|
+
}
|
|
625
|
+
async size() {
|
|
626
|
+
const db = await getDb();
|
|
627
|
+
return db.count("tiles");
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
function createDefaultStore() {
|
|
631
|
+
if (typeof indexedDB !== "undefined") {
|
|
632
|
+
try {
|
|
633
|
+
return new IdbTileStore();
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return new MemoryTileStore();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/controls/ScaleControl.ts
|
|
641
|
+
var EARTH_RADIUS = 6378137;
|
|
642
|
+
function computeScaleBar(latitude, zoom, opts = {}) {
|
|
643
|
+
const dpiScale = opts.dpiScale ?? 1;
|
|
644
|
+
const maxWidth = opts.maxWidth ?? 100;
|
|
645
|
+
const tileSize = opts.tileSize ?? 512;
|
|
646
|
+
const latRad = latitude * Math.PI / 180;
|
|
647
|
+
const metersPerPixel = Math.cos(latRad) * 2 * Math.PI * EARTH_RADIUS / (tileSize * Math.pow(2, zoom) * dpiScale);
|
|
648
|
+
let meters = metersPerPixel * maxWidth;
|
|
649
|
+
const pow = Math.pow(10, Math.floor(Math.log10(meters)));
|
|
650
|
+
const base = meters / pow;
|
|
651
|
+
const step = base >= 5 ? 5 : base >= 2 ? 2 : 1;
|
|
652
|
+
meters = step * pow;
|
|
653
|
+
const pixels = meters / metersPerPixel;
|
|
654
|
+
const label = meters >= 1e3 ? `${(meters / 1e3).toLocaleString()} km` : `${Math.round(meters)} m`;
|
|
655
|
+
return { meters, pixels, label };
|
|
656
|
+
}
|
|
657
|
+
var ScaleControl = class {
|
|
658
|
+
constructor(map, options = {}) {
|
|
659
|
+
this.onMove = (ev) => this.handleMove(ev);
|
|
660
|
+
this.onZoom = () => this.update();
|
|
661
|
+
this.map = map;
|
|
662
|
+
this.opts = {
|
|
663
|
+
showCoordinate: options.showCoordinate ?? true,
|
|
664
|
+
maxWidth: options.maxWidth ?? 100
|
|
665
|
+
};
|
|
666
|
+
this.el = options.container ?? document.createElement("div");
|
|
667
|
+
this.el.className = "caoguo-scale-control";
|
|
668
|
+
this.el.style.cssText = "position:absolute;left:10px;bottom:24px;padding:4px 8px;background:rgba(10,15,30,.7);color:#cfe;border-radius:4px;font:12px/1.4 system-ui,sans-serif;pointer-events:none;z-index:2;";
|
|
669
|
+
this.barEl = document.createElement("div");
|
|
670
|
+
this.barEl.style.cssText = "height:6px;border:1px solid #cfe;border-top:none;margin-bottom:2px;";
|
|
671
|
+
this.labelEl = document.createElement("div");
|
|
672
|
+
if (this.opts.showCoordinate) {
|
|
673
|
+
this.coordEl = document.createElement("div");
|
|
674
|
+
this.coordEl.style.cssText = "opacity:.85;margin-top:2px;";
|
|
675
|
+
this.el.append(this.barEl, this.labelEl, this.coordEl);
|
|
676
|
+
} else {
|
|
677
|
+
this.el.append(this.barEl, this.labelEl);
|
|
678
|
+
}
|
|
679
|
+
map.on("zoom", this.onZoom);
|
|
680
|
+
map.on("move", this.onZoom);
|
|
681
|
+
const canvas = map.getCanvas?.();
|
|
682
|
+
canvas?.addEventListener("mousemove", this.onMove);
|
|
683
|
+
}
|
|
684
|
+
/** 把控件挂到地图容器(无预设容器时调用) */
|
|
685
|
+
addTo(container) {
|
|
686
|
+
if (!this.el.parentElement) container.appendChild(this.el);
|
|
687
|
+
this.update();
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
/** 计算并刷新显示 */
|
|
691
|
+
update() {
|
|
692
|
+
const center = this.map.getCenter();
|
|
693
|
+
const bar = computeScaleBar(center.lat, this.map.getZoom(), { maxWidth: this.opts.maxWidth });
|
|
694
|
+
this.barEl.style.width = `${Math.round(bar.pixels)}px`;
|
|
695
|
+
this.labelEl.textContent = bar.label;
|
|
696
|
+
}
|
|
697
|
+
handleMove(ev) {
|
|
698
|
+
if (!this.coordEl) return;
|
|
699
|
+
const e = ev;
|
|
700
|
+
const ll = e.lngLat;
|
|
701
|
+
if (!ll) return;
|
|
702
|
+
this.coordEl.textContent = `${ll.lng.toFixed(5)}, ${ll.lat.toFixed(5)}`;
|
|
703
|
+
}
|
|
704
|
+
/** 移除控件与事件监听 */
|
|
705
|
+
remove() {
|
|
706
|
+
this.map.off("zoom", this.onZoom);
|
|
707
|
+
this.map.off("move", this.onZoom);
|
|
708
|
+
const canvas = this.map.getCanvas?.();
|
|
709
|
+
canvas?.removeEventListener("mousemove", this.onMove);
|
|
710
|
+
this.el.remove();
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// src/controls/ThemeSwitcher.ts
|
|
715
|
+
function oppositeTheme(theme) {
|
|
716
|
+
return theme === "caoguo-dark" ? "caoguo-light" : "caoguo-dark";
|
|
717
|
+
}
|
|
718
|
+
function themeFromStyle(style, fallback = "caoguo-dark") {
|
|
719
|
+
if (!style) return fallback;
|
|
720
|
+
const name = typeof style === "string" ? style : style.name;
|
|
721
|
+
if (name === "caoguo-dark" || name === "caoguo-light") return name;
|
|
722
|
+
return fallback;
|
|
723
|
+
}
|
|
724
|
+
var ThemeSwitcher = class {
|
|
725
|
+
constructor(map, options = {}) {
|
|
726
|
+
this.map = map;
|
|
727
|
+
this.current = options.initial ?? themeFromStyle(map.getStyle?.(), "caoguo-dark");
|
|
728
|
+
this.el = options.container ?? document.createElement("div");
|
|
729
|
+
this.el.className = "caoguo-theme-switcher";
|
|
730
|
+
this.el.style.cssText = "position:absolute;right:10px;top:10px;z-index:3;";
|
|
731
|
+
this.btn = document.createElement("button");
|
|
732
|
+
this.btn.type = "button";
|
|
733
|
+
this.applyLabel();
|
|
734
|
+
this.btn.style.cssText = "cursor:pointer;padding:4px 10px;border:1px solid #2a3550;border-radius:6px;background:rgba(10,15,30,.7);color:#cfe;font:12px system-ui,sans-serif;";
|
|
735
|
+
this.btn.addEventListener("click", () => this.toggle());
|
|
736
|
+
this.el.append(this.btn);
|
|
737
|
+
}
|
|
738
|
+
applyLabel() {
|
|
739
|
+
this.btn.textContent = this.current === "caoguo-dark" ? "\u{1F319} \u6697\u8272" : "\u2600 \u4EAE\u8272";
|
|
740
|
+
}
|
|
741
|
+
/** 当前主题 */
|
|
742
|
+
getTheme() {
|
|
743
|
+
return this.current;
|
|
744
|
+
}
|
|
745
|
+
/** 切换到指定主题(保留视图,diff 模式避免闪烁) */
|
|
746
|
+
setTheme(theme) {
|
|
747
|
+
if (theme === this.current) return;
|
|
748
|
+
this.current = theme;
|
|
749
|
+
this.applyLabel();
|
|
750
|
+
this.map.setStyle(caoguoStyle(theme), { diff: true });
|
|
751
|
+
}
|
|
752
|
+
/** 在明暗之间切换 */
|
|
753
|
+
toggle() {
|
|
754
|
+
this.setTheme(oppositeTheme(this.current));
|
|
755
|
+
}
|
|
756
|
+
/** 挂到容器 */
|
|
757
|
+
addTo(container) {
|
|
758
|
+
if (!this.el.parentElement) container.appendChild(this.el);
|
|
759
|
+
return this;
|
|
760
|
+
}
|
|
761
|
+
/** 移除 */
|
|
762
|
+
remove() {
|
|
763
|
+
this.btn.removeEventListener("click", () => this.toggle());
|
|
764
|
+
this.el.remove();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
// src/shaders/glowGeometry.ts
|
|
769
|
+
function glowPasses(passes = 4, coreOpacity = 1) {
|
|
770
|
+
if (passes < 1) passes = 1;
|
|
771
|
+
const out = [];
|
|
772
|
+
for (let i = 0; i < passes; i++) {
|
|
773
|
+
const t = passes === 1 ? 1 : 1 - i / (passes - 1);
|
|
774
|
+
out.push({
|
|
775
|
+
widthScale: 1 + (1 - t) * (passes - 1) * 1.5,
|
|
776
|
+
opacity: coreOpacity * (0.12 + 0.88 * t)
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return out;
|
|
780
|
+
}
|
|
781
|
+
function projectSimple(lng, lat) {
|
|
782
|
+
const x = lng / 180;
|
|
783
|
+
const y = Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)) / Math.PI;
|
|
784
|
+
return [x, Math.max(-1, Math.min(1, y))];
|
|
785
|
+
}
|
|
786
|
+
function buildGlowGeometry(lines, opts = {}) {
|
|
787
|
+
const passes = glowPasses(opts.passes ?? 4, opts.coreOpacity ?? 1);
|
|
788
|
+
let vertexCount = 0;
|
|
789
|
+
const projected = lines.map((l) => {
|
|
790
|
+
const points = l.coordinates.map(([lng, lat]) => projectSimple(lng, lat));
|
|
791
|
+
vertexCount += Math.max(0, points.length - 1) * 2;
|
|
792
|
+
return { points, group: l.group };
|
|
793
|
+
});
|
|
794
|
+
return { lines: projected, passes, vertexCount };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/shaders/CustomLineLayer.ts
|
|
798
|
+
var DEFAULT_COLORS = {
|
|
799
|
+
pipe: [0.2, 0.85, 1],
|
|
800
|
+
// 青蓝:管线
|
|
801
|
+
road: [0.6, 0.7, 0.9],
|
|
802
|
+
// 灰蓝:路网
|
|
803
|
+
water: [0.25, 0.55, 0.95]
|
|
804
|
+
// 深蓝:水系
|
|
805
|
+
};
|
|
806
|
+
var VERT = `
|
|
807
|
+
attribute vec2 aPos;
|
|
808
|
+
attribute float aMiter;
|
|
809
|
+
uniform mat4 uMatrix;
|
|
810
|
+
uniform float uWidth;
|
|
811
|
+
uniform vec2 uResolution;
|
|
812
|
+
void main() {
|
|
813
|
+
// \u7B80\u5316\uFF1A\u6CBF\u6CD5\u7EBF\u65B9\u5411\u504F\u79FB width\uFF08\u5C4F\u5E55\u7A7A\u95F4\uFF09\uFF0C\u8FD9\u91CC\u4EC5\u6F14\u793A\u6838\u5FC3\u6295\u5F71
|
|
814
|
+
vec4 clip = uMatrix * vec4(aPos, 0.0, 1.0);
|
|
815
|
+
vec2 screen = clip.xy / clip.w;
|
|
816
|
+
// \u6CE8\uFF1A\u7CBE\u786E\u7EBF\u5BBD\u9700\u6CD5\u7EBF\u8BA1\u7B97\uFF0C\u793A\u8303\u5C42\u7528\u70B9\u5BBD\u8FD1\u4F3C
|
|
817
|
+
vec2 offset = vec2(aMiter * uWidth / uResolution.x * 2.0, 0.0);
|
|
818
|
+
gl_Position = vec4(screen + offset * clip.w, clip.z, clip.w);
|
|
819
|
+
}
|
|
820
|
+
`;
|
|
821
|
+
var FRAG = `
|
|
822
|
+
precision mediump float;
|
|
823
|
+
uniform vec3 uColor;
|
|
824
|
+
uniform float uOpacity;
|
|
825
|
+
void main() {
|
|
826
|
+
gl_FragColor = vec4(uColor, uOpacity);
|
|
827
|
+
}
|
|
828
|
+
`;
|
|
829
|
+
function compile(gl, type, src) {
|
|
830
|
+
const sh = gl.createShader(type);
|
|
831
|
+
gl.shaderSource(sh, src);
|
|
832
|
+
gl.compileShader(sh);
|
|
833
|
+
return sh;
|
|
834
|
+
}
|
|
835
|
+
var CustomLineLayer = class {
|
|
836
|
+
constructor(opts) {
|
|
837
|
+
this.type = "custom";
|
|
838
|
+
this.renderingMode = "2d";
|
|
839
|
+
this.id = opts.id ?? "caoguo-glow-line";
|
|
840
|
+
this.lines = opts.lines;
|
|
841
|
+
this.colors = { ...DEFAULT_COLORS, ...opts.colors };
|
|
842
|
+
this.baseWidth = opts.baseWidth ?? 3;
|
|
843
|
+
this.passes = opts.passes ?? 4;
|
|
844
|
+
this.geometry = buildGlowGeometry(this.lines, { passes: this.passes });
|
|
845
|
+
}
|
|
846
|
+
onAdd(map, gl) {
|
|
847
|
+
this.map = map;
|
|
848
|
+
const vs = compile(gl, gl.VERTEX_SHADER, VERT);
|
|
849
|
+
const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);
|
|
850
|
+
const prog = gl.createProgram();
|
|
851
|
+
gl.attachShader(prog, vs);
|
|
852
|
+
gl.attachShader(prog, fs);
|
|
853
|
+
gl.linkProgram(prog);
|
|
854
|
+
this.program = prog;
|
|
855
|
+
const verts = [];
|
|
856
|
+
for (const line of this.geometry.lines) {
|
|
857
|
+
const pts = line.points;
|
|
858
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
859
|
+
verts.push(pts[i][0], pts[i][1], 0, pts[i + 1][0], pts[i + 1][1], 0);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const buf = gl.createBuffer();
|
|
863
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
864
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
|
|
865
|
+
this.buffer = buf;
|
|
866
|
+
}
|
|
867
|
+
render(gl, matrix) {
|
|
868
|
+
if (!this.program || !this.buffer) return;
|
|
869
|
+
gl.useProgram(this.program);
|
|
870
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
871
|
+
const aPos = gl.getAttribLocation(this.program, "aPos");
|
|
872
|
+
const aMiter = gl.getAttribLocation(this.program, "aMiter");
|
|
873
|
+
gl.enableVertexAttribArray(aPos);
|
|
874
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 24, 0);
|
|
875
|
+
gl.enableVertexAttribArray(aMiter);
|
|
876
|
+
gl.vertexAttribPointer(aMiter, 1, gl.FLOAT, false, 24, 16);
|
|
877
|
+
const uMatrix = gl.getUniformLocation(this.program, "uMatrix");
|
|
878
|
+
gl.uniformMatrix4fv(uMatrix, false, new Float32Array(matrix));
|
|
879
|
+
const uWidth = gl.getUniformLocation(this.program, "uWidth");
|
|
880
|
+
const uColor = gl.getUniformLocation(this.program, "uColor");
|
|
881
|
+
const uOpacity = gl.getUniformLocation(this.program, "uOpacity");
|
|
882
|
+
const uRes = gl.getUniformLocation(this.program, "uResolution");
|
|
883
|
+
const canvas = this.map?.getCanvas();
|
|
884
|
+
gl.uniform2f(uRes, canvas?.width ?? 1024, canvas?.height ?? 768);
|
|
885
|
+
gl.enable(gl.BLEND);
|
|
886
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
|
887
|
+
const color = this.colors[this.lines[0]?.group ?? "pipe"] ?? this.colors.pipe;
|
|
888
|
+
for (const pass of this.geometry.passes) {
|
|
889
|
+
gl.uniform1f(uWidth, this.baseWidth * pass.widthScale);
|
|
890
|
+
gl.uniform3f(uColor, color[0], color[1], color[2]);
|
|
891
|
+
gl.uniform1f(uOpacity, pass.opacity);
|
|
892
|
+
gl.drawArrays(gl.LINES, 0, this.geometry.vertexCount);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
onRemove(_map, gl) {
|
|
896
|
+
if (this.buffer) gl.deleteBuffer(this.buffer);
|
|
897
|
+
if (this.program) gl.deleteProgram(this.program);
|
|
898
|
+
this.buffer = void 0;
|
|
899
|
+
this.program = void 0;
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/lod/lodController.ts
|
|
904
|
+
function resolveLod(zoom, levels) {
|
|
905
|
+
let best = null;
|
|
906
|
+
for (const lv of levels) {
|
|
907
|
+
const max = lv.maxZoom ?? Infinity;
|
|
908
|
+
if (zoom >= lv.minZoom && zoom <= max) {
|
|
909
|
+
if (!best || lv.minZoom > best.minZoom) best = lv;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return best;
|
|
913
|
+
}
|
|
914
|
+
function suggestDensity(zoom, basePerTile = 200, tileCountFactor = 1) {
|
|
915
|
+
const z = Math.max(0, zoom);
|
|
916
|
+
return Math.round(basePerTile * Math.pow(2, Math.max(0, z - 8)) * tileCountFactor);
|
|
917
|
+
}
|
|
918
|
+
var LodController = class {
|
|
919
|
+
constructor(map, levels, onChange) {
|
|
920
|
+
this.current = null;
|
|
921
|
+
this.map = map;
|
|
922
|
+
this.levels = levels;
|
|
923
|
+
this.onChange = onChange;
|
|
924
|
+
this.handler = () => this.evaluate(false);
|
|
925
|
+
map.on("zoom", this.handler);
|
|
926
|
+
map.on("move", this.handler);
|
|
927
|
+
}
|
|
928
|
+
/** 立即评估当前 zoom 并(按需)触发回调 */
|
|
929
|
+
evaluate(force = false) {
|
|
930
|
+
const zoom = this.map.getZoom();
|
|
931
|
+
const next = resolveLod(zoom, this.levels);
|
|
932
|
+
if (!next) return this.current;
|
|
933
|
+
const changed = force || next.id !== this.current?.id;
|
|
934
|
+
if (changed || force) {
|
|
935
|
+
this.current = next;
|
|
936
|
+
this.onChange({ level: next, changed, zoom });
|
|
937
|
+
}
|
|
938
|
+
return next;
|
|
939
|
+
}
|
|
940
|
+
/** 当前等级 */
|
|
941
|
+
getLevel() {
|
|
942
|
+
return this.current;
|
|
943
|
+
}
|
|
944
|
+
/** 更新分级配置(如权限/数据就绪后) */
|
|
945
|
+
setLevels(levels) {
|
|
946
|
+
this.levels = levels;
|
|
947
|
+
this.evaluate(false);
|
|
948
|
+
}
|
|
949
|
+
/** 卸载监听 */
|
|
950
|
+
remove() {
|
|
951
|
+
this.map.off("zoom", this.handler);
|
|
952
|
+
this.map.off("move", this.handler);
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// src/index.ts
|
|
957
|
+
var Map2 = class {
|
|
958
|
+
constructor(options) {
|
|
959
|
+
/** 离线瓦片后端(默认浏览器 IndexedDB / Node 内存) */
|
|
960
|
+
this._store = createDefaultStore();
|
|
961
|
+
this._dataCRS = options.dataCRS ?? "WGS84";
|
|
962
|
+
this._map = new maplibregl__default.default.Map({
|
|
963
|
+
container: options.container,
|
|
964
|
+
center: options.center ?? WUHAN_CENTER,
|
|
965
|
+
zoom: options.zoom ?? WUHAN_ZOOM,
|
|
966
|
+
style: options.style ?? osmRasterStyle(),
|
|
967
|
+
pitch: options.pitch ?? 0,
|
|
968
|
+
bearing: options.bearing ?? 0,
|
|
969
|
+
attributionControl: { compact: true }
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* 把业务坐标系坐标转换到地图渲染基准(WGS84)。
|
|
974
|
+
* 用于叠加 GCJ-02 / CGCS2000 数据前的纠偏。
|
|
975
|
+
*/
|
|
976
|
+
transformToMap(lng, lat) {
|
|
977
|
+
return toWgs84(this._dataCRS, lng, lat);
|
|
978
|
+
}
|
|
979
|
+
/** 设定叠加数据坐标系 */
|
|
980
|
+
setDataCRS(crs) {
|
|
981
|
+
this._dataCRS = crs;
|
|
982
|
+
}
|
|
983
|
+
/** 读取叠加数据坐标系 */
|
|
984
|
+
getDataCRS() {
|
|
985
|
+
return this._dataCRS;
|
|
986
|
+
}
|
|
987
|
+
/** 生成当前 dataCRS -> WGS84 的变换器(供批量转换使用) */
|
|
988
|
+
getTransformer() {
|
|
989
|
+
return createTransformer(this._dataCRS, "WGS84");
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* 切换为底图为天地图(国内权威底图,CGCS2000 / Web Mercator)。
|
|
993
|
+
* token 必须由调用方注入,缺失时抛出 MissingTokenError。
|
|
994
|
+
*/
|
|
995
|
+
useTianditu(type, opts) {
|
|
996
|
+
this._map.setStyle(tiandituStyle(type, opts));
|
|
997
|
+
}
|
|
998
|
+
/** 向当前地图注入天地图底图(叠加在现有 style 之上) */
|
|
999
|
+
addTianditu(opts) {
|
|
1000
|
+
addTiandituBaseMap({ ...opts, map: this._map });
|
|
1001
|
+
}
|
|
1002
|
+
/** 注册离线协议并启用离线瓦片读取(F-1.4) */
|
|
1003
|
+
enableOffline(store) {
|
|
1004
|
+
if (store) this._store = store;
|
|
1005
|
+
registerOfflineProtocol(maplibregl__default.default, { store: this._store });
|
|
1006
|
+
}
|
|
1007
|
+
/** 当前离线存储实例 */
|
|
1008
|
+
getOfflineStore() {
|
|
1009
|
+
return this._store;
|
|
1010
|
+
}
|
|
1011
|
+
/** 构造离线源 tiles(供 source.tiles 使用) */
|
|
1012
|
+
offlineTiles(sourceId) {
|
|
1013
|
+
return offlineSourceTiles(sourceId);
|
|
1014
|
+
}
|
|
1015
|
+
/** 把 GeoJSON 打包进离线存储(分桶到瓦片网格) */
|
|
1016
|
+
async packGeoJSON(sourceId, geojson, opts = {}) {
|
|
1017
|
+
return packGeoJSONToStore(this._store, {
|
|
1018
|
+
sourceId,
|
|
1019
|
+
geojson,
|
|
1020
|
+
maxZoom: opts.maxZoom,
|
|
1021
|
+
expires: opts.expires
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
addLayer(layer) {
|
|
1025
|
+
this._map.addLayer(layer);
|
|
1026
|
+
}
|
|
1027
|
+
removeLayer(id) {
|
|
1028
|
+
if (this._map.getLayer(id)) this._map.removeLayer(id);
|
|
1029
|
+
}
|
|
1030
|
+
on(event, layerId, cb) {
|
|
1031
|
+
if (cb && layerId) this._map.on(event, layerId, cb);
|
|
1032
|
+
else this._map.on(event, cb);
|
|
1033
|
+
}
|
|
1034
|
+
addSource(id, source) {
|
|
1035
|
+
this._map.addSource(id, source);
|
|
1036
|
+
}
|
|
1037
|
+
getSource(id) {
|
|
1038
|
+
return this._map.getSource(id);
|
|
1039
|
+
}
|
|
1040
|
+
flyTo(opts) {
|
|
1041
|
+
this._map.flyTo(opts);
|
|
1042
|
+
}
|
|
1043
|
+
remove() {
|
|
1044
|
+
this._map.remove();
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* 挂载比例尺 + 实时坐标控件(T8 / F-1.8)。
|
|
1048
|
+
* 返回控件实例,可调用 .remove() 卸载。
|
|
1049
|
+
*/
|
|
1050
|
+
addScaleControl(options) {
|
|
1051
|
+
const ctrl = new ScaleControl(this._map, options);
|
|
1052
|
+
const container = this._map.getContainer();
|
|
1053
|
+
ctrl.addTo(container);
|
|
1054
|
+
return ctrl;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* 挂载主题切换控件(T8 / F-1.9),在 caoguo-dark / caoguo-light 间切换。
|
|
1058
|
+
* 返回控件实例,可调用 .toggle() / .setTheme() / .remove()。
|
|
1059
|
+
*/
|
|
1060
|
+
addThemeSwitcher(initial) {
|
|
1061
|
+
const ctrl = new ThemeSwitcher(this._map, { initial });
|
|
1062
|
+
const container = this._map.getContainer();
|
|
1063
|
+
ctrl.addTo(container);
|
|
1064
|
+
return ctrl;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* 挂载辉光管线 Custom Layer(T6 / F-1.3)。
|
|
1068
|
+
* 传入 GeoJSON 线集合,叠加渲染管线/路网/水系辉光效果。
|
|
1069
|
+
* @returns 图层 id(可用于 removeLayer)
|
|
1070
|
+
*/
|
|
1071
|
+
addGlowLayer(opts) {
|
|
1072
|
+
const layer = new CustomLineLayer(opts);
|
|
1073
|
+
this._map.addLayer(layer);
|
|
1074
|
+
return layer.id;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* 挂载 LOD 控制器(T7 / F-1.7)。
|
|
1078
|
+
* 随 zoom 切换数据密度等级,并在等级变化时回调(调用方据此 setData / 切源)。
|
|
1079
|
+
* @returns LodController 实例(可 .getLevel() / .remove())
|
|
1080
|
+
*/
|
|
1081
|
+
addLodController(levels, onLod) {
|
|
1082
|
+
const ctrl = new LodController(this._map, levels, onLod);
|
|
1083
|
+
ctrl.evaluate(true);
|
|
1084
|
+
return ctrl;
|
|
1085
|
+
}
|
|
1086
|
+
get instance() {
|
|
1087
|
+
return this._map;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
var src_default = Map2;
|
|
1091
|
+
|
|
1092
|
+
Object.defineProperty(exports, "maplibregl", {
|
|
1093
|
+
enumerable: true,
|
|
1094
|
+
get: function () { return maplibregl__default.default; }
|
|
1095
|
+
});
|
|
1096
|
+
exports.CACHEABLE_HOSTS = CACHEABLE_HOSTS;
|
|
1097
|
+
exports.CACHE_NAME = CACHE_NAME;
|
|
1098
|
+
exports.CustomLineLayer = CustomLineLayer;
|
|
1099
|
+
exports.IdbTileStore = IdbTileStore;
|
|
1100
|
+
exports.LodController = LodController;
|
|
1101
|
+
exports.MSG_AIRGAP = MSG_AIRGAP;
|
|
1102
|
+
exports.Map = Map2;
|
|
1103
|
+
exports.MemoryTileStore = MemoryTileStore;
|
|
1104
|
+
exports.MissingTokenError = MissingTokenError;
|
|
1105
|
+
exports.OFFLINE_PROTOCOL = OFFLINE_PROTOCOL;
|
|
1106
|
+
exports.SW_TEMPLATE_HINT = SW_TEMPLATE_HINT;
|
|
1107
|
+
exports.ScaleControl = ScaleControl;
|
|
1108
|
+
exports.ThemeSwitcher = ThemeSwitcher;
|
|
1109
|
+
exports.WUHAN_CENTER = WUHAN_CENTER;
|
|
1110
|
+
exports.WUHAN_ZOOM = WUHAN_ZOOM;
|
|
1111
|
+
exports.addTiandituBaseMap = addTiandituBaseMap;
|
|
1112
|
+
exports.buildGlowGeometry = buildGlowGeometry;
|
|
1113
|
+
exports.buildTiandituSources = buildTiandituSources;
|
|
1114
|
+
exports.cgcs2000ToWgs84 = cgcs2000ToWgs84;
|
|
1115
|
+
exports.computeScaleBar = computeScaleBar;
|
|
1116
|
+
exports.createDefaultStore = createDefaultStore;
|
|
1117
|
+
exports.createFetchHandler = createFetchHandler;
|
|
1118
|
+
exports.createOfflineLoader = createOfflineLoader;
|
|
1119
|
+
exports.createTransformer = createTransformer;
|
|
1120
|
+
exports.default = src_default;
|
|
1121
|
+
exports.fromWgs84 = fromWgs84;
|
|
1122
|
+
exports.gcj02ToWgs84 = gcj02ToWgs84;
|
|
1123
|
+
exports.glowPasses = glowPasses;
|
|
1124
|
+
exports.installServiceWorker = installServiceWorker;
|
|
1125
|
+
exports.isInChina = isInChina;
|
|
1126
|
+
exports.offlineGeoJSONSource = offlineGeoJSONSource;
|
|
1127
|
+
exports.offlineSourceTiles = offlineSourceTiles;
|
|
1128
|
+
exports.offlineTileUrl = offlineTileUrl;
|
|
1129
|
+
exports.oppositeTheme = oppositeTheme;
|
|
1130
|
+
exports.osmRasterStyle = osmRasterStyle;
|
|
1131
|
+
exports.packGeoJSONToStore = packGeoJSONToStore;
|
|
1132
|
+
exports.parseOfflineUrl = parseOfflineUrl;
|
|
1133
|
+
exports.projectSimple = projectSimple;
|
|
1134
|
+
exports.registerOfflineProtocol = registerOfflineProtocol;
|
|
1135
|
+
exports.registerOfflineServiceWorker = registerOfflineServiceWorker;
|
|
1136
|
+
exports.resolveCacheKey = resolveCacheKey;
|
|
1137
|
+
exports.resolveFromStore = resolveFromStore;
|
|
1138
|
+
exports.resolveLod = resolveLod;
|
|
1139
|
+
exports.resolveResponse = resolveResponse;
|
|
1140
|
+
exports.setAirgap = setAirgap;
|
|
1141
|
+
exports.setCgcs2000GridShift = setCgcs2000GridShift;
|
|
1142
|
+
exports.shouldCache = shouldCache;
|
|
1143
|
+
exports.suggestDensity = suggestDensity;
|
|
1144
|
+
exports.themeFromStyle = themeFromStyle;
|
|
1145
|
+
exports.tiandituStyle = tiandituStyle;
|
|
1146
|
+
exports.tiandituTileUrls = tiandituTileUrls;
|
|
1147
|
+
exports.tileKey = tileKey;
|
|
1148
|
+
exports.toWgs84 = toWgs84;
|
|
1149
|
+
exports.transformBounds = transformBounds;
|
|
1150
|
+
exports.transformPoint = transformPoint;
|
|
1151
|
+
exports.wgs84ToCgcs2000 = wgs84ToCgcs2000;
|
|
1152
|
+
exports.wgs84ToGcj02 = wgs84ToGcj02;
|
|
1153
|
+
//# sourceMappingURL=index.cjs.map
|
|
1154
|
+
//# sourceMappingURL=index.cjs.map
|