@caoguo/maplibre 0.0.3 → 0.0.6

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.
@@ -20,7 +20,7 @@ function buildTiandituSources(type, opts) {
20
20
  const { base, label } = LAYER_MAP[type];
21
21
  const tileSize = opts.tileSize ?? 256;
22
22
  const maxzoom = opts.maxzoom ?? 18;
23
- const attr = "\xA9 \u5929\u5730\u56FE GS(2023)3295\u53F7";
23
+ const attr = "\xA9 \u5929\u5730\u56FE GS(2023)3295\u53F7 \xB7 \u8349\u679C\u5730\u56FE";
24
24
  return [
25
25
  {
26
26
  id: `tianditu-${base}`,
@@ -92,5 +92,5 @@ function addTiandituBaseMap(opts) {
92
92
  }
93
93
 
94
94
  export { MissingTokenError, addTiandituBaseMap, buildTiandituSources, tiandituStyle, tiandituTileUrls };
95
- //# sourceMappingURL=chunk-EZOV5O2M.js.map
96
- //# sourceMappingURL=chunk-EZOV5O2M.js.map
95
+ //# sourceMappingURL=chunk-QGVXYR2I.js.map
96
+ //# sourceMappingURL=chunk-QGVXYR2I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sources/tianditu.ts","../src/sources/addTianditu.ts"],"names":[],"mappings":";AA4CA,IAAM,SAAA,GAAiF;AAAA,EACrF,MAAA,EAAQ,EAAE,IAAA,EAAM,KAAA,EAAO,OAAO,KAAA,EAAM;AAAA,EACpC,SAAA,EAAW,EAAE,IAAA,EAAM,KAAA,EAAO,OAAO,KAAA,EAAM;AAAA,EACvC,OAAA,EAAS,EAAE,IAAA,EAAM,KAAA,EAAO,OAAO,KAAA;AACjC,CAAA;AAEA,IAAM,cAAA,GAAgE;AAAA,EACpE,GAAA,EAAK,KAAA;AAAA,EACL,GAAA,EAAK,KAAA;AAAA,EACL,GAAA,EAAK;AACP,CAAA;AAGO,SAAS,gBAAA,CACd,OACA,IAAA,EACU;AACV,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,UAAA,IAAc,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AACtD,EAAA,MAAM,aAAa,IAAA,CAAK,IAAA,KAAS,OAAO,cAAA,CAAe,KAAK,KAAK,KAAA,GAAQ,KAAA;AACzE,EAAA,OAAO,GAAA,CAAI,GAAA;AAAA,IACT,CAAC,CAAA,KACC,CAAA,SAAA,EAAY,CAAC,CAAA,iBAAA,EAAoB,UAAU,CAAA,yDAAA,EAA4D,UAAU,CAAA,sFAAA,EAAyF,kBAAA,CAAmB,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA,GAC5O;AACF;AAgBO,SAAS,oBAAA,CACd,MACA,IAAA,EACsB;AACtB,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,UAAU,IAAI,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,GAAA;AAClC,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,EAAA;AAChC,EAAA,MAAM,IAAA,GAAO,4CAAA;AACb,EAAA,OAAO;AAAA,IACL;AAAA,MACE,EAAA,EAAI,YAAY,IAAI,CAAA,CAAA;AAAA,MACpB,OAAA,EAAS,KAAA;AAAA,MACT,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,gBAAA,CAAiB,IAAA,EAAM,IAAI,CAAA;AAAA,QAClC,QAAA;AAAA,QACA,OAAA;AAAA,QACA,WAAA,EAAa;AAAA;AACf,KACF;AAAA,IACA;AAAA,MACE,EAAA,EAAI,YAAY,KAAK,CAAA,CAAA;AAAA,MACrB,OAAA,EAAS,IAAA;AAAA,MACT,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,gBAAA,CAAiB,KAAA,EAAO,IAAI,CAAA;AAAA,QACnC,QAAA;AAAA,QACA,OAAA;AAAA,QACA,WAAA,EAAa;AAAA;AACf;AACF,GACF;AACF;AAEO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EAC3C,WAAA,GAAc;AACZ,IAAA,KAAA,CAAM,4IAAiE,CAAA;AACvE,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EACd;AACF;;;ACjGO,SAAS,aAAA,CACd,MACA,IAAA,EACoB;AACpB,EAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,MAAM,IAAI,iBAAA,EAAkB;AAC7C,EAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,EAAM,IAAI,CAAA;AAC/C,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,MAAA,EAAQ,6DAAA;AAAA,IACR,SAAS,MAAA,CAAO,WAAA;AAAA,MACd,OAAA,CAAQ,IAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAM,CAAC;AAAA,KACrC;AAAA,IACA,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MAC1B,IAAI,CAAA,CAAE,EAAA;AAAA,MACN,IAAA,EAAM,QAAA;AAAA,MACN,QAAQ,CAAA,CAAE,EAAA;AAAA;AAAA,MAEV,GAAI,CAAA,CAAE,OAAA,GAAU,EAAE,OAAA,EAAS,CAAA,KAAM;AAAC,KACpC,CAAE;AAAA,GACJ;AACF;AAGO,SAAS,mBAAmB,IAAA,EAAgC;AACjE,EAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,MAAM,IAAI,iBAAA,EAAkB;AAC7C,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,IAAQ,QAAA;AAC1B,EAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,EAAM,IAAI,CAAA;AAC/C,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,IAAI,CAAC,GAAA,CAAI,SAAA,CAAU,CAAA,CAAE,EAAE,CAAA,EAAG;AACxB,MAAA,GAAA,CAAI,SAAA,CAAU,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAe,CAAA;AAAA,IACvC;AACA,IAAA,IAAI,CAAC,GAAA,CAAI,QAAA,CAAS,CAAA,CAAE,EAAE,CAAA,EAAG;AACvB,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAI,CAAA,CAAE,EAAA;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,QAAQ,CAAA,CAAE;AAAA,OACF,CAAA;AAAA,IACZ;AAAA,EACF;AACF","file":"chunk-EZOV5O2M.js","sourcesContent":["/**\n * 天地图(Tianditu)WMTS 底图源(F-1.2)。\n *\n * 天地图为国内权威底图,坐标系为 CGCS2000 / Web Mercator(显示尺度等价于 WGS84)。\n * token 必须由调用方在运行时注入,**绝不硬编码**;缺失时抛出明确错误。\n *\n * 文档:https://lbs.tianditu.gov.cn/server/MapService.html\n */\n\nexport type TiandituLayer =\n /** 矢量底图(含道路/注记基础) */\n | 'vec'\n /** 影像底图 */\n | 'img'\n /** 地形底图 */\n | 'ter'\n /** 矢量注记(道路/地名) */\n | 'cva'\n /** 影像注记 */\n | 'cia'\n /** 地形注记 */\n | 'cta'\n /** 矢量注记(英文) */\n | 'eva'\n /** 影像注记(英文) */\n | 'eia'\n /** 地形注记(英文) */\n | 'eta';\n\nexport type TiandituType = 'vector' | 'satellite' | 'terrain';\n\nexport interface TiandituOptions {\n /** 天地图授权 token(必填,运行时注入) */\n token: string;\n /** 语言:中文 cva/cia/cta 或英文 eva/eia/eta */\n lang?: 'zh' | 'en';\n /** 瓦片服务子域,默认 0-7 */\n subdomains?: number[];\n /** 瓦片尺寸,默认 256 */\n tileSize?: number;\n /** 最大层级,默认 18 */\n maxzoom?: number;\n}\n\nconst LAYER_MAP: Record<TiandituType, { base: TiandituLayer; label: TiandituLayer }> = {\n vector: { base: 'vec', label: 'cva' },\n satellite: { base: 'img', label: 'cia' },\n terrain: { base: 'ter', label: 'cta' },\n};\n\nconst LAYER_LABEL_EN: Partial<Record<TiandituLayer, TiandituLayer>> = {\n cva: 'eva',\n cia: 'eia',\n cta: 'eta',\n};\n\n/** 构建单张 WMTS 栅格源的 tiles URL 列表(含子域与 token) */\nexport function tiandituTileUrls(\n layer: TiandituLayer,\n opts: TiandituOptions\n): string[] {\n const sub = opts.subdomains ?? [0, 1, 2, 3, 4, 5, 6, 7];\n const labelLayer = opts.lang === 'en' ? LAYER_LABEL_EN[layer] ?? layer : layer;\n return sub.map(\n (s) =>\n `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)}`\n );\n}\n\nexport interface TiandituSourceSpec {\n id: string;\n source: {\n type: 'raster';\n tiles: string[];\n tileSize: number;\n maxzoom: number;\n attribution: string;\n };\n /** 是否注记层(绘制于底图之上) */\n isLabel: boolean;\n}\n\n/** 生成一组天地图源(底图 + 注记) */\nexport function buildTiandituSources(\n type: TiandituType,\n opts: TiandituOptions\n): TiandituSourceSpec[] {\n const { base, label } = LAYER_MAP[type];\n const tileSize = opts.tileSize ?? 256;\n const maxzoom = opts.maxzoom ?? 18;\n const attr = '© 天地图 GS(2023)3295号';\n return [\n {\n id: `tianditu-${base}`,\n isLabel: false,\n source: {\n type: 'raster',\n tiles: tiandituTileUrls(base, opts),\n tileSize,\n maxzoom,\n attribution: attr,\n },\n },\n {\n id: `tianditu-${label}`,\n isLabel: true,\n source: {\n type: 'raster',\n tiles: tiandituTileUrls(label, opts),\n tileSize,\n maxzoom,\n attribution: attr,\n },\n },\n ];\n}\n\nexport class MissingTokenError extends Error {\n constructor() {\n super('天地图 token 缺失:调用 addTiandituBaseMap 必须传入 tianditu: { token } 选项。');\n this.name = 'MissingTokenError';\n }\n}\n","/**\n * 把天地图底图(底图 + 注记)作为源/图层添加到 MapLibre 地图实例。\n *\n * 由于天地图为栅格底图,推荐作为「无 style 背景」的地图底层:\n * 直接通过 setStyle 注入最小 style,或先 addSource + addLayer。\n * 这里提供低侵入的「注入已有 map」方式,业务可叠加自有矢量图层。\n */\n\nimport type { Map as MlMap, StyleSpecification } from 'maplibre-gl';\nimport {\n buildTiandituSources,\n MissingTokenError,\n type TiandituOptions,\n type TiandituType,\n} from './tianditu';\n\nexport interface AddTiandituOptions extends TiandituOptions {\n type?: TiandituType;\n /** 添加到指定地图实例 */\n map: MlMap;\n /** 注入为 style 的源/图层还是 addSource(默认 addSource + addLayer) */\n asStyle?: false;\n}\n\n/** 生成可直接作为 Map style 的天地图 Style JSON(含底图+注记两层) */\nexport function tiandituStyle(\n type: TiandituType,\n opts: TiandituOptions\n): StyleSpecification {\n if (!opts.token) throw new MissingTokenError();\n const sources = buildTiandituSources(type, opts);\n return {\n version: 8,\n glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',\n sources: Object.fromEntries(\n sources.map((s) => [s.id, s.source])\n ) as StyleSpecification['sources'],\n layers: sources.map((s) => ({\n id: s.id,\n type: 'raster' as const,\n source: s.id,\n // 注记层置于底图之上\n ...(s.isLabel ? { minzoom: 0 } : {}),\n })),\n };\n}\n\n/** 向已存在的地图实例注入天地图底图(底图 + 注记) */\nexport function addTiandituBaseMap(opts: AddTiandituOptions): void {\n if (!opts.token) throw new MissingTokenError();\n const type = opts.type ?? 'vector';\n const sources = buildTiandituSources(type, opts);\n const map = opts.map;\n for (const s of sources) {\n if (!map.getSource(s.id)) {\n map.addSource(s.id, s.source as never);\n }\n if (!map.getLayer(s.id)) {\n map.addLayer({\n id: s.id,\n type: 'raster',\n source: s.id,\n } as never);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/sources/tianditu.ts","../src/sources/addTianditu.ts"],"names":[],"mappings":"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","file":"chunk-QGVXYR2I.js","sourcesContent":["/**\n * 天地图(Tianditu)WMTS 底图源(F-1.2)。\n *\n * 天地图为国内权威底图,坐标系为 CGCS2000 / Web Mercator(显示尺度等价于 WGS84)。\n * token 必须由调用方在运行时注入,**绝不硬编码**;缺失时抛出明确错误。\n *\n * 文档:https://lbs.tianditu.gov.cn/server/MapService.html\n */\n\nexport type TiandituLayer =\n /** 矢量底图(含道路/注记基础) */\n | 'vec'\n /** 影像底图 */\n | 'img'\n /** 地形底图 */\n | 'ter'\n /** 矢量注记(道路/地名) */\n | 'cva'\n /** 影像注记 */\n | 'cia'\n /** 地形注记 */\n | 'cta'\n /** 矢量注记(英文) */\n | 'eva'\n /** 影像注记(英文) */\n | 'eia'\n /** 地形注记(英文) */\n | 'eta';\n\nexport type TiandituType = 'vector' | 'satellite' | 'terrain';\n\nexport interface TiandituOptions {\n /** 天地图授权 token(必填,运行时注入) */\n token: string;\n /** 语言:中文 cva/cia/cta 或英文 eva/eia/eta */\n lang?: 'zh' | 'en';\n /** 瓦片服务子域,默认 0-7 */\n subdomains?: number[];\n /** 瓦片尺寸,默认 256 */\n tileSize?: number;\n /** 最大层级,默认 18 */\n maxzoom?: number;\n}\n\nconst LAYER_MAP: Record<TiandituType, { base: TiandituLayer; label: TiandituLayer }> = {\n vector: { base: 'vec', label: 'cva' },\n satellite: { base: 'img', label: 'cia' },\n terrain: { base: 'ter', label: 'cta' },\n};\n\nconst LAYER_LABEL_EN: Partial<Record<TiandituLayer, TiandituLayer>> = {\n cva: 'eva',\n cia: 'eia',\n cta: 'eta',\n};\n\n/** 构建单张 WMTS 栅格源的 tiles URL 列表(含子域与 token) */\nexport function tiandituTileUrls(\n layer: TiandituLayer,\n opts: TiandituOptions\n): string[] {\n const sub = opts.subdomains ?? [0, 1, 2, 3, 4, 5, 6, 7];\n const labelLayer = opts.lang === 'en' ? LAYER_LABEL_EN[layer] ?? layer : layer;\n return sub.map(\n (s) =>\n `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)}`\n );\n}\n\nexport interface TiandituSourceSpec {\n id: string;\n source: {\n type: 'raster';\n tiles: string[];\n tileSize: number;\n maxzoom: number;\n attribution: string;\n };\n /** 是否注记层(绘制于底图之上) */\n isLabel: boolean;\n}\n\n/** 生成一组天地图源(底图 + 注记) */\nexport function buildTiandituSources(\n type: TiandituType,\n opts: TiandituOptions\n): TiandituSourceSpec[] {\n const { base, label } = LAYER_MAP[type];\n const tileSize = opts.tileSize ?? 256;\n const maxzoom = opts.maxzoom ?? 18;\n const attr = '© 天地图 GS(2023)3295号 · 草果地图';\n return [\n {\n id: `tianditu-${base}`,\n isLabel: false,\n source: {\n type: 'raster',\n tiles: tiandituTileUrls(base, opts),\n tileSize,\n maxzoom,\n attribution: attr,\n },\n },\n {\n id: `tianditu-${label}`,\n isLabel: true,\n source: {\n type: 'raster',\n tiles: tiandituTileUrls(label, opts),\n tileSize,\n maxzoom,\n attribution: attr,\n },\n },\n ];\n}\n\nexport class MissingTokenError extends Error {\n constructor() {\n super('天地图 token 缺失:调用 addTiandituBaseMap 必须传入 tianditu: { token } 选项。');\n this.name = 'MissingTokenError';\n }\n}\n","/**\n * 把天地图底图(底图 + 注记)作为源/图层添加到 MapLibre 地图实例。\n *\n * 由于天地图为栅格底图,推荐作为「无 style 背景」的地图底层:\n * 直接通过 setStyle 注入最小 style,或先 addSource + addLayer。\n * 这里提供低侵入的「注入已有 map」方式,业务可叠加自有矢量图层。\n */\n\nimport type { Map as MlMap, StyleSpecification } from 'maplibre-gl';\nimport {\n buildTiandituSources,\n MissingTokenError,\n type TiandituOptions,\n type TiandituType,\n} from './tianditu';\n\nexport interface AddTiandituOptions extends TiandituOptions {\n type?: TiandituType;\n /** 添加到指定地图实例 */\n map: MlMap;\n /** 注入为 style 的源/图层还是 addSource(默认 addSource + addLayer) */\n asStyle?: false;\n}\n\n/** 生成可直接作为 Map style 的天地图 Style JSON(含底图+注记两层) */\nexport function tiandituStyle(\n type: TiandituType,\n opts: TiandituOptions\n): StyleSpecification {\n if (!opts.token) throw new MissingTokenError();\n const sources = buildTiandituSources(type, opts);\n return {\n version: 8,\n glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',\n sources: Object.fromEntries(\n sources.map((s) => [s.id, s.source])\n ) as StyleSpecification['sources'],\n layers: sources.map((s) => ({\n id: s.id,\n type: 'raster' as const,\n source: s.id,\n // 注记层置于底图之上\n ...(s.isLabel ? { minzoom: 0 } : {}),\n })),\n };\n}\n\n/** 向已存在的地图实例注入天地图底图(底图 + 注记) */\nexport function addTiandituBaseMap(opts: AddTiandituOptions): void {\n if (!opts.token) throw new MissingTokenError();\n const type = opts.type ?? 'vector';\n const sources = buildTiandituSources(type, opts);\n const map = opts.map;\n for (const s of sources) {\n if (!map.getSource(s.id)) {\n map.addSource(s.id, s.source as never);\n }\n if (!map.getLayer(s.id)) {\n map.addLayer({\n id: s.id,\n type: 'raster',\n source: s.id,\n } as never);\n }\n }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -197,7 +197,7 @@ function buildTiandituSources(type, opts) {
197
197
  const { base, label } = LAYER_MAP[type];
198
198
  const tileSize = opts.tileSize ?? 256;
199
199
  const maxzoom = opts.maxzoom ?? 18;
200
- const attr = "\xA9 \u5929\u5730\u56FE GS(2023)3295\u53F7";
200
+ const attr = "\xA9 \u5929\u5730\u56FE GS(2023)3295\u53F7 \xB7 \u8349\u679C\u5730\u56FE";
201
201
  return [
202
202
  {
203
203
  id: `tianditu-${base}`,
@@ -765,15 +765,96 @@ var ThemeSwitcher = class {
765
765
  }
766
766
  };
767
767
 
768
+ // src/controls/LegendControl.ts
769
+ function escapeHtml(s) {
770
+ return s.replace(
771
+ /[&<>"']/g,
772
+ (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : c === '"' ? "&quot;" : "&#39;"
773
+ );
774
+ }
775
+ function renderLegendHtml(items, title) {
776
+ const titleHtml = title ? `<div class="cg-legend-title">${escapeHtml(title)}</div>` : "";
777
+ const itemsHtml = items.map((it) => {
778
+ 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>`;
779
+ return `<div class="cg-legend-item">${swatch}<span>${escapeHtml(it.label)}</span></div>`;
780
+ }).join("");
781
+ return `<div class="cg-legend-body">${titleHtml}${itemsHtml}</div>`;
782
+ }
783
+ var LegendControl = class {
784
+ constructor(options) {
785
+ this.opts = { title: options.title ?? "", items: options.items };
786
+ this.el = options.container ?? document.createElement("div");
787
+ this.el.className = "caoguo-legend-control";
788
+ 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;";
789
+ this.bodyEl = document.createElement("div");
790
+ this.el.appendChild(this.bodyEl);
791
+ this.render();
792
+ }
793
+ render() {
794
+ this.bodyEl.innerHTML = renderLegendHtml(this.opts.items, this.opts.title || void 0);
795
+ }
796
+ /** 运行时更新图例项(随图层切换重新渲染) */
797
+ setItems(items, title) {
798
+ if (title !== void 0) this.opts.title = title;
799
+ this.opts.items = items;
800
+ this.render();
801
+ }
802
+ /** 挂载到容器 */
803
+ addTo(container) {
804
+ if (!this.el.parentElement) container.appendChild(this.el);
805
+ return this;
806
+ }
807
+ remove() {
808
+ this.el.remove();
809
+ }
810
+ };
811
+
812
+ // src/controls/ExportControl.ts
813
+ function triggerDownload(dataUrl, filename) {
814
+ const a = document.createElement("a");
815
+ a.href = dataUrl;
816
+ a.download = filename.endsWith(".png") ? filename : `${filename}.png`;
817
+ document.body.appendChild(a);
818
+ a.click();
819
+ a.remove();
820
+ }
821
+ var ExportControl = class {
822
+ constructor(map, options = {}) {
823
+ this.onClick = () => this.export();
824
+ this.map = map;
825
+ this.filename = options.filename ?? "caoguo-map";
826
+ this.el = options.container ?? document.createElement("button");
827
+ this.el.className = "caoguo-export-control";
828
+ this.el.textContent = options.buttonText ?? "\u5BFC\u51FA PNG";
829
+ 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;";
830
+ this.el.addEventListener("click", this.onClick);
831
+ }
832
+ /** 导出当前地图视图为 PNG 并下载 */
833
+ export() {
834
+ const canvas = this.map.getCanvas();
835
+ const url = canvas.toDataURL("image/png");
836
+ triggerDownload(url, this.filename);
837
+ }
838
+ /** 挂载到容器 */
839
+ addTo(container) {
840
+ if (!this.el.parentElement) container.appendChild(this.el);
841
+ return this;
842
+ }
843
+ remove() {
844
+ this.el.removeEventListener("click", this.onClick);
845
+ this.el.remove();
846
+ }
847
+ };
848
+
768
849
  // src/shaders/glowGeometry.ts
769
- function glowPasses(passes = 4, coreOpacity = 1) {
850
+ function glowPasses(passes = 4, baseWidth = 3) {
770
851
  if (passes < 1) passes = 1;
771
852
  const out = [];
772
853
  for (let i = 0; i < passes; i++) {
773
854
  const t = passes === 1 ? 1 : 1 - i / (passes - 1);
774
855
  out.push({
775
- widthScale: 1 + (1 - t) * (passes - 1) * 1.5,
776
- opacity: coreOpacity * (0.12 + 0.88 * t)
856
+ width: baseWidth * (1 + (1 - t) * (passes - 1) * 1.8),
857
+ opacity: 0.1 + 0.9 * t * t
777
858
  });
778
859
  }
779
860
  return out;
@@ -781,20 +862,58 @@ function glowPasses(passes = 4, coreOpacity = 1) {
781
862
  function projectSimple(lng, lat) {
782
863
  const x = lng / 180;
783
864
  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))];
865
+ return [x, Math.max(-1, Math.min(1, y / Math.PI))];
785
866
  }
786
867
  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 };
868
+ const passes = glowPasses(opts.passes ?? 4, opts.baseWidth ?? 3);
869
+ const stride = 6;
870
+ const verts = [];
871
+ const passRanges = [];
872
+ const renderGroups = [];
873
+ const groups = [];
874
+ for (const line of lines) {
875
+ const g = line.group ?? "default";
876
+ if (!groups.includes(g)) groups.push(g);
877
+ }
878
+ for (let p = 0; p < passes.length; p++) {
879
+ passes[p];
880
+ const start = verts.length / stride;
881
+ for (const group of groups) {
882
+ const groupStart = verts.length / stride;
883
+ const groupIndex = groups.indexOf(group);
884
+ for (const line of lines) {
885
+ if ((line.group ?? "default") !== group) continue;
886
+ const pts = line.coordinates.map(([lng, lat]) => projectSimple(lng, lat));
887
+ if (pts.length < 2) continue;
888
+ for (let i = 0; i < pts.length - 1; i++) {
889
+ const a = pts[i];
890
+ const b = pts[i + 1];
891
+ const dx = b[0] - a[0];
892
+ const dy = b[1] - a[1];
893
+ const quad = [
894
+ [a[0], a[1], dx, dy, 1, groupIndex],
895
+ [a[0], a[1], dx, dy, -1, groupIndex],
896
+ [b[0], b[1], dx, dy, 1, groupIndex],
897
+ [b[0], b[1], dx, dy, -1, groupIndex]
898
+ ];
899
+ const idx = [0, 1, 2, 2, 1, 3];
900
+ for (const k of idx) {
901
+ const v = quad[k];
902
+ verts.push(v[0], v[1], v[2], v[3], v[4], v[5]);
903
+ }
904
+ }
905
+ }
906
+ const count2 = verts.length / stride - groupStart;
907
+ if (count2 > 0) renderGroups.push({ passIndex: p, group, start: groupStart, count: count2 });
908
+ }
909
+ const count = verts.length / stride - start;
910
+ passRanges.push({ start, count });
911
+ }
912
+ return { vertices: new Float32Array(verts), passRanges, passes, stride, groups, renderGroups };
795
913
  }
796
914
 
797
915
  // src/shaders/CustomLineLayer.ts
916
+ var FALLBACK_COLOR = [0.6, 0.7, 0.9];
798
917
  var DEFAULT_COLORS = {
799
918
  pipe: [0.2, 0.85, 1],
800
919
  // 青蓝:管线
@@ -805,17 +924,21 @@ var DEFAULT_COLORS = {
805
924
  };
806
925
  var VERT = `
807
926
  attribute vec2 aPos;
808
- attribute float aMiter;
927
+ attribute vec2 aDir;
928
+ attribute float aSide;
809
929
  uniform mat4 uMatrix;
810
930
  uniform float uWidth;
811
931
  uniform vec2 uResolution;
812
932
  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
933
  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);
934
+ // \u6BB5\u65B9\u5411\u4E5F\u6295\u5F71\uFF0C\u6C42\u5C4F\u5E55\u7A7A\u95F4\u65B9\u5411
935
+ vec4 clipDir = uMatrix * vec4(aPos + aDir, 0.0, 1.0);
936
+ vec2 s0 = clip.xy / clip.w;
937
+ vec2 s1 = clipDir.xy / clipDir.w;
938
+ vec2 dir = normalize(s1 - s0 + vec2(1e-6));
939
+ vec2 normal = vec2(-dir.y, dir.x);
940
+ vec2 offset = normal * aSide * (uWidth / uResolution * 2.0);
941
+ gl_Position = vec4(s0 + offset * clip.w, clip.z, clip.w);
819
942
  }
820
943
  `;
821
944
  var FRAG = `
@@ -830,6 +953,9 @@ function compile(gl, type, src) {
830
953
  const sh = gl.createShader(type);
831
954
  gl.shaderSource(sh, src);
832
955
  gl.compileShader(sh);
956
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
957
+ console.error("[CustomLineLayer] shader \u7F16\u8BD1\u5931\u8D25:", gl.getShaderInfoLog(sh));
958
+ }
833
959
  return sh;
834
960
  }
835
961
  var CustomLineLayer = class {
@@ -840,40 +966,48 @@ var CustomLineLayer = class {
840
966
  this.lines = opts.lines;
841
967
  this.colors = { ...DEFAULT_COLORS, ...opts.colors };
842
968
  this.baseWidth = opts.baseWidth ?? 3;
843
- this.passes = opts.passes ?? 4;
844
- this.geometry = buildGlowGeometry(this.lines, { passes: this.passes });
969
+ this.geometry = buildGlowGeometry(this.lines, { passes: opts.passes ?? 4, baseWidth: this.baseWidth });
970
+ for (const g of this.geometry.groups) {
971
+ if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;
972
+ }
845
973
  }
846
974
  onAdd(map, gl) {
847
975
  this.map = map;
976
+ this.gl = gl;
848
977
  const vs = compile(gl, gl.VERTEX_SHADER, VERT);
849
978
  const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);
850
979
  const prog = gl.createProgram();
851
980
  gl.attachShader(prog, vs);
852
981
  gl.attachShader(prog, fs);
853
982
  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
- }
983
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
984
+ console.error("[CustomLineLayer] program \u94FE\u63A5\u5931\u8D25:", gl.getProgramInfoLog(prog));
861
985
  }
986
+ this.program = prog;
862
987
  const buf = gl.createBuffer();
863
988
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
864
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
989
+ gl.bufferData(gl.ARRAY_BUFFER, this.geometry.vertices, gl.STATIC_DRAW);
865
990
  this.buffer = buf;
866
991
  }
867
992
  render(gl, matrix) {
868
993
  if (!this.program || !this.buffer) return;
869
994
  gl.useProgram(this.program);
870
995
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
996
+ const stride = this.geometry.stride * 4;
871
997
  const aPos = gl.getAttribLocation(this.program, "aPos");
872
- const aMiter = gl.getAttribLocation(this.program, "aMiter");
998
+ const aDir = gl.getAttribLocation(this.program, "aDir");
999
+ const aSide = gl.getAttribLocation(this.program, "aSide");
1000
+ const aGroup = gl.getAttribLocation(this.program, "aGroup");
873
1001
  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);
1002
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);
1003
+ gl.enableVertexAttribArray(aDir);
1004
+ gl.vertexAttribPointer(aDir, 2, gl.FLOAT, false, stride, 8);
1005
+ gl.enableVertexAttribArray(aSide);
1006
+ gl.vertexAttribPointer(aSide, 1, gl.FLOAT, false, stride, 16);
1007
+ if (aGroup >= 0) {
1008
+ gl.enableVertexAttribArray(aGroup);
1009
+ gl.vertexAttribPointer(aGroup, 1, gl.FLOAT, false, stride, 20);
1010
+ }
877
1011
  const uMatrix = gl.getUniformLocation(this.program, "uMatrix");
878
1012
  gl.uniformMatrix4fv(uMatrix, false, new Float32Array(matrix));
879
1013
  const uWidth = gl.getUniformLocation(this.program, "uWidth");
@@ -884,12 +1018,31 @@ var CustomLineLayer = class {
884
1018
  gl.uniform2f(uRes, canvas?.width ?? 1024, canvas?.height ?? 768);
885
1019
  gl.enable(gl.BLEND);
886
1020
  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);
1021
+ for (const rg of this.geometry.renderGroups) {
1022
+ const pass = this.geometry.passes[rg.passIndex];
1023
+ const color = this.colors[rg.group] ?? FALLBACK_COLOR;
1024
+ gl.uniform1f(uWidth, pass.width);
890
1025
  gl.uniform3f(uColor, color[0], color[1], color[2]);
891
1026
  gl.uniform1f(uOpacity, pass.opacity);
892
- gl.drawArrays(gl.LINES, 0, this.geometry.vertexCount);
1027
+ gl.drawArrays(gl.TRIANGLES, rg.start, rg.count);
1028
+ }
1029
+ }
1030
+ /**
1031
+ * 动态更新线集合(高亮选中、切换数据等无需重建图层)。
1032
+ * 浏览器环境会刷新 GPU 缓冲;纯逻辑/测试环境仅更新几何与数据。
1033
+ */
1034
+ setLines(lines) {
1035
+ this.lines = lines;
1036
+ this.geometry = buildGlowGeometry(this.lines, {
1037
+ passes: this.geometry.passes.length,
1038
+ baseWidth: this.baseWidth
1039
+ });
1040
+ for (const g of this.geometry.groups) {
1041
+ if (!(g in this.colors)) this.colors[g] = FALLBACK_COLOR;
1042
+ }
1043
+ if (this.buffer && this.gl) {
1044
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
1045
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.geometry.vertices, this.gl.STATIC_DRAW);
893
1046
  }
894
1047
  }
895
1048
  onRemove(_map, gl) {
@@ -953,17 +1106,119 @@ var LodController = class {
953
1106
  }
954
1107
  };
955
1108
 
1109
+ // src/terrain.ts
1110
+ var DEFAULT_TERRAIN_TILES = [
1111
+ "https://elevation-tiles-prod.s3.amazonaws.com/terrarium/{z}/{x}/{y}.png"
1112
+ ];
1113
+ function applyTerrain(mlMap, opts = {}) {
1114
+ const sourceId = opts.sourceId ?? "cg-dem";
1115
+ if (!mlMap.getSource(sourceId)) {
1116
+ mlMap.addSource(sourceId, {
1117
+ type: "raster-dem",
1118
+ tiles: opts.tiles ?? DEFAULT_TERRAIN_TILES,
1119
+ encoding: opts.encoding ?? "terrarium",
1120
+ tileSize: 256,
1121
+ maxzoom: opts.maxzoom ?? 15
1122
+ });
1123
+ }
1124
+ mlMap.setTerrain({ source: sourceId, exaggeration: opts.exaggeration ?? 1.5 });
1125
+ }
1126
+ function removeTerrain(mlMap, sourceId = "cg-dem") {
1127
+ mlMap.setTerrain(null);
1128
+ if (mlMap.getSource(sourceId)) {
1129
+ mlMap.removeSource(sourceId);
1130
+ }
1131
+ }
1132
+
1133
+ // src/sourceUtils.ts
1134
+ function upsertSource(mlMap, id, data) {
1135
+ if (mlMap.getSource(id)) {
1136
+ if (mlMap.setData) {
1137
+ mlMap.setData(id, data);
1138
+ } else if (mlMap.addSource) {
1139
+ mlMap.addSource(id, data);
1140
+ }
1141
+ } else if (mlMap.addSource) {
1142
+ mlMap.addSource(id, data);
1143
+ }
1144
+ }
1145
+ function removeSourceSafe(mlMap, id) {
1146
+ if (mlMap.getSource(id) && mlMap.removeSource) {
1147
+ mlMap.removeSource(id);
1148
+ }
1149
+ }
1150
+ function removeSourcesSafe(mlMap, ids) {
1151
+ for (const id of ids) {
1152
+ removeSourceSafe(mlMap, id);
1153
+ }
1154
+ }
1155
+
956
1156
  // src/index.ts
1157
+ var WebGLUnavailableError = class extends Error {
1158
+ constructor(message = "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 WebGL\uFF0C\u65E0\u6CD5\u6E32\u67D3\u5730\u56FE") {
1159
+ super(message);
1160
+ this.name = "WebGLUnavailableError";
1161
+ }
1162
+ };
1163
+ function isWebGLAvailable() {
1164
+ try {
1165
+ const canvas = document.createElement("canvas");
1166
+ let failed = false;
1167
+ const onErr = () => {
1168
+ failed = true;
1169
+ };
1170
+ canvas.addEventListener("webglcontextcreationerror", onErr, { once: true });
1171
+ const gl = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1172
+ canvas.removeEventListener("webglcontextcreationerror", onErr);
1173
+ if (failed || !gl) return false;
1174
+ const vendor = gl.getParameter(gl.VERSION);
1175
+ if (!vendor) return false;
1176
+ const vs = gl.createShader(gl.VERTEX_SHADER);
1177
+ if (!vs) return false;
1178
+ gl.shaderSource(vs, "attribute vec2 p; void main(){ gl_Position = vec4(p,0.0,1.0); }");
1179
+ gl.compileShader(vs);
1180
+ if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) return false;
1181
+ return true;
1182
+ } catch {
1183
+ return false;
1184
+ }
1185
+ }
1186
+ var GLOBAL_KEY = "__caoguoMapConfig__";
1187
+ function setGlobalConfig(cfg) {
1188
+ const prev = getGlobalConfig();
1189
+ globalThis[GLOBAL_KEY] = { ...prev, ...cfg };
1190
+ }
1191
+ function getGlobalConfig() {
1192
+ return globalThis[GLOBAL_KEY] ?? {};
1193
+ }
957
1194
  var Map2 = class {
958
1195
  constructor(options) {
959
1196
  /** 离线瓦片后端(默认浏览器 IndexedDB / Node 内存) */
960
1197
  this._store = createDefaultStore();
961
1198
  this._dataCRS = options.dataCRS ?? "WGS84";
1199
+ if (!isWebGLAvailable()) {
1200
+ throw new WebGLUnavailableError();
1201
+ }
1202
+ let style = osmRasterStyle();
1203
+ if (options.style) {
1204
+ style = options.style;
1205
+ } else if (options.tianditu) {
1206
+ if (!options.tianditu.token) throw new MissingTokenError();
1207
+ style = tiandituStyle(
1208
+ options.tianditu.type ?? "vector",
1209
+ options.tianditu
1210
+ );
1211
+ } else {
1212
+ const g = getGlobalConfig();
1213
+ if (g.tiandituToken) {
1214
+ style = tiandituStyle("vector", { token: g.tiandituToken });
1215
+ }
1216
+ }
962
1217
  this._map = new maplibregl__default.default.Map({
963
1218
  container: options.container,
964
1219
  center: options.center ?? WUHAN_CENTER,
965
1220
  zoom: options.zoom ?? WUHAN_ZOOM,
966
- style: options.style ?? osmRasterStyle(),
1221
+ style,
967
1222
  pitch: options.pitch ?? 0,
968
1223
  bearing: options.bearing ?? 0,
969
1224
  attributionControl: { compact: true }
@@ -1032,8 +1287,23 @@ var Map2 = class {
1032
1287
  else this._map.on(event, cb);
1033
1288
  }
1034
1289
  addSource(id, source) {
1290
+ if (this._map.getSource(id)) {
1291
+ try {
1292
+ this._map.removeSource(id);
1293
+ } catch {
1294
+ }
1295
+ }
1035
1296
  this._map.addSource(id, source);
1036
1297
  }
1298
+ /** 安全移除 source(不存在时静默忽略) */
1299
+ removeSource(id) {
1300
+ if (this._map.getSource(id)) {
1301
+ try {
1302
+ this._map.removeSource(id);
1303
+ } catch {
1304
+ }
1305
+ }
1306
+ }
1037
1307
  getSource(id) {
1038
1308
  return this._map.getSource(id);
1039
1309
  }
@@ -1063,6 +1333,26 @@ var Map2 = class {
1063
1333
  ctrl.addTo(container);
1064
1334
  return ctrl;
1065
1335
  }
1336
+ /**
1337
+ * 挂载图例控件(通用):渲染数据驱动的色块/线段图例,说明专题图层语义。
1338
+ * 返回控件实例,可调用 .setItems() 随图层切换更新、.remove() 卸载。
1339
+ */
1340
+ addLegendControl(options) {
1341
+ const ctrl = new LegendControl(options);
1342
+ const container = this._map.getContainer();
1343
+ ctrl.addTo(container);
1344
+ return ctrl;
1345
+ }
1346
+ /**
1347
+ * 挂载地图导出控件(通用):点击按钮将当前视图导出为 PNG 下载。
1348
+ * 返回控件实例,可调用 .remove() 卸载。
1349
+ */
1350
+ addExportControl(options = {}) {
1351
+ const ctrl = new ExportControl(this._map, options);
1352
+ const container = this._map.getContainer();
1353
+ ctrl.addTo(container);
1354
+ return ctrl;
1355
+ }
1066
1356
  /**
1067
1357
  * 挂载辉光管线 Custom Layer(T6 / F-1.3)。
1068
1358
  * 传入 GeoJSON 线集合,叠加渲染管线/路网/水系辉光效果。
@@ -1071,7 +1361,10 @@ var Map2 = class {
1071
1361
  addGlowLayer(opts) {
1072
1362
  const layer = new CustomLineLayer(opts);
1073
1363
  this._map.addLayer(layer);
1074
- return layer.id;
1364
+ return {
1365
+ id: layer.id,
1366
+ setLines: (lines) => layer.setLines(lines)
1367
+ };
1075
1368
  }
1076
1369
  /**
1077
1370
  * 挂载 LOD 控制器(T7 / F-1.7)。
@@ -1083,6 +1376,24 @@ var Map2 = class {
1083
1376
  ctrl.evaluate(true);
1084
1377
  return ctrl;
1085
1378
  }
1379
+ /**
1380
+ * 启用 3D 地形渲染(F-1.5)。
1381
+ *
1382
+ * 注入 raster-dem 高程源并调用 maplibre-gl 原生 `setTerrain`,使地图呈现地形起伏。
1383
+ * DEM 瓦片默认使用公共 Terrarium 源(elevation-tiles-prod,Terrain-RGB 编码,支持 CORS),
1384
+ * 可在 opts.tiles 覆盖为私有瓦片服务(如 MinIO / 本地 MBTiles 服务)。
1385
+ *
1386
+ * @param opts.exaggeration 地形夸张系数(默认 1.5)
1387
+ * @param opts.tiles DEM 瓦片模板,{z}/{x}/{y} 占位
1388
+ * @param opts.encoding 'terrarium'(默认,Mapzen)| 'mapbox'
1389
+ */
1390
+ enableTerrain(opts = {}) {
1391
+ applyTerrain(this._map, opts);
1392
+ }
1393
+ /** 关闭 3D 地形渲染(F-1.5),并移除 DEM 源 */
1394
+ disableTerrain(sourceId = "cg-dem") {
1395
+ removeTerrain(this._map, sourceId);
1396
+ }
1086
1397
  get instance() {
1087
1398
  return this._map;
1088
1399
  }
@@ -1096,7 +1407,9 @@ Object.defineProperty(exports, "maplibregl", {
1096
1407
  exports.CACHEABLE_HOSTS = CACHEABLE_HOSTS;
1097
1408
  exports.CACHE_NAME = CACHE_NAME;
1098
1409
  exports.CustomLineLayer = CustomLineLayer;
1410
+ exports.ExportControl = ExportControl;
1099
1411
  exports.IdbTileStore = IdbTileStore;
1412
+ exports.LegendControl = LegendControl;
1100
1413
  exports.LodController = LodController;
1101
1414
  exports.MSG_AIRGAP = MSG_AIRGAP;
1102
1415
  exports.Map = Map2;
@@ -1108,6 +1421,7 @@ exports.ScaleControl = ScaleControl;
1108
1421
  exports.ThemeSwitcher = ThemeSwitcher;
1109
1422
  exports.WUHAN_CENTER = WUHAN_CENTER;
1110
1423
  exports.WUHAN_ZOOM = WUHAN_ZOOM;
1424
+ exports.WebGLUnavailableError = WebGLUnavailableError;
1111
1425
  exports.addTiandituBaseMap = addTiandituBaseMap;
1112
1426
  exports.buildGlowGeometry = buildGlowGeometry;
1113
1427
  exports.buildTiandituSources = buildTiandituSources;
@@ -1120,9 +1434,11 @@ exports.createTransformer = createTransformer;
1120
1434
  exports.default = src_default;
1121
1435
  exports.fromWgs84 = fromWgs84;
1122
1436
  exports.gcj02ToWgs84 = gcj02ToWgs84;
1437
+ exports.getGlobalConfig = getGlobalConfig;
1123
1438
  exports.glowPasses = glowPasses;
1124
1439
  exports.installServiceWorker = installServiceWorker;
1125
1440
  exports.isInChina = isInChina;
1441
+ exports.isWebGLAvailable = isWebGLAvailable;
1126
1442
  exports.offlineGeoJSONSource = offlineGeoJSONSource;
1127
1443
  exports.offlineSourceTiles = offlineSourceTiles;
1128
1444
  exports.offlineTileUrl = offlineTileUrl;
@@ -1133,12 +1449,16 @@ exports.parseOfflineUrl = parseOfflineUrl;
1133
1449
  exports.projectSimple = projectSimple;
1134
1450
  exports.registerOfflineProtocol = registerOfflineProtocol;
1135
1451
  exports.registerOfflineServiceWorker = registerOfflineServiceWorker;
1452
+ exports.removeSourceSafe = removeSourceSafe;
1453
+ exports.removeSourcesSafe = removeSourcesSafe;
1454
+ exports.renderLegendHtml = renderLegendHtml;
1136
1455
  exports.resolveCacheKey = resolveCacheKey;
1137
1456
  exports.resolveFromStore = resolveFromStore;
1138
1457
  exports.resolveLod = resolveLod;
1139
1458
  exports.resolveResponse = resolveResponse;
1140
1459
  exports.setAirgap = setAirgap;
1141
1460
  exports.setCgcs2000GridShift = setCgcs2000GridShift;
1461
+ exports.setGlobalConfig = setGlobalConfig;
1142
1462
  exports.shouldCache = shouldCache;
1143
1463
  exports.suggestDensity = suggestDensity;
1144
1464
  exports.themeFromStyle = themeFromStyle;
@@ -1148,6 +1468,8 @@ exports.tileKey = tileKey;
1148
1468
  exports.toWgs84 = toWgs84;
1149
1469
  exports.transformBounds = transformBounds;
1150
1470
  exports.transformPoint = transformPoint;
1471
+ exports.triggerDownload = triggerDownload;
1472
+ exports.upsertSource = upsertSource;
1151
1473
  exports.wgs84ToCgcs2000 = wgs84ToCgcs2000;
1152
1474
  exports.wgs84ToGcj02 = wgs84ToGcj02;
1153
1475
  //# sourceMappingURL=index.cjs.map