@caoguo/maplibre 0.0.5 → 0.0.7

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.
Files changed (64) hide show
  1. package/dist/chunk-DENQSIV2.js +81 -0
  2. package/dist/chunk-DENQSIV2.js.map +1 -0
  3. package/dist/chunk-G7VY42ZG.js +210 -0
  4. package/dist/chunk-G7VY42ZG.js.map +1 -0
  5. package/dist/chunk-MLLIAWVI.js +214 -0
  6. package/dist/chunk-MLLIAWVI.js.map +1 -0
  7. package/dist/chunk-OLS4DD3F.js +27 -0
  8. package/dist/chunk-OLS4DD3F.js.map +1 -0
  9. package/dist/chunk-OUEPE6JT.js +26 -0
  10. package/dist/chunk-OUEPE6JT.js.map +1 -0
  11. package/dist/chunk-SKXL3DKL.js +125 -0
  12. package/dist/chunk-SKXL3DKL.js.map +1 -0
  13. package/dist/chunk-Y3JNDOHU.js +56 -0
  14. package/dist/chunk-Y3JNDOHU.js.map +1 -0
  15. package/dist/controls/index.cjs +227 -0
  16. package/dist/controls/index.cjs.map +1 -0
  17. package/dist/controls/index.d.cts +171 -0
  18. package/dist/controls/index.d.ts +171 -0
  19. package/dist/controls/index.js +4 -0
  20. package/dist/controls/index.js.map +1 -0
  21. package/dist/crs/index.cjs +137 -0
  22. package/dist/crs/index.cjs.map +1 -0
  23. package/dist/crs/index.d.cts +80 -0
  24. package/dist/crs/index.d.ts +80 -0
  25. package/dist/crs/index.js +3 -0
  26. package/dist/crs/index.js.map +1 -0
  27. package/dist/index.cjs +119 -41
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +19 -484
  30. package/dist/index.d.ts +19 -484
  31. package/dist/index.js +18 -604
  32. package/dist/index.js.map +1 -1
  33. package/dist/lod/index.cjs +60 -0
  34. package/dist/lod/index.cjs.map +1 -0
  35. package/dist/lod/index.d.cts +62 -0
  36. package/dist/lod/index.d.ts +62 -0
  37. package/dist/lod/index.js +3 -0
  38. package/dist/lod/index.js.map +1 -0
  39. package/dist/shaders/index.cjs +215 -0
  40. package/dist/shaders/index.cjs.map +1 -0
  41. package/dist/shaders/index.d.cts +131 -0
  42. package/dist/shaders/index.d.ts +131 -0
  43. package/dist/shaders/index.js +3 -0
  44. package/dist/shaders/index.js.map +1 -0
  45. package/dist/sourceUtils.cjs +30 -0
  46. package/dist/sourceUtils.cjs.map +1 -0
  47. package/dist/sourceUtils.d.cts +60 -0
  48. package/dist/sourceUtils.d.ts +60 -0
  49. package/dist/sourceUtils.js +3 -0
  50. package/dist/sourceUtils.js.map +1 -0
  51. package/dist/styles.cjs +47 -12
  52. package/dist/styles.cjs.map +1 -1
  53. package/dist/styles.d.cts +13 -4
  54. package/dist/styles.d.ts +13 -4
  55. package/dist/styles.js +1 -1
  56. package/dist/terrain.cjs +31 -0
  57. package/dist/terrain.cjs.map +1 -0
  58. package/dist/terrain.d.cts +28 -0
  59. package/dist/terrain.d.ts +28 -0
  60. package/dist/terrain.js +3 -0
  61. package/dist/terrain.js.map +1 -0
  62. package/package.json +35 -5
  63. package/dist/chunk-DNEF4DHG.js +0 -48
  64. package/dist/chunk-DNEF4DHG.js.map +0 -1
@@ -0,0 +1,81 @@
1
+ import { buildStyle } from '@caoguo/theme';
2
+
3
+ // src/styles.ts
4
+ var WUHAN_CENTER = [114.3055, 30.5928];
5
+ var WUHAN_ZOOM = 11;
6
+ function caoguoStyle(theme = "caoguo-dark") {
7
+ return buildStyle(theme);
8
+ }
9
+ function geoqRasterStyle() {
10
+ return {
11
+ version: 8,
12
+ glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
13
+ sources: {
14
+ "geoq-raster": {
15
+ type: "raster",
16
+ tiles: [
17
+ "https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}"
18
+ ],
19
+ tileSize: 256,
20
+ attribution: "\xA9 GeoQ \u667A\u56FE"
21
+ }
22
+ },
23
+ layers: [
24
+ {
25
+ id: "background",
26
+ type: "background",
27
+ paint: { "background-color": "#0a0f1e" }
28
+ },
29
+ {
30
+ id: "geoq-raster",
31
+ type: "raster",
32
+ source: "geoq-raster",
33
+ paint: {
34
+ "raster-opacity": 0.9,
35
+ "raster-saturation": -0.2,
36
+ "raster-contrast": 0.05
37
+ }
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ var osmRasterStyle = geoqRasterStyle;
43
+ function buildGraticule(step = 10) {
44
+ const features = [];
45
+ for (let lon = -180; lon <= 180; lon += step) {
46
+ features.push({
47
+ type: "Feature",
48
+ properties: { kind: "meridian" },
49
+ geometry: { type: "LineString", coordinates: [[lon, -85], [lon, 85]] }
50
+ });
51
+ }
52
+ for (let lat = -80; lat <= 80; lat += step) {
53
+ features.push({
54
+ type: "Feature",
55
+ properties: { kind: "parallel" },
56
+ geometry: { type: "LineString", coordinates: [[-180, lat], [180, lat]] }
57
+ });
58
+ }
59
+ return { type: "FeatureCollection", features };
60
+ }
61
+ function localBasemapStyle() {
62
+ return {
63
+ version: 8,
64
+ sources: {
65
+ graticule: { type: "geojson", data: buildGraticule() }
66
+ },
67
+ layers: [
68
+ { id: "bg", type: "background", paint: { "background-color": "#0a0f1e" } },
69
+ {
70
+ id: "graticule",
71
+ type: "line",
72
+ source: "graticule",
73
+ paint: { "line-color": "#1e293b", "line-width": 0.6, "line-opacity": 0.6 }
74
+ }
75
+ ]
76
+ };
77
+ }
78
+
79
+ export { WUHAN_CENTER, WUHAN_ZOOM, caoguoStyle, geoqRasterStyle, localBasemapStyle, osmRasterStyle };
80
+ //# sourceMappingURL=chunk-DENQSIV2.js.map
81
+ //# sourceMappingURL=chunk-DENQSIV2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.ts"],"names":[],"mappings":";;;AAYO,IAAM,YAAA,GAAiC,CAAC,QAAA,EAAU,OAAO;AACzD,IAAM,UAAA,GAAa;AAGnB,SAAS,WAAA,CAAY,QAAmB,aAAA,EAAmC;AAChF,EAAA,OAAO,WAAW,KAAK,CAAA;AACzB;AAQO,SAAS,eAAA,GAAsC;AACpD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,MAAA,EAAQ,6DAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,aAAA,EAAe;AAAA,QACb,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO;AAAA,UACL;AAAA,SACF;AAAA,QACA,QAAA,EAAU,GAAA;AAAA,QACV,WAAA,EAAa;AAAA;AACf,KACF;AAAA,IACA,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,EAAA,EAAI,YAAA;AAAA,QACJ,IAAA,EAAM,YAAA;AAAA,QACN,KAAA,EAAO,EAAE,kBAAA,EAAoB,SAAA;AAAU,OACzC;AAAA,MACA;AAAA,QACE,EAAA,EAAI,aAAA;AAAA,QACJ,IAAA,EAAM,QAAA;AAAA,QACN,MAAA,EAAQ,aAAA;AAAA,QACR,KAAA,EAAO;AAAA,UACL,gBAAA,EAAkB,GAAA;AAAA,UAClB,mBAAA,EAAqB,IAAA;AAAA,UACrB,iBAAA,EAAmB;AAAA;AACrB;AACF;AACF,GACF;AACF;AAGO,IAAM,cAAA,GAAiB;AAQ9B,SAAS,cAAA,CAAe,OAAO,EAAA,EAA+B;AAC5D,EAAA,MAAM,WAA8B,EAAC;AACrC,EAAA,KAAA,IAAS,GAAA,GAAM,IAAA,EAAM,GAAA,IAAO,GAAA,EAAK,OAAO,IAAA,EAAM;AAC5C,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,IAAA,EAAM,SAAA;AAAA,MACN,UAAA,EAAY,EAAE,IAAA,EAAM,UAAA,EAAW;AAAA,MAC/B,QAAA,EAAU,EAAE,IAAA,EAAM,YAAA,EAAc,aAAa,CAAC,CAAC,GAAA,EAAK,GAAG,CAAA,EAAG,CAAC,GAAA,EAAK,EAAE,CAAC,CAAA;AAAE,KACtE,CAAA;AAAA,EACH;AACA,EAAA,KAAA,IAAS,GAAA,GAAM,GAAA,EAAK,GAAA,IAAO,EAAA,EAAI,OAAO,IAAA,EAAM;AAC1C,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,IAAA,EAAM,SAAA;AAAA,MACN,UAAA,EAAY,EAAE,IAAA,EAAM,UAAA,EAAW;AAAA,MAC/B,QAAA,EAAU,EAAE,IAAA,EAAM,YAAA,EAAc,aAAa,CAAC,CAAC,IAAA,EAAM,GAAG,CAAA,EAAG,CAAC,GAAA,EAAK,GAAG,CAAC,CAAA;AAAE,KACxE,CAAA;AAAA,EACH;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,mBAAA,EAAqB,QAAA,EAAS;AAC/C;AAEO,SAAS,iBAAA,GAAwC;AACtD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,OAAA,EAAS;AAAA,MACP,WAAW,EAAE,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,gBAAe;AAAE,KACvD;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,EAAE,IAAI,IAAA,EAAM,IAAA,EAAM,cAAc,KAAA,EAAO,EAAE,kBAAA,EAAoB,SAAA,EAAU,EAAE;AAAA,MACzE;AAAA,QACE,EAAA,EAAI,WAAA;AAAA,QACJ,IAAA,EAAM,MAAA;AAAA,QACN,MAAA,EAAQ,WAAA;AAAA,QACR,OAAO,EAAE,YAAA,EAAc,WAAW,YAAA,EAAc,GAAA,EAAK,gBAAgB,GAAA;AAAI;AAC3E;AACF,GACF;AACF","file":"chunk-DENQSIV2.js","sourcesContent":["/**\n * 草果地图内置样式。\n *\n * - `caoguoStyle()`:官方矢量主题(caoguo-dark / caoguo-light),\n * 由 @caoguo/theme 生成,底层使用 MapLibre 免费 demo 矢量瓦片,无需 token。\n * - `geoqRasterStyle()` / `osmRasterStyle()`:国内可达的栅格兜底底图\n * (智图 GeoQ 公共服务,免 key,适配国内网络)。`osmRasterStyle` 保留为别名。\n */\n\nimport type { StyleSpecification } from 'maplibre-gl';\nimport { buildStyle, type ThemeName } from '@caoguo/theme';\n\nexport const WUHAN_CENTER: [number, number] = [114.3055, 30.5928];\nexport const WUHAN_ZOOM = 11;\n\n/** 草果官方矢量主题(默认暗色)。 */\nexport function caoguoStyle(theme: ThemeName = 'caoguo-dark'): StyleSpecification {\n return buildStyle(theme);\n}\n\n/**\n * 国内可达的栅格兜底底图(智图 GeoQ 公共服务,免 key)。\n * 使用 ChinaOnlineStreetPurplishBlue(蓝紫街道,贴合暗色演示主题)。\n * 替代原 OSM 兜底:OSM 在国内网络普遍不可达,导致部署在国内访问的\n * 演示中心底图空白。GeoQ 为 Esri 中国公共服务,长期稳定免 key。\n */\nexport function geoqRasterStyle(): StyleSpecification {\n return {\n version: 8,\n glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',\n sources: {\n 'geoq-raster': {\n type: 'raster',\n tiles: [\n 'https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}',\n ],\n tileSize: 256,\n attribution: '© GeoQ 智图',\n },\n },\n layers: [\n {\n id: 'background',\n type: 'background',\n paint: { 'background-color': '#0a0f1e' },\n },\n {\n id: 'geoq-raster',\n type: 'raster',\n source: 'geoq-raster',\n paint: {\n 'raster-opacity': 0.9,\n 'raster-saturation': -0.2,\n 'raster-contrast': 0.05,\n },\n },\n ],\n };\n}\n\n/** 兼容别名:国内可达栅格兜底底图。 */\nexport const osmRasterStyle = geoqRasterStyle;\n\n/**\n * 纯本地底图(零外部依赖,完全离线)。\n * 暗色背景 + 全球经纬网,无需任何在线瓦片 / 字体服务。\n * 用于:国内网络无法访问 OSM / GeoQ 等境外或受限瓦片源时,\n * 保证演示中心地图永远可渲染(业务图层为自有 GeoJSON,叠加其上即可见)。\n */\nfunction buildGraticule(step = 10): GeoJSON.FeatureCollection {\n const features: GeoJSON.Feature[] = [];\n for (let lon = -180; lon <= 180; lon += step) {\n features.push({\n type: 'Feature',\n properties: { kind: 'meridian' },\n geometry: { type: 'LineString', coordinates: [[lon, -85], [lon, 85]] },\n });\n }\n for (let lat = -80; lat <= 80; lat += step) {\n features.push({\n type: 'Feature',\n properties: { kind: 'parallel' },\n geometry: { type: 'LineString', coordinates: [[-180, lat], [180, lat]] },\n });\n }\n return { type: 'FeatureCollection', features };\n}\n\nexport function localBasemapStyle(): StyleSpecification {\n return {\n version: 8,\n sources: {\n graticule: { type: 'geojson', data: buildGraticule() },\n },\n layers: [\n { id: 'bg', type: 'background', paint: { 'background-color': '#0a0f1e' } },\n {\n id: 'graticule',\n type: 'line',\n source: 'graticule',\n paint: { 'line-color': '#1e293b', 'line-width': 0.6, 'line-opacity': 0.6 },\n },\n ],\n };\n}\n\n\n"]}
@@ -0,0 +1,210 @@
1
+ // src/shaders/glowGeometry.ts
2
+ function glowPasses(passes = 4, baseWidth = 3) {
3
+ if (passes < 1) passes = 1;
4
+ const out = [];
5
+ for (let i = 0; i < passes; i++) {
6
+ const t = passes === 1 ? 1 : 1 - i / (passes - 1);
7
+ out.push({
8
+ width: baseWidth * (1 + (1 - t) * (passes - 1) * 1.8),
9
+ opacity: 0.1 + 0.9 * t * t
10
+ });
11
+ }
12
+ return out;
13
+ }
14
+ function projectSimple(lng, lat) {
15
+ const x = lng / 180;
16
+ const y = Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)) / Math.PI;
17
+ return [x, Math.max(-1, Math.min(1, y / Math.PI))];
18
+ }
19
+ function buildGlowGeometry(lines, opts = {}) {
20
+ const passes = glowPasses(opts.passes ?? 4, opts.baseWidth ?? 3);
21
+ const stride = 6;
22
+ const verts = [];
23
+ const passRanges = [];
24
+ const renderGroups = [];
25
+ const groups = [];
26
+ for (const line of lines) {
27
+ const g = line.group ?? "default";
28
+ if (!groups.includes(g)) groups.push(g);
29
+ }
30
+ for (let p = 0; p < passes.length; p++) {
31
+ passes[p];
32
+ const start = verts.length / stride;
33
+ for (const group of groups) {
34
+ const groupStart = verts.length / stride;
35
+ const groupIndex = groups.indexOf(group);
36
+ for (const line of lines) {
37
+ if ((line.group ?? "default") !== group) continue;
38
+ const pts = line.coordinates.map(([lng, lat]) => projectSimple(lng, lat));
39
+ if (pts.length < 2) continue;
40
+ for (let i = 0; i < pts.length - 1; i++) {
41
+ const a = pts[i];
42
+ const b = pts[i + 1];
43
+ const dx = b[0] - a[0];
44
+ const dy = b[1] - a[1];
45
+ const quad = [
46
+ [a[0], a[1], dx, dy, 1, groupIndex],
47
+ [a[0], a[1], dx, dy, -1, groupIndex],
48
+ [b[0], b[1], dx, dy, 1, groupIndex],
49
+ [b[0], b[1], dx, dy, -1, groupIndex]
50
+ ];
51
+ const idx = [0, 1, 2, 2, 1, 3];
52
+ for (const k of idx) {
53
+ const v = quad[k];
54
+ verts.push(v[0], v[1], v[2], v[3], v[4], v[5]);
55
+ }
56
+ }
57
+ }
58
+ const count2 = verts.length / stride - groupStart;
59
+ if (count2 > 0) renderGroups.push({ passIndex: p, group, start: groupStart, count: count2 });
60
+ }
61
+ const count = verts.length / stride - start;
62
+ passRanges.push({ start, count });
63
+ }
64
+ return { vertices: new Float32Array(verts), passRanges, passes, stride, groups, renderGroups };
65
+ }
66
+
67
+ // src/shaders/CustomLineLayer.ts
68
+ var FALLBACK_COLOR = [0.6, 0.7, 0.9];
69
+ var DEFAULT_COLORS = {
70
+ pipe: [0.2, 0.85, 1],
71
+ // 青蓝:管线
72
+ road: [0.6, 0.7, 0.9],
73
+ // 灰蓝:路网
74
+ water: [0.25, 0.55, 0.95]
75
+ // 深蓝:水系
76
+ };
77
+ var VERT = `
78
+ attribute vec2 aPos;
79
+ attribute vec2 aDir;
80
+ attribute float aSide;
81
+ uniform mat4 uMatrix;
82
+ uniform float uWidth;
83
+ uniform vec2 uResolution;
84
+ void main() {
85
+ vec4 clip = uMatrix * vec4(aPos, 0.0, 1.0);
86
+ // \u6BB5\u65B9\u5411\u4E5F\u6295\u5F71\uFF0C\u6C42\u5C4F\u5E55\u7A7A\u95F4\u65B9\u5411
87
+ vec4 clipDir = uMatrix * vec4(aPos + aDir, 0.0, 1.0);
88
+ vec2 s0 = clip.xy / clip.w;
89
+ vec2 s1 = clipDir.xy / clipDir.w;
90
+ vec2 dir = normalize(s1 - s0 + vec2(1e-6));
91
+ vec2 normal = vec2(-dir.y, dir.x);
92
+ vec2 offset = normal * aSide * (uWidth / uResolution * 2.0);
93
+ gl_Position = vec4(s0 + offset * clip.w, clip.z, clip.w);
94
+ }
95
+ `;
96
+ var FRAG = `
97
+ precision mediump float;
98
+ uniform vec3 uColor;
99
+ uniform float uOpacity;
100
+ void main() {
101
+ gl_FragColor = vec4(uColor, uOpacity);
102
+ }
103
+ `;
104
+ function compile(gl, type, src) {
105
+ const sh = gl.createShader(type);
106
+ gl.shaderSource(sh, src);
107
+ gl.compileShader(sh);
108
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
109
+ console.error("[CustomLineLayer] shader \u7F16\u8BD1\u5931\u8D25:", gl.getShaderInfoLog(sh));
110
+ }
111
+ return sh;
112
+ }
113
+ var CustomLineLayer = class {
114
+ constructor(opts) {
115
+ this.type = "custom";
116
+ this.renderingMode = "2d";
117
+ this.id = opts.id ?? "caoguo-glow-line";
118
+ this.lines = opts.lines;
119
+ this.colors = { ...DEFAULT_COLORS, ...opts.colors };
120
+ this.baseWidth = opts.baseWidth ?? 3;
121
+ this.geometry = buildGlowGeometry(this.lines, { passes: opts.passes ?? 4, baseWidth: this.baseWidth });
122
+ for (const g of this.geometry.groups) {
123
+ if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;
124
+ }
125
+ }
126
+ onAdd(map, gl) {
127
+ this.map = map;
128
+ this.gl = gl;
129
+ const vs = compile(gl, gl.VERTEX_SHADER, VERT);
130
+ const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);
131
+ const prog = gl.createProgram();
132
+ gl.attachShader(prog, vs);
133
+ gl.attachShader(prog, fs);
134
+ gl.linkProgram(prog);
135
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
136
+ console.error("[CustomLineLayer] program \u94FE\u63A5\u5931\u8D25:", gl.getProgramInfoLog(prog));
137
+ }
138
+ this.program = prog;
139
+ const buf = gl.createBuffer();
140
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
141
+ gl.bufferData(gl.ARRAY_BUFFER, this.geometry.vertices, gl.STATIC_DRAW);
142
+ this.buffer = buf;
143
+ }
144
+ render(gl, matrix) {
145
+ if (!this.program || !this.buffer) return;
146
+ gl.useProgram(this.program);
147
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
148
+ const stride = this.geometry.stride * 4;
149
+ const aPos = gl.getAttribLocation(this.program, "aPos");
150
+ const aDir = gl.getAttribLocation(this.program, "aDir");
151
+ const aSide = gl.getAttribLocation(this.program, "aSide");
152
+ const aGroup = gl.getAttribLocation(this.program, "aGroup");
153
+ gl.enableVertexAttribArray(aPos);
154
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);
155
+ gl.enableVertexAttribArray(aDir);
156
+ gl.vertexAttribPointer(aDir, 2, gl.FLOAT, false, stride, 8);
157
+ gl.enableVertexAttribArray(aSide);
158
+ gl.vertexAttribPointer(aSide, 1, gl.FLOAT, false, stride, 16);
159
+ if (aGroup >= 0) {
160
+ gl.enableVertexAttribArray(aGroup);
161
+ gl.vertexAttribPointer(aGroup, 1, gl.FLOAT, false, stride, 20);
162
+ }
163
+ const uMatrix = gl.getUniformLocation(this.program, "uMatrix");
164
+ gl.uniformMatrix4fv(uMatrix, false, new Float32Array(matrix));
165
+ const uWidth = gl.getUniformLocation(this.program, "uWidth");
166
+ const uColor = gl.getUniformLocation(this.program, "uColor");
167
+ const uOpacity = gl.getUniformLocation(this.program, "uOpacity");
168
+ const uRes = gl.getUniformLocation(this.program, "uResolution");
169
+ const canvas = this.map?.getCanvas();
170
+ gl.uniform2f(uRes, canvas?.width ?? 1024, canvas?.height ?? 768);
171
+ gl.enable(gl.BLEND);
172
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
173
+ for (const rg of this.geometry.renderGroups) {
174
+ const pass = this.geometry.passes[rg.passIndex];
175
+ const color = this.colors[rg.group] ?? FALLBACK_COLOR;
176
+ gl.uniform1f(uWidth, pass.width);
177
+ gl.uniform3f(uColor, color[0], color[1], color[2]);
178
+ gl.uniform1f(uOpacity, pass.opacity);
179
+ gl.drawArrays(gl.TRIANGLES, rg.start, rg.count);
180
+ }
181
+ }
182
+ /**
183
+ * 动态更新线集合(高亮选中、切换数据等无需重建图层)。
184
+ * 浏览器环境会刷新 GPU 缓冲;纯逻辑/测试环境仅更新几何与数据。
185
+ */
186
+ setLines(lines) {
187
+ this.lines = lines;
188
+ this.geometry = buildGlowGeometry(this.lines, {
189
+ passes: this.geometry.passes.length,
190
+ baseWidth: this.baseWidth
191
+ });
192
+ for (const g of this.geometry.groups) {
193
+ if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;
194
+ }
195
+ if (this.buffer && this.gl) {
196
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
197
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.geometry.vertices, this.gl.STATIC_DRAW);
198
+ }
199
+ }
200
+ onRemove(_map, gl) {
201
+ if (this.buffer) gl.deleteBuffer(this.buffer);
202
+ if (this.program) gl.deleteProgram(this.program);
203
+ this.buffer = void 0;
204
+ this.program = void 0;
205
+ }
206
+ };
207
+
208
+ export { CustomLineLayer, buildGlowGeometry, glowPasses, projectSimple };
209
+ //# sourceMappingURL=chunk-G7VY42ZG.js.map
210
+ //# sourceMappingURL=chunk-G7VY42ZG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shaders/glowGeometry.ts","../src/shaders/CustomLineLayer.ts"],"names":["count"],"mappings":"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","file":"chunk-G7VY42ZG.js","sourcesContent":["/**\n * 辉光线几何构建(T6 / F-1.3 纯逻辑部分)。\n *\n * 把 GeoJSON LineString 集合转换为「多遍描边」所需的三角形带几何。\n * 渲染层(CustomLineLayer)据此在 WebGL 中以屏幕像素宽度的三角面叠加绘制,\n * 每遍一个宽度档 + 透明度档,加法混合形成管线/路网/水系的辉光效果。\n *\n * 设计:本模块**不依赖 WebGL / maplibre**,纯函数可在 Node 单测。\n * 坐标投影使用简化 Web Mercator(经度→x、纬度→y 的归一化世界坐标),\n * 真实渲染时由 CustomLayer 的 matrix 变换到屏幕;线宽在屏幕空间计算。\n */\n\nexport interface GlowLine {\n /** 线坐标 [lng, lat][] */\n coordinates: [number, number][];\n /** 该线所属分组(如 'pipe' | 'road' | 'water'),用于按组分色。缺省归入 'default'。 */\n group?: string;\n}\n\nexport interface GlowPass {\n /** 该遍屏幕像素宽度 */\n width: number;\n /** 该遍不透明度(核心线高、外晕低) */\n opacity: number;\n}\n\nexport interface GlowGeometry {\n /**\n * 扁平化顶点缓冲。每个顶点 6 个 float:\n * [worldX, worldY, dirX, dirY, side, groupIndex]\n * - worldX/worldY:该端点的归一化世界坐标\n * - dirX/dirY:所在线段的世界空间方向(用于屏幕空间法线)\n * - side:±1,左/右扩展符号\n * - groupIndex:该顶点所属分组在 `groups` 中的下标(用于按组分色)\n */\n vertices: Float32Array;\n /** 每遍在 vertices 中的顶点区间 [start, count](按顶点数,非 float 数) */\n passRanges: { start: number; count: number }[];\n /** 每遍参数(从最外晕到核心,由宽到窄) */\n passes: GlowPass[];\n /** 顶点属性跨距(float 数) */\n stride: number;\n /** 参与渲染的去重分组名(与上层 colorMap 对应,下标即 groupIndex) */\n groups: string[];\n /** 逐遍、逐分组的连续顶点区间,供按组分色绘制 */\n renderGroups: { passIndex: number; group: string; start: number; count: number }[];\n}\n\n/**\n * 生成辉光多遍档位。\n * @param passes 遍数(默认 4:3 层外晕 + 1 核心)\n * @param baseWidth 核心线像素宽度(默认 3)\n */\nexport function glowPasses(passes = 4, baseWidth = 3): GlowPass[] {\n if (passes < 1) passes = 1;\n const out: GlowPass[] = [];\n // 从最外晕(宽、淡)到核心(窄、浓)排列\n for (let i = 0; i < passes; i++) {\n const t = passes === 1 ? 1 : 1 - i / (passes - 1); // 1(核心)..0(外晕)\n out.push({\n width: baseWidth * (1 + (1 - t) * (passes - 1) * 1.8),\n opacity: 0.1 + 0.9 * t * t,\n });\n }\n return out;\n}\n\n/**\n * 简化 Web Mercator 归一化(经度→x∈[-1,1],纬度→y 经墨卡托压缩)。\n * 仅用于几何构建与单测,真实投影在渲染层用 map 的 matrix。\n */\nexport function projectSimple(lng: number, lat: number): [number, number] {\n const x = lng / 180;\n const y = Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360)) / Math.PI;\n // 纬度墨卡托范围约 [-π/2, π/2],归一化到 [-1,1] 方便观察\n return [x, Math.max(-1, Math.min(1, y / Math.PI))];\n}\n\n/**\n * 构建辉光几何:对每条线、每遍、每段生成三角面(2 三角形 = 6 顶点)。\n */\nexport function buildGlowGeometry(\n lines: GlowLine[],\n opts: { passes?: number; baseWidth?: number } = {}\n): GlowGeometry {\n const passes = glowPasses(opts.passes ?? 4, opts.baseWidth ?? 3);\n const stride = 6; // worldX, worldY, dirX, dirY, side, groupIndex\n const verts: number[] = [];\n const passRanges: { start: number; count: number }[] = [];\n const renderGroups: { passIndex: number; group: string; start: number; count: number }[] = [];\n\n // 分组名按首次出现顺序去重,下标即顶点携带的 groupIndex。\n const groups: string[] = [];\n for (const line of lines) {\n const g = line.group ?? 'default';\n if (!groups.includes(g)) groups.push(g);\n }\n\n for (let p = 0; p < passes.length; p++) {\n const pass = passes[p];\n const start = verts.length / stride;\n // 每遍内部按分组聚集成连续区间,便于按组取色绘制。\n for (const group of groups) {\n const groupStart = verts.length / stride;\n const groupIndex = groups.indexOf(group);\n for (const line of lines) {\n if ((line.group ?? 'default') !== group) continue;\n const pts = line.coordinates.map(([lng, lat]) => projectSimple(lng, lat));\n if (pts.length < 2) continue;\n for (let i = 0; i < pts.length - 1; i++) {\n const a = pts[i];\n const b = pts[i + 1];\n // 段方向(世界空间,未归一化,shader 内会归一化)\n const dx = b[0] - a[0];\n const dy = b[1] - a[1];\n // 每个端点左右各一个顶点,组成 quad:aL,aR,bL,bR → (aL,aR,bL),(bL,aR,bR)\n const quad: Array<[number, number, number, number, number, number]> = [\n [a[0], a[1], dx, dy, 1, groupIndex],\n [a[0], a[1], dx, dy, -1, groupIndex],\n [b[0], b[1], dx, dy, 1, groupIndex],\n [b[0], b[1], dx, dy, -1, groupIndex],\n ];\n // 两个三角形:0,1,2 与 2,1,3\n const idx = [0, 1, 2, 2, 1, 3];\n for (const k of idx) {\n const v = quad[k];\n verts.push(v[0], v[1], v[2], v[3], v[4], v[5]);\n }\n }\n }\n const count = verts.length / stride - groupStart;\n if (count > 0) renderGroups.push({ passIndex: p, group, start: groupStart, count });\n }\n const count = verts.length / stride - start;\n passRanges.push({ start, count });\n }\n\n return { vertices: new Float32Array(verts), passRanges, passes, stride, groups, renderGroups };\n}\n","/**\n * 辉光管线 Custom Layer(T6 / F-1.3 WebGL 渲染层)。\n *\n * 实现 MapLibre v4 `CustomLayerInterface`:在地图之上叠加绘制 GeoJSON 线,\n * 用多遍(glowPasses)三角面描边形成管线/路网/水系的辉光效果。\n *\n * 设计:几何构建(投影/三角带)在 `./glowGeometry` 纯函数中完成(可单测);\n * 本文件仅负责 WebGL 状态机与绘制,仅在浏览器执行。\n * 线宽在屏幕空间按像素计算(标准 Mapbox line shader 思路),因此可随缩放保持视觉宽度。\n * 通过 `Map.addGlowLayer` 注入,调用方无需感知 WebGL 细节。\n *\n * 依赖 MapLibre 注入的 `matrix`(map 的 projectionMatrix * modelViewMatrix)。\n */\n\nimport { buildGlowGeometry, type GlowLine } from './glowGeometry';\n\n/** MapLibre v4 CustomLayerInterface 最小子集(避免强类型耦合) */\nexport interface CustomLayerInterface {\n id: string;\n type: 'custom';\n renderingMode?: '2d' | '3d';\n onAdd?(map: unknown, gl: WebGLRenderingContext): void;\n render(gl: WebGLRenderingContext, matrix: number[]): void;\n onRemove?(map: unknown, gl: WebGLRenderingContext): void;\n prerender?(gl: WebGLRenderingContext, matrix: number[]): void;\n}\n\nexport interface GlowLayerOptions {\n /** 图层 id */\n id?: string;\n /** 线分组颜色映射(group -> [r,g,b] 0..1) */\n colors?: Record<string, [number, number, number]>;\n /** 基础线宽(像素,核心线) */\n baseWidth?: number;\n /** 辉光遍数 */\n passes?: number;\n /** 线集合 */\n lines: GlowLine[];\n}\n\n/** 取分组色的兜底色(灰蓝),避免未配置分组时渲染透明/黑。 */\nconst FALLBACK_COLOR: [number, number, number] = [0.6, 0.7, 0.9];\n\nconst DEFAULT_COLORS: Record<string, [number, number, number]> = {\n pipe: [0.2, 0.85, 1.0], // 青蓝:管线\n road: [0.6, 0.7, 0.9], // 灰蓝:路网\n water: [0.25, 0.55, 0.95], // 深蓝:水系\n};\n\n// 顶点属性:aPos(世界xy)、aDir(段方向xy)、aSide(±1)\nconst VERT = `\nattribute vec2 aPos;\nattribute vec2 aDir;\nattribute float aSide;\nuniform mat4 uMatrix;\nuniform float uWidth;\nuniform vec2 uResolution;\nvoid main() {\n vec4 clip = uMatrix * vec4(aPos, 0.0, 1.0);\n // 段方向也投影,求屏幕空间方向\n vec4 clipDir = uMatrix * vec4(aPos + aDir, 0.0, 1.0);\n vec2 s0 = clip.xy / clip.w;\n vec2 s1 = clipDir.xy / clipDir.w;\n vec2 dir = normalize(s1 - s0 + vec2(1e-6));\n vec2 normal = vec2(-dir.y, dir.x);\n vec2 offset = normal * aSide * (uWidth / uResolution * 2.0);\n gl_Position = vec4(s0 + offset * clip.w, clip.z, clip.w);\n}\n`;\n\nconst FRAG = `\nprecision mediump float;\nuniform vec3 uColor;\nuniform float uOpacity;\nvoid main() {\n gl_FragColor = vec4(uColor, uOpacity);\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string): WebGLShader {\n const sh = gl.createShader(type)!;\n gl.shaderSource(sh, src);\n gl.compileShader(sh);\n if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {\n console.error('[CustomLineLayer] shader 编译失败:', gl.getShaderInfoLog(sh));\n }\n return sh;\n}\n\nexport class CustomLineLayer implements CustomLayerInterface {\n id: string;\n type = 'custom' as const;\n renderingMode: '2d' = '2d';\n\n private lines: GlowLine[];\n private colors: Record<string, [number, number, number]>;\n private baseWidth: number;\n\n private program?: WebGLProgram;\n private buffer?: WebGLBuffer;\n private geometry: ReturnType<typeof buildGlowGeometry>;\n private map?: { getCanvas: () => HTMLCanvasElement };\n private gl?: WebGLRenderingContext;\n\n constructor(opts: GlowLayerOptions) {\n this.id = opts.id ?? 'caoguo-glow-line';\n this.lines = opts.lines;\n this.colors = { ...DEFAULT_COLORS, ...opts.colors };\n this.baseWidth = opts.baseWidth ?? 3;\n this.geometry = buildGlowGeometry(this.lines, { passes: opts.passes ?? 4, baseWidth: this.baseWidth });\n // 为新分组补充兜底色,保证未显式配置的分组也能渲染。\n for (const g of this.geometry.groups) {\n if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;\n }\n }\n\n onAdd(map: unknown, gl: WebGLRenderingContext): void {\n this.map = map as { getCanvas: () => HTMLCanvasElement };\n this.gl = gl;\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n console.error('[CustomLineLayer] program 链接失败:', gl.getProgramInfoLog(prog));\n }\n this.program = prog;\n\n const buf = gl.createBuffer()!;\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, this.geometry.vertices, gl.STATIC_DRAW);\n this.buffer = buf;\n }\n\n render(gl: WebGLRenderingContext, matrix: number[]): void {\n if (!this.program || !this.buffer) return;\n gl.useProgram(this.program);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);\n\n const stride = this.geometry.stride * 4; // float -> byte\n const aPos = gl.getAttribLocation(this.program, 'aPos');\n const aDir = gl.getAttribLocation(this.program, 'aDir');\n const aSide = gl.getAttribLocation(this.program, 'aSide');\n const aGroup = gl.getAttribLocation(this.program, 'aGroup');\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);\n gl.enableVertexAttribArray(aDir);\n gl.vertexAttribPointer(aDir, 2, gl.FLOAT, false, stride, 8);\n gl.enableVertexAttribArray(aSide);\n gl.vertexAttribPointer(aSide, 1, gl.FLOAT, false, stride, 16);\n if (aGroup >= 0) {\n gl.enableVertexAttribArray(aGroup);\n gl.vertexAttribPointer(aGroup, 1, gl.FLOAT, false, stride, 20);\n }\n\n const uMatrix = gl.getUniformLocation(this.program, 'uMatrix');\n gl.uniformMatrix4fv(uMatrix, false, new Float32Array(matrix));\n const uWidth = gl.getUniformLocation(this.program, 'uWidth');\n const uColor = gl.getUniformLocation(this.program, 'uColor');\n const uOpacity = gl.getUniformLocation(this.program, 'uOpacity');\n const uRes = gl.getUniformLocation(this.program, 'uResolution');\n const canvas = this.map?.getCanvas();\n gl.uniform2f(uRes, canvas?.width ?? 1024, canvas?.height ?? 768);\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // 加法混合形成辉光\n\n // 逐遍、逐分组取色绘制:实现「按分组多色」,不同业务线(管/路/水)各自着色。\n for (const rg of this.geometry.renderGroups) {\n const pass = this.geometry.passes[rg.passIndex];\n const color = this.colors[rg.group] ?? FALLBACK_COLOR;\n gl.uniform1f(uWidth, pass.width);\n gl.uniform3f(uColor, color[0], color[1], color[2]);\n gl.uniform1f(uOpacity, pass.opacity);\n gl.drawArrays(gl.TRIANGLES, rg.start, rg.count);\n }\n }\n\n /**\n * 动态更新线集合(高亮选中、切换数据等无需重建图层)。\n * 浏览器环境会刷新 GPU 缓冲;纯逻辑/测试环境仅更新几何与数据。\n */\n setLines(lines: GlowLine[]): void {\n this.lines = lines;\n this.geometry = buildGlowGeometry(this.lines, {\n passes: this.geometry.passes.length,\n baseWidth: this.baseWidth,\n });\n // 为新分组补充兜底色,保证未显式配置的分组也能渲染。\n for (const g of this.geometry.groups) {\n if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;\n }\n // 图层已挂载(this.gl 在 onAdd 时缓存)则刷新 GPU 缓冲。\n if (this.buffer && this.gl) {\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);\n this.gl.bufferData(this.gl.ARRAY_BUFFER, this.geometry.vertices, this.gl.STATIC_DRAW);\n }\n }\n\n onRemove(_map: unknown, gl: WebGLRenderingContext): void {\n if (this.buffer) gl.deleteBuffer(this.buffer);\n if (this.program) gl.deleteProgram(this.program);\n this.buffer = undefined;\n this.program = undefined;\n }\n}\n"]}
@@ -0,0 +1,214 @@
1
+ import { caoguoStyle } from './chunk-DENQSIV2.js';
2
+
3
+ // src/controls/ScaleControl.ts
4
+ var EARTH_RADIUS = 6378137;
5
+ function computeScaleBar(latitude, zoom, opts = {}) {
6
+ const dpiScale = opts.dpiScale ?? 1;
7
+ const maxWidth = opts.maxWidth ?? 100;
8
+ const tileSize = opts.tileSize ?? 512;
9
+ const latRad = latitude * Math.PI / 180;
10
+ const metersPerPixel = Math.cos(latRad) * 2 * Math.PI * EARTH_RADIUS / (tileSize * Math.pow(2, zoom) * dpiScale);
11
+ let meters = metersPerPixel * maxWidth;
12
+ const pow = Math.pow(10, Math.floor(Math.log10(meters)));
13
+ const base = meters / pow;
14
+ const step = base >= 5 ? 5 : base >= 2 ? 2 : 1;
15
+ meters = step * pow;
16
+ const pixels = meters / metersPerPixel;
17
+ const label = meters >= 1e3 ? `${(meters / 1e3).toLocaleString()} km` : `${Math.round(meters)} m`;
18
+ return { meters, pixels, label };
19
+ }
20
+ var ScaleControl = class {
21
+ constructor(map, options = {}) {
22
+ this.onMove = (ev) => this.handleMove(ev);
23
+ this.onZoom = () => this.update();
24
+ this.map = map;
25
+ this.opts = {
26
+ showCoordinate: options.showCoordinate ?? true,
27
+ maxWidth: options.maxWidth ?? 100
28
+ };
29
+ this.el = options.container ?? document.createElement("div");
30
+ this.el.className = "caoguo-scale-control";
31
+ 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;";
32
+ this.barEl = document.createElement("div");
33
+ this.barEl.style.cssText = "height:6px;border:1px solid #cfe;border-top:none;margin-bottom:2px;";
34
+ this.labelEl = document.createElement("div");
35
+ if (this.opts.showCoordinate) {
36
+ this.coordEl = document.createElement("div");
37
+ this.coordEl.style.cssText = "opacity:.85;margin-top:2px;";
38
+ this.el.append(this.barEl, this.labelEl, this.coordEl);
39
+ } else {
40
+ this.el.append(this.barEl, this.labelEl);
41
+ }
42
+ map.on("zoom", this.onZoom);
43
+ map.on("move", this.onZoom);
44
+ const canvas = map.getCanvas?.();
45
+ canvas?.addEventListener("mousemove", this.onMove);
46
+ }
47
+ /** 把控件挂到地图容器(无预设容器时调用) */
48
+ addTo(container) {
49
+ if (!this.el.parentElement) container.appendChild(this.el);
50
+ this.update();
51
+ return this;
52
+ }
53
+ /** 计算并刷新显示 */
54
+ update() {
55
+ const center = this.map.getCenter();
56
+ const bar = computeScaleBar(center.lat, this.map.getZoom(), { maxWidth: this.opts.maxWidth });
57
+ this.barEl.style.width = `${Math.round(bar.pixels)}px`;
58
+ this.labelEl.textContent = bar.label;
59
+ }
60
+ handleMove(ev) {
61
+ if (!this.coordEl) return;
62
+ const e = ev;
63
+ const ll = e.lngLat;
64
+ if (!ll) return;
65
+ this.coordEl.textContent = `${ll.lng.toFixed(5)}, ${ll.lat.toFixed(5)}`;
66
+ }
67
+ /** 移除控件与事件监听 */
68
+ remove() {
69
+ this.map.off("zoom", this.onZoom);
70
+ this.map.off("move", this.onZoom);
71
+ const canvas = this.map.getCanvas?.();
72
+ canvas?.removeEventListener("mousemove", this.onMove);
73
+ this.el.remove();
74
+ }
75
+ };
76
+
77
+ // src/controls/ThemeSwitcher.ts
78
+ function oppositeTheme(theme) {
79
+ return theme === "caoguo-dark" ? "caoguo-light" : "caoguo-dark";
80
+ }
81
+ function themeFromStyle(style, fallback = "caoguo-dark") {
82
+ if (!style) return fallback;
83
+ const name = typeof style === "string" ? style : style.name;
84
+ if (name === "caoguo-dark" || name === "caoguo-light") return name;
85
+ return fallback;
86
+ }
87
+ var ThemeSwitcher = class {
88
+ constructor(map, options = {}) {
89
+ this.map = map;
90
+ this.current = options.initial ?? themeFromStyle(map.getStyle?.(), "caoguo-dark");
91
+ this.el = options.container ?? document.createElement("div");
92
+ this.el.className = "caoguo-theme-switcher";
93
+ this.el.style.cssText = "position:absolute;right:10px;top:10px;z-index:3;";
94
+ this.btn = document.createElement("button");
95
+ this.btn.type = "button";
96
+ this.applyLabel();
97
+ 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;";
98
+ this.btn.addEventListener("click", () => this.toggle());
99
+ this.el.append(this.btn);
100
+ }
101
+ applyLabel() {
102
+ this.btn.textContent = this.current === "caoguo-dark" ? "\u{1F319} \u6697\u8272" : "\u2600 \u4EAE\u8272";
103
+ }
104
+ /** 当前主题 */
105
+ getTheme() {
106
+ return this.current;
107
+ }
108
+ /** 切换到指定主题(保留视图,diff 模式避免闪烁) */
109
+ setTheme(theme) {
110
+ if (theme === this.current) return;
111
+ this.current = theme;
112
+ this.applyLabel();
113
+ this.map.setStyle(caoguoStyle(theme), { diff: true });
114
+ }
115
+ /** 在明暗之间切换 */
116
+ toggle() {
117
+ this.setTheme(oppositeTheme(this.current));
118
+ }
119
+ /** 挂到容器 */
120
+ addTo(container) {
121
+ if (!this.el.parentElement) container.appendChild(this.el);
122
+ return this;
123
+ }
124
+ /** 移除 */
125
+ remove() {
126
+ this.btn.removeEventListener("click", () => this.toggle());
127
+ this.el.remove();
128
+ }
129
+ };
130
+
131
+ // src/controls/LegendControl.ts
132
+ function escapeHtml(s) {
133
+ return s.replace(
134
+ /[&<>"']/g,
135
+ (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : c === '"' ? "&quot;" : "&#39;"
136
+ );
137
+ }
138
+ function renderLegendHtml(items, title) {
139
+ const titleHtml = title ? `<div class="cg-legend-title">${escapeHtml(title)}</div>` : "";
140
+ const itemsHtml = items.map((it) => {
141
+ const swatch = it.shape === "line" ? `<span class="cg-legend-swatch cg-legend-swatch--line" style="background:${it.color}"></span>` : `<span class="cg-legend-swatch" style="background:${it.color}"></span>`;
142
+ return `<div class="cg-legend-item">${swatch}<span>${escapeHtml(it.label)}</span></div>`;
143
+ }).join("");
144
+ return `<div class="cg-legend-body">${titleHtml}${itemsHtml}</div>`;
145
+ }
146
+ var LegendControl = class {
147
+ constructor(options) {
148
+ this.opts = { title: options.title ?? "", items: options.items };
149
+ this.el = options.container ?? document.createElement("div");
150
+ this.el.className = "caoguo-legend-control";
151
+ this.el.style.cssText = "position:absolute;right:10px;top:10px;padding:8px 10px;background:rgba(10,15,30,.72);color:#cfe;border-radius:6px;font:12px/1.5 system-ui,sans-serif;z-index:2;max-width:220px;";
152
+ this.bodyEl = document.createElement("div");
153
+ this.el.appendChild(this.bodyEl);
154
+ this.render();
155
+ }
156
+ render() {
157
+ this.bodyEl.innerHTML = renderLegendHtml(this.opts.items, this.opts.title || void 0);
158
+ }
159
+ /** 运行时更新图例项(随图层切换重新渲染) */
160
+ setItems(items, title) {
161
+ if (title !== void 0) this.opts.title = title;
162
+ this.opts.items = items;
163
+ this.render();
164
+ }
165
+ /** 挂载到容器 */
166
+ addTo(container) {
167
+ if (!this.el.parentElement) container.appendChild(this.el);
168
+ return this;
169
+ }
170
+ remove() {
171
+ this.el.remove();
172
+ }
173
+ };
174
+
175
+ // src/controls/ExportControl.ts
176
+ function triggerDownload(dataUrl, filename) {
177
+ const a = document.createElement("a");
178
+ a.href = dataUrl;
179
+ a.download = filename.endsWith(".png") ? filename : `${filename}.png`;
180
+ document.body.appendChild(a);
181
+ a.click();
182
+ a.remove();
183
+ }
184
+ var ExportControl = class {
185
+ constructor(map, options = {}) {
186
+ this.onClick = () => this.export();
187
+ this.map = map;
188
+ this.filename = options.filename ?? "caoguo-map";
189
+ this.el = options.container ?? document.createElement("button");
190
+ this.el.className = "caoguo-export-control";
191
+ this.el.textContent = options.buttonText ?? "\u5BFC\u51FA PNG";
192
+ this.el.style.cssText = "position:absolute;right:10px;top:10px;padding:5px 10px;background:rgba(10,15,30,.72);color:#cfe;border:1px solid #2a3550;border-radius:4px;font:12px system-ui,sans-serif;cursor:pointer;z-index:3;";
193
+ this.el.addEventListener("click", this.onClick);
194
+ }
195
+ /** 导出当前地图视图为 PNG 并下载 */
196
+ export() {
197
+ const canvas = this.map.getCanvas();
198
+ const url = canvas.toDataURL("image/png");
199
+ triggerDownload(url, this.filename);
200
+ }
201
+ /** 挂载到容器 */
202
+ addTo(container) {
203
+ if (!this.el.parentElement) container.appendChild(this.el);
204
+ return this;
205
+ }
206
+ remove() {
207
+ this.el.removeEventListener("click", this.onClick);
208
+ this.el.remove();
209
+ }
210
+ };
211
+
212
+ export { ExportControl, LegendControl, ScaleControl, ThemeSwitcher, computeScaleBar, oppositeTheme, renderLegendHtml, themeFromStyle, triggerDownload };
213
+ //# sourceMappingURL=chunk-MLLIAWVI.js.map
214
+ //# sourceMappingURL=chunk-MLLIAWVI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/controls/ScaleControl.ts","../src/controls/ThemeSwitcher.ts","../src/controls/LegendControl.ts","../src/controls/ExportControl.ts"],"names":[],"mappings":"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","file":"chunk-MLLIAWVI.js","sourcesContent":["/**\n * 比例尺 + 坐标显示控件(T8 / F-1.8)。\n *\n * - 比例尺:基于 Web Mercator 在给定纬度下的地面分辨率(m/像素),\n * 自动选择「米 / 公里」并吸附到 1/2/5 的整数档(标准地图比例尺行为)。\n * - 坐标:鼠标在图上移动时,实时显示当前光标处的经纬坐标(WGS84 渲染基准)。\n *\n * 设计为「纯函数 core + 薄 DOM 绑定」:比例计算 `computeScaleBar` 不依赖浏览器,\n * 可独立单测;`ScaleControl` 类负责把结果渲染进容器并监听地图事件。\n */\n\nexport type LengthUnit = 'metric';\n\nexport interface ScaleBar {\n /** 比例尺条代表的真实距离(米) */\n meters: number;\n /** 比例尺条在屏幕上的像素宽度 */\n pixels: number;\n /** 展示文案,如 \"500 m\" / \"2 km\" */\n label: string;\n}\n\nconst EARTH_RADIUS = 6378137; // WGS84 赤道半径(米)\n\n/**\n * 计算比例尺。\n * @param latitude 当前视图中心纬度(度)\n * @param zoom MapLibre zoom(Web Mercator)\n * @param dpiScale 设备像素比(默认 1;Retina 设为 window.devicePixelRatio)\n * @param maxWidth 比例尺条最大像素宽度(默认 100)\n * @param tileSize 瓦片像素尺寸(默认 512,MapLibre v4 默认)\n */\nexport function computeScaleBar(\n latitude: number,\n zoom: number,\n opts: { dpiScale?: number; maxWidth?: number; tileSize?: number } = {}\n): ScaleBar {\n const dpiScale = opts.dpiScale ?? 1;\n const maxWidth = opts.maxWidth ?? 100;\n const tileSize = opts.tileSize ?? 512;\n\n // 每像素的地面距离(米/像素),在给定纬度下\n const latRad = (latitude * Math.PI) / 180;\n const metersPerPixel =\n (Math.cos(latRad) * 2 * Math.PI * EARTH_RADIUS) /\n (tileSize * Math.pow(2, zoom) * dpiScale);\n\n // 最大像素宽度对应的真实距离\n let meters = metersPerPixel * maxWidth;\n\n // 吸附到 1/2/5 档\n const pow = Math.pow(10, Math.floor(Math.log10(meters)));\n const base = meters / pow; // 1..10\n const step = base >= 5 ? 5 : base >= 2 ? 2 : 1;\n meters = step * pow;\n\n const pixels = meters / metersPerPixel;\n const label = meters >= 1000 ? `${(meters / 1000).toLocaleString()} km` : `${Math.round(meters)} m`;\n\n return { meters, pixels, label };\n}\n\nexport interface ScaleControlOptions {\n /** 挂载容器(可选;不传则在地图容器内自动创建) */\n container?: HTMLElement;\n /** 是否显示实时坐标(默认 true) */\n showCoordinate?: boolean;\n /** 最大比例尺像素宽度 */\n maxWidth?: number;\n}\n\n/**\n * 比例尺控件。需传入 maplibre Map 实例(直接用 maplibre-gl 的 Map)。\n */\nexport class ScaleControl {\n private el: HTMLElement;\n private barEl: HTMLElement;\n private labelEl: HTMLElement;\n private coordEl?: HTMLElement;\n private map: { getZoom: () => number; getCenter: () => { lat: number }; on: (e: string, h: (ev: unknown) => void) => void; off: (e: string, h: (ev: unknown) => void) => void; getCanvas?: () => { addEventListener: (t: string, h: (ev: unknown) => void) => void; removeEventListener: (t: string, h: (ev: unknown) => void) => void } };\n private opts: Required<Omit<ScaleControlOptions, 'container'>>;\n private onMove = (ev: unknown) => this.handleMove(ev);\n private onZoom = () => this.update();\n\n constructor(\n map: ScaleControl['map'],\n options: ScaleControlOptions = {}\n ) {\n this.map = map;\n this.opts = {\n showCoordinate: options.showCoordinate ?? true,\n maxWidth: options.maxWidth ?? 100,\n };\n this.el = options.container ?? document.createElement('div');\n this.el.className = 'caoguo-scale-control';\n this.el.style.cssText =\n '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;';\n\n this.barEl = document.createElement('div');\n this.barEl.style.cssText =\n 'height:6px;border:1px solid #cfe;border-top:none;margin-bottom:2px;';\n this.labelEl = document.createElement('div');\n if (this.opts.showCoordinate) {\n this.coordEl = document.createElement('div');\n this.coordEl.style.cssText = 'opacity:.85;margin-top:2px;';\n this.el.append(this.barEl, this.labelEl, this.coordEl);\n } else {\n this.el.append(this.barEl, this.labelEl);\n }\n\n map.on('zoom', this.onZoom);\n map.on('move', this.onZoom);\n const canvas = (map as any).getCanvas?.();\n canvas?.addEventListener('mousemove', this.onMove as never);\n }\n\n /** 把控件挂到地图容器(无预设容器时调用) */\n addTo(container: HTMLElement): this {\n if (!this.el.parentElement) container.appendChild(this.el);\n this.update();\n return this;\n }\n\n /** 计算并刷新显示 */\n update(): void {\n const center = this.map.getCenter();\n const bar = computeScaleBar(center.lat, this.map.getZoom(), { maxWidth: this.opts.maxWidth });\n this.barEl.style.width = `${Math.round(bar.pixels)}px`;\n this.labelEl.textContent = bar.label;\n }\n\n private handleMove(ev: unknown): void {\n if (!this.coordEl) return;\n const e = ev as { lngLat?: { lng: number; lat: number } };\n const ll = e.lngLat;\n if (!ll) return;\n this.coordEl.textContent = `${ll.lng.toFixed(5)}, ${ll.lat.toFixed(5)}`;\n }\n\n /** 移除控件与事件监听 */\n remove(): void {\n this.map.off('zoom', this.onZoom);\n this.map.off('move', this.onZoom);\n const canvas = (this.map as any).getCanvas?.();\n canvas?.removeEventListener('mousemove', this.onMove as never);\n this.el.remove();\n }\n}\n","/**\n * 主题切换控件(T8 / F-1.9)。\n *\n * 在暗色(caoguo-dark)与亮色(caoguo-light)官方矢量主题间切换,\n * 调用 `map.setStyle` 并保留当前视图(center/zoom)与现有 GeoJSON 源/图层。\n *\n * 设计为「纯函数 core + 薄 DOM 绑定」:\n * - `toggleTheme` / `themeFromStyle` 为纯逻辑,可独立单测;\n * - `ThemeSwitcher` 类负责渲染按钮并绑定点击事件。\n */\n\nimport { caoguoStyle } from '../styles';\nimport type { ThemeName } from '@caoguo/theme';\n\nexport interface ThemeSwitcherOptions {\n /** 挂载容器(可选) */\n container?: HTMLElement;\n /** 初始主题 */\n initial?: ThemeName;\n}\n\nexport function oppositeTheme(theme: ThemeName): ThemeName {\n return theme === 'caoguo-dark' ? 'caoguo-light' : 'caoguo-dark';\n}\n\n/**\n * 从当前 style 对象推断主题名(依据其 JSON 标识)。\n * 非草果主题时回退到传入的 fallback。\n */\nexport function themeFromStyle(\n style: { name?: string } | string | null | undefined,\n fallback: ThemeName = 'caoguo-dark'\n): ThemeName {\n if (!style) return fallback;\n const name = typeof style === 'string' ? style : style.name;\n if (name === 'caoguo-dark' || name === 'caoguo-light') return name;\n return fallback;\n}\n\nexport class ThemeSwitcher {\n private el: HTMLElement;\n private btn: HTMLButtonElement;\n private current: ThemeName;\n private map: {\n getStyle: () => { name?: string } | undefined;\n setStyle: (s: unknown, opts?: { diff?: boolean }) => void;\n };\n\n constructor(map: ThemeSwitcher['map'], options: ThemeSwitcherOptions = {}) {\n this.map = map;\n this.current = options.initial ?? themeFromStyle(map.getStyle?.(), 'caoguo-dark');\n\n this.el = options.container ?? document.createElement('div');\n this.el.className = 'caoguo-theme-switcher';\n this.el.style.cssText =\n 'position:absolute;right:10px;top:10px;z-index:3;';\n\n this.btn = document.createElement('button');\n this.btn.type = 'button';\n this.applyLabel();\n this.btn.style.cssText =\n 'cursor:pointer;padding:4px 10px;border:1px solid #2a3550;border-radius:6px;' +\n 'background:rgba(10,15,30,.7);color:#cfe;font:12px system-ui,sans-serif;';\n this.btn.addEventListener('click', () => this.toggle());\n this.el.append(this.btn);\n }\n\n private applyLabel(): void {\n this.btn.textContent = this.current === 'caoguo-dark' ? '🌙 暗色' : '☀ 亮色';\n }\n\n /** 当前主题 */\n getTheme(): ThemeName {\n return this.current;\n }\n\n /** 切换到指定主题(保留视图,diff 模式避免闪烁) */\n setTheme(theme: ThemeName): void {\n if (theme === this.current) return;\n this.current = theme;\n this.applyLabel();\n this.map.setStyle(caoguoStyle(theme), { diff: true });\n }\n\n /** 在明暗之间切换 */\n toggle(): void {\n this.setTheme(oppositeTheme(this.current));\n }\n\n /** 挂到容器 */\n addTo(container: HTMLElement): this {\n if (!this.el.parentElement) container.appendChild(this.el);\n return this;\n }\n\n /** 移除 */\n remove(): void {\n this.btn.removeEventListener('click', () => this.toggle());\n this.el.remove();\n }\n}\n","/**\n * 图例控件(通用)。\n *\n * 行业专题图(水深色阶 / 光缆利用率 / 闸站状态 / 故障告警等)渲染后,\n * 需要用户可读的图例说明。本控件接收数据驱动的图例项(label + color),\n * 以「色块 + 文案」形式渲染;支持运行时 setItems 随图层切换更新。\n *\n * 设计为「纯函数 renderLegendHtml + 薄 DOM 绑定」:renderLegendHtml 不依赖浏览器,可独立单测。\n */\n\nexport interface LegendItem {\n /** 文案,如「水深 > 3m」「利用率高」 */\n label: string;\n /** 色值(CSS color,可与渲染层 paint 配色保持一致) */\n color: string;\n /** 标记形状:color 色块(默认)/ line 线段 */\n shape?: 'color' | 'line';\n}\n\nexport interface LegendOptions {\n /** 图例标题(可选) */\n title?: string;\n /** 图例项 */\n items: LegendItem[];\n /** 挂载容器(可选;不传则自动创建) */\n container?: HTMLElement;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) =>\n c === '&' ? '&amp;' : c === '<' ? '&lt;' : c === '>' ? '&gt;' : c === '\"' ? '&quot;' : '&#39;',\n );\n}\n\n/** 纯函数:根据图例项生成 HTML 字符串(可单测) */\nexport function renderLegendHtml(items: LegendItem[], title?: string): string {\n const titleHtml = title ? `<div class=\"cg-legend-title\">${escapeHtml(title)}</div>` : '';\n const itemsHtml = items\n .map((it) => {\n const swatch =\n it.shape === 'line'\n ? `<span class=\"cg-legend-swatch cg-legend-swatch--line\" style=\"background:${it.color}\"></span>`\n : `<span class=\"cg-legend-swatch\" style=\"background:${it.color}\"></span>`;\n return `<div class=\"cg-legend-item\">${swatch}<span>${escapeHtml(it.label)}</span></div>`;\n })\n .join('');\n return `<div class=\"cg-legend-body\">${titleHtml}${itemsHtml}</div>`;\n}\n\n/** 图例控件 */\nexport class LegendControl {\n private el: HTMLElement;\n private bodyEl: HTMLElement;\n private opts: Required<Pick<LegendOptions, 'title'>> & { items: LegendItem[] };\n\n constructor(options: LegendOptions) {\n this.opts = { title: options.title ?? '', items: options.items };\n this.el = options.container ?? document.createElement('div');\n this.el.className = 'caoguo-legend-control';\n this.el.style.cssText =\n 'position:absolute;right:10px;top:10px;padding:8px 10px;background:rgba(10,15,30,.72);color:#cfe;border-radius:6px;font:12px/1.5 system-ui,sans-serif;z-index:2;max-width:220px;';\n this.bodyEl = document.createElement('div');\n this.el.appendChild(this.bodyEl);\n this.render();\n }\n\n private render(): void {\n this.bodyEl.innerHTML = renderLegendHtml(this.opts.items, this.opts.title || undefined);\n }\n\n /** 运行时更新图例项(随图层切换重新渲染) */\n setItems(items: LegendItem[], title?: string): void {\n if (title !== undefined) this.opts.title = title;\n this.opts.items = items;\n this.render();\n }\n\n /** 挂载到容器 */\n addTo(container: HTMLElement): this {\n if (!this.el.parentElement) container.appendChild(this.el);\n return this;\n }\n\n remove(): void {\n this.el.remove();\n }\n}\n","/**\n * 地图导出控件(通用)。\n *\n * 行业专题图渲染后,常需导出当前视图为成果图(PNG)。\n * 基于 maplibre-gl 画布 `toDataURL('image/png')` 生成截图并触发浏览器下载。\n *\n * 设计为「纯函数 triggerDownload + 薄 DOM 绑定」:triggerDownload 不依赖地图,可独立单测。\n */\n\nexport interface ExportOptions {\n /** 下载文件名(不含扩展名) */\n filename?: string;\n /** 挂载容器(可选;不传则自动创建) */\n container?: HTMLElement;\n /** 按钮文案 */\n buttonText?: string;\n}\n\n/** 纯函数:触发浏览器下载(data URL) */\nexport function triggerDownload(dataUrl: string, filename: string): void {\n const a = document.createElement('a');\n a.href = dataUrl;\n a.download = filename.endsWith('.png') ? filename : `${filename}.png`;\n document.body.appendChild(a);\n a.click();\n a.remove();\n}\n\n/** 导出控件 */\nexport class ExportControl {\n private el: HTMLElement;\n private map: {\n getCanvas: () => { toDataURL: (type?: string) => string };\n };\n private filename: string;\n private onClick = () => this.export();\n\n constructor(map: ExportControl['map'], options: ExportOptions = {}) {\n this.map = map;\n this.filename = options.filename ?? 'caoguo-map';\n this.el = options.container ?? document.createElement('button');\n this.el.className = 'caoguo-export-control';\n this.el.textContent = options.buttonText ?? '导出 PNG';\n this.el.style.cssText =\n 'position:absolute;right:10px;top:10px;padding:5px 10px;background:rgba(10,15,30,.72);color:#cfe;border:1px solid #2a3550;border-radius:4px;font:12px system-ui,sans-serif;cursor:pointer;z-index:3;';\n this.el.addEventListener('click', this.onClick);\n }\n\n /** 导出当前地图视图为 PNG 并下载 */\n export(): void {\n const canvas = this.map.getCanvas();\n const url = canvas.toDataURL('image/png');\n triggerDownload(url, this.filename);\n }\n\n /** 挂载到容器 */\n addTo(container: HTMLElement): this {\n if (!this.el.parentElement) container.appendChild(this.el);\n return this;\n }\n\n remove(): void {\n this.el.removeEventListener('click', this.onClick);\n this.el.remove();\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ // src/terrain.ts
2
+ var DEFAULT_TERRAIN_TILES = [
3
+ "https://elevation-tiles-prod.s3.amazonaws.com/terrarium/{z}/{x}/{y}.png"
4
+ ];
5
+ function applyTerrain(mlMap, opts = {}) {
6
+ const sourceId = opts.sourceId ?? "cg-dem";
7
+ if (!mlMap.getSource(sourceId)) {
8
+ mlMap.addSource(sourceId, {
9
+ type: "raster-dem",
10
+ tiles: opts.tiles ?? DEFAULT_TERRAIN_TILES,
11
+ encoding: opts.encoding ?? "terrarium",
12
+ tileSize: 256,
13
+ maxzoom: opts.maxzoom ?? 15
14
+ });
15
+ }
16
+ mlMap.setTerrain({ source: sourceId, exaggeration: opts.exaggeration ?? 1.5 });
17
+ }
18
+ function removeTerrain(mlMap, sourceId = "cg-dem") {
19
+ mlMap.setTerrain(null);
20
+ if (mlMap.getSource(sourceId)) {
21
+ mlMap.removeSource(sourceId);
22
+ }
23
+ }
24
+
25
+ export { DEFAULT_TERRAIN_TILES, applyTerrain, removeTerrain };
26
+ //# sourceMappingURL=chunk-OLS4DD3F.js.map
27
+ //# sourceMappingURL=chunk-OLS4DD3F.js.map