@movk/maplibre 1.1.0 → 1.2.0

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/module.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
1
+ import { NuxtModule } from '@nuxt/schema';
2
2
  export * from '../dist/runtime/types/index.js';
3
3
 
4
4
  interface ModuleOptions {
@@ -19,9 +19,11 @@ interface ModuleOptions {
19
19
  pmtiles?: boolean;
20
20
  /** 注册 `cog://` 协议,依赖 @geomatico/maplibre-cog-protocol */
21
21
  cog?: boolean;
22
+ /** 注册 `zzts://` 协议(ZZTS 二维动态切片服务),无额外依赖;需自定义请求时改为自行调用 `registerZztsProtocol` */
23
+ zzts?: boolean;
22
24
  };
23
25
  }
24
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
26
+ declare const module$1: NuxtModule<ModuleOptions, ModuleOptions, false>;
25
27
 
26
- export { _default as default };
28
+ export { module$1 as default };
27
29
  export type { ModuleOptions };
package/dist/module.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
1
+ import { NuxtModule } from '@nuxt/schema';
2
2
  export * from '../dist/runtime/types/index.js';
3
3
 
4
4
  interface ModuleOptions {
@@ -19,9 +19,11 @@ interface ModuleOptions {
19
19
  pmtiles?: boolean;
20
20
  /** 注册 `cog://` 协议,依赖 @geomatico/maplibre-cog-protocol */
21
21
  cog?: boolean;
22
+ /** 注册 `zzts://` 协议(ZZTS 二维动态切片服务),无额外依赖;需自定义请求时改为自行调用 `registerZztsProtocol` */
23
+ zzts?: boolean;
22
24
  };
23
25
  }
24
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
26
+ declare const module$1: NuxtModule<ModuleOptions, ModuleOptions, false>;
25
27
 
26
- export { _default as default };
28
+ export { module$1 as default };
27
29
  export type { ModuleOptions };
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@movk/maplibre",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "configKey": "maplibre",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -3,7 +3,7 @@ import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addP
3
3
  import { defu } from 'defu';
4
4
 
5
5
  const name = "@movk/maplibre";
6
- const version = "1.1.0";
6
+ const version = "1.2.0";
7
7
 
8
8
  const OPTIONAL_DEPS = ["lottie-web", "pmtiles", "@geomatico/maplibre-cog-protocol", "maplibre-contour"];
9
9
  const module$1 = defineNuxtModule({
@@ -45,7 +45,8 @@ const module$1 = defineNuxtModule({
45
45
  });
46
46
  const protocols = [
47
47
  options.protocols?.pmtiles && { path: "./runtime/utils/pmtiles", register: "registerPmtilesProtocol" },
48
- options.protocols?.cog && { path: "./runtime/utils/cog", register: "registerCogProtocol" }
48
+ options.protocols?.cog && { path: "./runtime/utils/cog", register: "registerCogProtocol" },
49
+ options.protocols?.zzts && { path: "./runtime/utils/zzts", register: "registerZztsProtocol" }
49
50
  ].filter((item) => !!item);
50
51
  if (protocols.length) {
51
52
  addPluginTemplate({
@@ -0,0 +1,54 @@
1
+ /** 经纬度包围盒 [west, south, east, north] */
2
+ export type LngLatBBox = [number, number, number, number];
3
+ export interface ZztsExtent {
4
+ xmin: number;
5
+ ymin: number;
6
+ xmax: number;
7
+ ymax: number;
8
+ }
9
+ /** elements 接口返回的单个元素:一张按 extent 贴图的 GeoSOT 网格图片 */
10
+ export interface ZztsElement {
11
+ id: string;
12
+ type: string;
13
+ url: string;
14
+ extent: ZztsExtent;
15
+ }
16
+ /** media 接口返回的图层元数据(WGS84) */
17
+ export interface ZztsMedia {
18
+ name: string;
19
+ extent: ZztsExtent;
20
+ minZoom: number;
21
+ maxZoom: number;
22
+ /** 原始影像分辨率(米) */
23
+ resolution: number;
24
+ }
25
+ export interface TileCoord {
26
+ z: number;
27
+ x: number;
28
+ y: number;
29
+ }
30
+ /** 源矩形为元素图片的比例坐标 [x, y, w, h](0-1),目标矩形为瓦片像素坐标 */
31
+ export interface ZztsDraw {
32
+ source: [number, number, number, number];
33
+ target: [number, number, number, number];
34
+ }
35
+ export declare const ZZTS_TILE_SIZE = 512;
36
+ export declare function parseZztsUrl(url: string): {
37
+ mediaUrl: string;
38
+ tile?: TileCoord;
39
+ };
40
+ export declare function tileBBox({ z, x, y }: TileCoord): LngLatBBox;
41
+ /** 瓦片在给定纬度处的比例尺分母,与公司 Cesium 组件的取值口径一致,保证服务端选出相同的网格级别 */
42
+ export declare function tileScale(z: number, lat: number): number;
43
+ export declare function zztsTileJson(url: string, media: ZztsMedia): {
44
+ tiles: string[];
45
+ bounds: LngLatBBox;
46
+ minzoom: number;
47
+ maxzoom: number;
48
+ tileSize: number;
49
+ name: string;
50
+ };
51
+ /** `coarser` 为相对瓦片默认级别向上取粗的级数,每级比例尺翻倍,服务端随之降一级 GeoSOT 网格 */
52
+ export declare function elementsUrl(mediaUrl: string, tile: TileCoord, coarser?: number): string;
53
+ /** 规划元素图片在瓦片上的绘制:经向线性;纬向在低级别按等高条带分段,逐段换算墨卡托以消除拉伸误差 */
54
+ export declare function planDraws(extent: ZztsExtent, tile: TileCoord): ZztsDraw[];
@@ -0,0 +1,85 @@
1
+ export const ZZTS_TILE_SIZE = 512;
2
+ const PROTOCOL_PREFIX = "zzts://";
3
+ const TILE_HASH = /#(\d+)\/(\d+)\/(\d+)$/;
4
+ const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
5
+ const METERS_PER_PIXEL_TO_SCALE = 96 / 0.0254;
6
+ const MAX_BBOX_SPAN = 20;
7
+ const LINEAR_ZOOM = 8;
8
+ const STRIP_HEIGHT = 32;
9
+ export function parseZztsUrl(url) {
10
+ const raw = url.startsWith(PROTOCOL_PREFIX) ? url.slice(PROTOCOL_PREFIX.length) : url;
11
+ const match = raw.match(TILE_HASH);
12
+ if (!match) return { mediaUrl: raw };
13
+ return {
14
+ mediaUrl: raw.slice(0, match.index),
15
+ tile: { z: Number(match[1]), x: Number(match[2]), y: Number(match[3]) }
16
+ };
17
+ }
18
+ const lngAt = (x, z) => x / 2 ** z * 360 - 180;
19
+ function snapOut(x, y, w, h) {
20
+ const left = Math.floor(x);
21
+ const top = Math.floor(y);
22
+ return [left, top, Math.ceil(x + w) - left, Math.ceil(y + h) - top];
23
+ }
24
+ const latAt = (y, z) => Math.atan(Math.sinh(Math.PI * (1 - 2 * y / 2 ** z))) * 180 / Math.PI;
25
+ const rowAt = (lat, z) => (1 - Math.asinh(Math.tan(lat * Math.PI / 180)) / Math.PI) / 2 * 2 ** z;
26
+ export function tileBBox({ z, x, y }) {
27
+ return [lngAt(x, z), latAt(y + 1, z), lngAt(x + 1, z), latAt(y, z)];
28
+ }
29
+ export function tileScale(z, lat) {
30
+ const metersPerPixel = EARTH_CIRCUMFERENCE * Math.cos(lat * Math.PI / 180) / (ZZTS_TILE_SIZE * 2 ** z);
31
+ return metersPerPixel * METERS_PER_PIXEL_TO_SCALE;
32
+ }
33
+ export function zztsTileJson(url, media) {
34
+ const { xmin, ymin, xmax, ymax } = media.extent;
35
+ const centerLat = (ymin + ymax) / 2;
36
+ const minzoom = Math.ceil(Math.log2(360 / MAX_BBOX_SPAN));
37
+ const nativeZoom = Math.ceil(Math.log2(EARTH_CIRCUMFERENCE * Math.cos(centerLat * Math.PI / 180) / (ZZTS_TILE_SIZE * media.resolution)));
38
+ return {
39
+ tiles: [`${url}#{z}/{x}/{y}`],
40
+ bounds: [xmin, ymin, xmax, ymax],
41
+ minzoom,
42
+ maxzoom: Math.max(minzoom, nativeZoom),
43
+ tileSize: ZZTS_TILE_SIZE,
44
+ name: media.name
45
+ };
46
+ }
47
+ export function elementsUrl(mediaUrl, tile, coarser = 0) {
48
+ const [west, south, east, north] = tileBBox(tile);
49
+ const url = new URL(mediaUrl);
50
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/elements`;
51
+ url.searchParams.set("width", String(ZZTS_TILE_SIZE));
52
+ url.searchParams.set("height", String(ZZTS_TILE_SIZE));
53
+ url.searchParams.set("scale", String(tileScale(tile.z, (south + north) / 2) * 2 ** coarser));
54
+ url.searchParams.set("bbox", [west, south, east, north].join(","));
55
+ return url.toString();
56
+ }
57
+ export function planDraws(extent, tile) {
58
+ const [west, south, east, north] = tileBBox(tile);
59
+ const left = Math.max(extent.xmin, west);
60
+ const right = Math.min(extent.xmax, east);
61
+ const bottom = Math.max(extent.ymin, south);
62
+ const top = Math.min(extent.ymax, north);
63
+ if (left >= right || bottom >= top) return [];
64
+ const { z, x, y } = tile;
65
+ const width = extent.xmax - extent.xmin;
66
+ const height = extent.ymax - extent.ymin;
67
+ const sx = (left - extent.xmin) / width;
68
+ const sw = (right - left) / width;
69
+ const dx = ((left + 180) / 360 * 2 ** z - x) * ZZTS_TILE_SIZE;
70
+ const dw = (right - left) / 360 * 2 ** z * ZZTS_TILE_SIZE;
71
+ const dyTop = (rowAt(top, z) - y) * ZZTS_TILE_SIZE;
72
+ const dyBottom = (rowAt(bottom, z) - y) * ZZTS_TILE_SIZE;
73
+ const strips = z >= LINEAR_ZOOM ? 1 : Math.max(1, Math.ceil((dyBottom - dyTop) / STRIP_HEIGHT));
74
+ const stripHeight = (dyBottom - dyTop) / strips;
75
+ const latAtPixel = (py) => latAt(y + py / ZZTS_TILE_SIZE, z);
76
+ return Array.from({ length: strips }, (_, i) => {
77
+ const dy = dyTop + i * stripHeight;
78
+ const latTop = i === 0 ? top : latAtPixel(dy);
79
+ const latBottom = i === strips - 1 ? bottom : latAtPixel(dy + stripHeight);
80
+ return {
81
+ source: [sx, (extent.ymax - latTop) / height, sw, (latTop - latBottom) / height],
82
+ target: snapOut(dx, dy, dw, stripHeight)
83
+ };
84
+ });
85
+ }
@@ -0,0 +1,14 @@
1
+ export interface ZztsProtocolOptions {
2
+ /** 自定义请求,元数据、元素列表与元素图片共用;用于附加鉴权头或改走代理 */
3
+ fetch?: typeof globalThis.fetch;
4
+ /**
5
+ * 元素图片缓存数量;相邻瓦片常共用同一网格图片,命中时不再重复下载
6
+ * @defaultValue 256
7
+ */
8
+ cacheSize?: number;
9
+ }
10
+ /**
11
+ * 注册 `zzts://` 协议(全局单例、幂等):把 ZZTS 二维动态切片服务适配为 512 墨卡托栅格瓦片。
12
+ * `MaplibreRasterLayer` 的 `url` 写成 `zzts://` + media 接口地址即可。
13
+ */
14
+ export declare function registerZztsProtocol(options?: ZztsProtocolOptions): void;
@@ -0,0 +1,72 @@
1
+ import { addProtocol } from "maplibre-gl";
2
+ import { defineGlobalSingleton } from "@movk/core";
3
+ import { ZZTS_TILE_SIZE, elementsUrl, parseZztsUrl, planDraws, zztsTileJson } from "./zzts-tile.js";
4
+ const EMPTY_TILE = new ArrayBuffer(0);
5
+ const MAX_FALLBACK_LEVELS = 3;
6
+ async function getJson(request, url, signal) {
7
+ const res = await request(url, { signal });
8
+ if (!res.ok) throw new Error(`ZZTS \u8BF7\u6C42\u5931\u8D25\uFF1AHTTP ${res.status}`);
9
+ return await res.json();
10
+ }
11
+ function imageElements(payload) {
12
+ if (!Array.isArray(payload.elements)) throw new Error(payload.msg || "ZZTS \u5143\u7D20\u8BF7\u6C42\u5931\u8D25");
13
+ return payload.elements.filter(
14
+ (element, i, list) => element.type === "image" && list.findIndex((item) => item.id === element.id) === i
15
+ );
16
+ }
17
+ function createImageCache(request, size) {
18
+ const cache = /* @__PURE__ */ new Map();
19
+ return (url) => {
20
+ const hit = cache.get(url);
21
+ if (hit) {
22
+ cache.delete(url);
23
+ cache.set(url, hit);
24
+ return hit;
25
+ }
26
+ const pending = request(url).then(async (res) => {
27
+ if (res.status === 404) return void 0;
28
+ if (!res.ok) throw new Error(`ZZTS \u56FE\u7247\u52A0\u8F7D\u5931\u8D25\uFF1AHTTP ${res.status}`);
29
+ return createImageBitmap(await res.blob());
30
+ });
31
+ pending.catch(() => cache.delete(url));
32
+ cache.set(url, pending);
33
+ if (cache.size > size) cache.delete(cache.keys().next().value);
34
+ return pending;
35
+ };
36
+ }
37
+ function compose(tile, items) {
38
+ const canvas = new OffscreenCanvas(ZZTS_TILE_SIZE, ZZTS_TILE_SIZE);
39
+ const context = canvas.getContext("2d");
40
+ for (const { element, image } of items) {
41
+ for (const { source: [sx, sy, sw, sh], target: [dx, dy, dw, dh] } of planDraws(element.extent, tile)) {
42
+ context.drawImage(image, sx * image.width, sy * image.height, sw * image.width, sh * image.height, dx, dy, dw, dh);
43
+ }
44
+ }
45
+ return canvas.transferToImageBitmap();
46
+ }
47
+ function createZztsProtocol({ fetch: request = globalThis.fetch.bind(globalThis), cacheSize = 256 } = {}) {
48
+ const loadImage = createImageCache(request, cacheSize);
49
+ async function loadLevel(mediaUrl, tile, coarser, signal) {
50
+ const elements = imageElements(await getJson(request, elementsUrl(mediaUrl, tile, coarser), signal));
51
+ const images = await Promise.all(elements.map((element) => loadImage(element.url).catch(() => void 0)));
52
+ const layers = elements.flatMap((element, i) => images[i] ? [{ element, image: images[i] }] : []);
53
+ if (layers.length === elements.length || coarser >= MAX_FALLBACK_LEVELS) return layers;
54
+ return [...await loadLevel(mediaUrl, tile, coarser + 1, signal), ...layers];
55
+ }
56
+ return async ({ url }, { signal }) => {
57
+ const { mediaUrl, tile } = parseZztsUrl(url);
58
+ if (!tile) {
59
+ const media = await getJson(request, mediaUrl, signal);
60
+ return { data: zztsTileJson(url, media) };
61
+ }
62
+ const layers = await loadLevel(mediaUrl, tile, 0, signal);
63
+ signal.throwIfAborted();
64
+ return { data: layers.length ? compose(tile, layers) : EMPTY_TILE };
65
+ };
66
+ }
67
+ export function registerZztsProtocol(options) {
68
+ defineGlobalSingleton("movk-maplibre:protocol:zzts", () => {
69
+ addProtocol("zzts", createZztsProtocol(options));
70
+ return true;
71
+ });
72
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@movk/maplibre",
3
3
  "type": "module",
4
- "version": "1.1.0",
5
- "packageManager": "pnpm@12.6.0",
4
+ "version": "1.2.0",
5
+ "packageManager": "pnpm@12.10.1",
6
6
  "description": "声明式 MapLibre GL 封装库:MaplibreMap/Source/Layer 等组件与 composables,原生支持 Nuxt 4 模块,并通过 Vite 插件在纯 Vue + Vite 项目中可用。无需 access token,内置 3D 建筑、雷达/扩散/辉光等动态效果,sky/terrain 环境,绘制(terra-draw)、天地图、WMS/WMTS 与多坐标系本土化支持。",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
8
8
  "license": "MIT",
@@ -155,28 +155,28 @@
155
155
  "devDependencies": {
156
156
  "@geomatico/maplibre-cog-protocol": "^0.10.0",
157
157
  "@nuxt/eslint-config": "^1.17.0",
158
- "@nuxt/kit": "^4.5.2",
158
+ "@nuxt/kit": "^4.6.0",
159
159
  "@nuxt/module-builder": "^1.0.3",
160
- "@nuxt/schema": "^4.5.2",
160
+ "@nuxt/schema": "^4.6.0",
161
161
  "@release-it/conventional-changelog": "^12.0.2",
162
162
  "@types/geojson": "^7946.0.16",
163
163
  "@vitejs/plugin-vue": "^6.0.9",
164
164
  "@vue/test-utils": "^2.5.1",
165
- "eslint": "^10.11.0",
166
- "happy-dom": "^20.14.5",
165
+ "eslint": "^10.12.0",
166
+ "happy-dom": "^20.14.6",
167
167
  "lottie-web": "^5.13.0",
168
168
  "maplibre-contour": "^0.1.1",
169
- "maplibre-gl": "^6.11.2",
170
- "nuxt": "^4.5.2",
169
+ "maplibre-gl": "^6.13.0",
170
+ "nuxt": "^4.6.0",
171
171
  "pmtiles": "^4.5.0",
172
- "release-it": "^21.1.0",
173
- "terra-draw": "^1.35.0",
174
- "terra-draw-maplibre-gl-adapter": "^1.4.1",
172
+ "release-it": "^21.1.1",
173
+ "terra-draw": "^1.37.0",
174
+ "terra-draw-maplibre-gl-adapter": "^1.5.0",
175
175
  "typescript": "^6.0.3",
176
176
  "unbuild": "^3.6.1",
177
177
  "unplugin": "^3.4.0",
178
- "vitest": "^5.0.2",
178
+ "vitest": "^5.0.3",
179
179
  "vue": "^3.5.43",
180
- "vue-tsc": "^3.3.11"
180
+ "vue-tsc": "^3.3.12"
181
181
  }
182
182
  }