├── README.md ├── packages ├── libs │ ├── src │ │ ├── utils │ │ │ ├── mod.ts │ │ │ └── math.ts │ │ ├── dbf │ │ │ ├── mod.ts │ │ │ ├── utils.ts │ │ │ ├── field.ts │ │ │ ├── dbf.ts │ │ │ └── record.ts │ │ ├── geo │ │ │ ├── mod.ts │ │ │ ├── utils.ts │ │ │ ├── area.ts │ │ │ ├── projection.ts │ │ │ ├── cgcs.ts │ │ │ ├── xyz.ts │ │ │ └── types.ts │ │ ├── filetype.ts │ │ ├── base64.ts │ │ ├── array.ts │ │ └── ovmap.ts │ ├── README.md │ ├── tests │ │ ├── dbf │ │ │ ├── data │ │ │ │ ├── 边界文件.dbf │ │ │ │ ├── arcmap_gen.dbf │ │ │ │ ├── null_value.dbf │ │ │ │ └── arcmap创建的dbf文件说明.docx │ │ │ ├── record_test.ts │ │ │ ├── field_test.ts │ │ │ └── dbf_test.ts │ │ ├── base64_test.ts │ │ ├── math_test.ts │ │ ├── ovmap_test.ts │ │ ├── geo │ │ │ ├── xyz_test.ts │ │ │ ├── cgcs_test.ts │ │ │ ├── projection_test.ts │ │ │ └── types_test.ts │ │ └── array_test.ts │ ├── deno.json │ └── deno.lock ├── create-shp │ ├── src │ │ ├── source │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ └── csv.ts │ │ ├── index.ts │ │ ├── shp.ts │ │ ├── bjwj.ts │ │ └── dbf.ts │ ├── deno.json │ ├── tests │ │ ├── bjwj_test.ts │ │ ├── csv_test.ts │ │ └── shp_test.ts │ ├── .gitignore │ └── deno.lock ├── capgen │ ├── src │ │ ├── index.ts │ │ ├── cangen.ts │ │ ├── template.ts │ │ └── matrix.ts │ ├── README.md │ ├── deno.json │ ├── LICENSE │ ├── deno.lock │ └── .gitignore └── webp_to_png │ ├── deno.json │ ├── src │ ├── bundle.ts │ ├── main.ts │ └── main_test.ts │ ├── README.md │ ├── bench │ └── bench.ts │ └── deno.lock ├── crates ├── google_earth │ ├── src │ │ ├── key.bin │ │ ├── lib.rs │ │ └── decrypt.rs │ ├── tests │ │ ├── test_data │ │ │ └── f1-021-i.1016 │ │ └── test_decrypt.rs │ └── Cargo.toml ├── wasm_webp_to_png │ ├── .gitignore │ ├── src │ │ ├── lib.rs │ │ └── utils.rs │ ├── Cargo.toml │ ├── LICENSE_MIT │ ├── README.md │ └── LICENSE_APACHE └── webp_to_png │ ├── src │ └── lib.rs │ ├── Cargo.toml │ └── tests │ └── test_wep_to_png.rs ├── Cargo.toml ├── LICENSE ├── .github └── workflows │ └── publish.yml ├── .gitignore └── Cargo.lock /README.md: -------------------------------------------------------------------------------- 1 | # Collection Of My Libraries 2 | 3 | -------------------------------------------------------------------------------- /packages/libs/src/utils/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./math.ts"; 2 | -------------------------------------------------------------------------------- /packages/create-shp/src/source/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./csv.ts"; 2 | -------------------------------------------------------------------------------- /packages/capgen/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./matrix.ts"; 2 | export * from "./cangen.ts"; 3 | -------------------------------------------------------------------------------- /packages/libs/src/dbf/mod.ts: -------------------------------------------------------------------------------- 1 | export { DBF } from "./dbf.ts"; 2 | export { Field } from "./field.ts"; 3 | -------------------------------------------------------------------------------- /crates/google_earth/src/key.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/crates/google_earth/src/key.bin -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /packages/libs/README.md: -------------------------------------------------------------------------------- 1 | `libs/geo`: Geographically related library functions 2 | 3 | `libs/dbf`: Dbf databse file -------------------------------------------------------------------------------- /crates/google_earth/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod decrypt; 2 | 3 | pub use decrypt::{decrypt_data, decrypt_data_with_default_key}; 4 | -------------------------------------------------------------------------------- /packages/libs/tests/dbf/data/边界文件.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/packages/libs/tests/dbf/data/边界文件.dbf -------------------------------------------------------------------------------- /packages/create-shp/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bjwj.ts"; 2 | export * from "./shp.ts"; 3 | export * from "./source/index.ts"; 4 | -------------------------------------------------------------------------------- /packages/capgen/README.md: -------------------------------------------------------------------------------- 1 | # capgen 2 | 3 | ![](https://jsr.io/badges/@liuxspro/capgen) 4 | 5 | generate a simple wmts capabilities 6 | -------------------------------------------------------------------------------- /packages/libs/tests/dbf/data/arcmap_gen.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/packages/libs/tests/dbf/data/arcmap_gen.dbf -------------------------------------------------------------------------------- /packages/libs/tests/dbf/data/null_value.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/packages/libs/tests/dbf/data/null_value.dbf -------------------------------------------------------------------------------- /crates/google_earth/tests/test_data/f1-021-i.1016: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/crates/google_earth/tests/test_data/f1-021-i.1016 -------------------------------------------------------------------------------- /packages/libs/tests/dbf/data/arcmap创建的dbf文件说明.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/libs/main/packages/libs/tests/dbf/data/arcmap创建的dbf文件说明.docx -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/webp_to_png", "crates/wasm_webp_to_png", "crates/google_earth"] 4 | 5 | 6 | [workspace.dependencies] 7 | -------------------------------------------------------------------------------- /packages/libs/src/geo/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./projection.ts"; 2 | export * from "./cgcs.ts"; 3 | export * from "./area.ts"; 4 | export * from "./types.ts"; 5 | export * from "./xyz.ts"; 6 | -------------------------------------------------------------------------------- /crates/google_earth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "google_earth" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [dev-dependencies] 9 | file-format = "=0.27.0" 10 | -------------------------------------------------------------------------------- /packages/libs/tests/base64_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { decode, encode } from "../src/base64.ts"; 3 | 4 | Deno.test("base64 encode and decode", () => { 5 | const s = "hello 你好 💖"; 6 | assertEquals(encode(s), "aGVsbG8g5L2g5aW9IPCfkpY="); 7 | assertEquals(decode(encode(s)), s); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/capgen/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@liuxspro/capgen", 3 | "version": "0.2.2", 4 | "license": "MIT", 5 | "exports": "./src/index.ts", 6 | "imports": { 7 | "@es-toolkit/es-toolkit": "jsr:@es-toolkit/es-toolkit@^1.38.0", 8 | "@std/assert": "jsr:@std/assert@1", 9 | "minijinja-js": "npm:minijinja-js@^2.9.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | use webp_to_png::webp_to_png as webp_to_png_lib; 6 | 7 | #[wasm_bindgen] 8 | pub fn webp_to_png(webp_data: Vec) -> Result, JsValue> { 9 | utils::set_panic_hook(); // 设置更好的错误信息 10 | webp_to_png_lib(webp_data).map_err(|e| JsValue::from_str(&e.to_string())) 11 | } 12 | -------------------------------------------------------------------------------- /packages/webp_to_png/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@liuxspro/webp-to-png", 3 | "version": "0.0.3", 4 | "license": "MIT", 5 | "exports": "./src/main.ts", 6 | "tasks": { 7 | "build": "deno run -A src/bundle.ts", 8 | "bench": "deno bench --allow-ffi" 9 | }, 10 | "imports": { 11 | "@std/assert": "jsr:@std/assert@1" 12 | }, 13 | "publish": { 14 | "include": ["README.md", "./src/main.ts", "./src/wasm_webp_to_png.js"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/libs/tests/dbf/record_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { record_to_uint8array } from "../../src/dbf/record.ts"; 3 | import { Field } from "../../src/dbf/mod.ts"; 4 | 5 | Deno.test(function test_record_to_uint8array() { 6 | const char = new Field("DMMC", "C", 10); 7 | const num = new Field("NUM", "N", 10); 8 | const data = record_to_uint8array([char, num], ["DKMC", 12]); 9 | assertEquals(data.length, 21); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/libs/tests/math_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { round_to } from "../src/utils/math.ts"; 3 | 4 | Deno.test("Math: round_to", () => { 5 | assertEquals(round_to(1.234, 2), 1.23); 6 | assertEquals(round_to(2.55, 1), 2.6); 7 | assertEquals(round_to(1.55, 1), 1.6); 8 | assertEquals(round_to(-2.675, 2), -2.68); 9 | assertEquals(round_to(-0, 2), -0); 10 | assertEquals(round_to(0, 2), 0); 11 | assertEquals(round_to(9.8250, 2), 9.83); 12 | }); 13 | -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/libs/tests/ovmap_test.ts: -------------------------------------------------------------------------------- 1 | // import { assertEquals } from "jsr:@std/assert"; 2 | import { OVMap, type OVMapOptions } from "../src/ovmap.ts"; 3 | 4 | Deno.test("ovmap", () => { 5 | const url = 6 | "https://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={$x}&y={$y}&z={$z}"; 7 | const options: OVMapOptions = { 8 | crs: "EPSG:3857", 9 | format: "JPG", 10 | }; 11 | const map = new OVMap(300, "高德地图", url, options); 12 | // console.log(map); 13 | console.log(map.qr_data); 14 | }); 15 | -------------------------------------------------------------------------------- /crates/webp_to_png/src/lib.rs: -------------------------------------------------------------------------------- 1 | use image::ImageFormat; 2 | use std::io::Cursor; 3 | 4 | pub fn webp_to_png(webp_data: Vec) -> Result, image::ImageError> { 5 | // 使用 Cursor 将 Vec 转换为可读的流 6 | let reader = Cursor::new(webp_data); 7 | // 解码 WebP 图片 8 | let img = image::load(reader, ImageFormat::WebP)?; 9 | // 创建一个 Vec 来存储 PNG 数据 10 | let mut png_data = Vec::new(); 11 | // 将图片保存为 PNG 格式 12 | img.write_to(&mut Cursor::new(&mut png_data), ImageFormat::Png)?; 13 | 14 | Ok(png_data) 15 | } 16 | -------------------------------------------------------------------------------- /crates/webp_to_png/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webp_to_png" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | image = { version = "0.25.5", default-features = false, features = [ 8 | "webp", 9 | "png", 10 | ] } 11 | 12 | # https://doc.rust-lang.org/cargo/reference/profiles.html 13 | [profile.release] 14 | codegen-units = 1 # 控制代码生成的单元数量 15 | lto = true # 启用链接时优化, 减少生成的二进制文件的大小并提高性能 16 | opt-level = "s" # 设置优化级别 17 | panic = "unwind" # 控制 panic 时的行为 unwind: 在 panic 时展开堆栈 18 | strip = true # 控制是否剥离生成的二进制文件中的符号信息 19 | -------------------------------------------------------------------------------- /packages/libs/src/geo/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将角度转换为弧度。 3 | * @param degree 角度值 4 | * @returns 弧度值 5 | */ 6 | export function degree_to_radius(degree: number): number { 7 | return (degree * Math.PI) / 180; 8 | } 9 | 10 | /** 11 | * 将弧度转换为角度。 12 | * @param radius 弧度值 13 | * @returns 角度值 14 | */ 15 | export function radius_to_degree(radius: number): number { 16 | return (radius * 180) / Math.PI; 17 | } 18 | 19 | /** 20 | * 将角度转换为弧度的别名函数。 21 | */ 22 | export const d2r = degree_to_radius; 23 | 24 | /** 25 | * 将弧度转换为角度的别名函数。 26 | */ 27 | export const r2d = radius_to_degree; 28 | -------------------------------------------------------------------------------- /packages/webp_to_png/src/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * using [@libs/bundle](https://jsr.io/@libs/bundle/doc/wasm) to bundle wasm 3 | */ 4 | import { bundle } from "jsr:@libs/bundle/wasm"; 5 | import { dirname, join } from "jsr:@std/path"; 6 | 7 | const js_file_name = "wasm_webp_to_png.js"; 8 | const root_path = dirname(dirname(Deno.cwd())); 9 | const project_path = join(root_path, "crates/wasm_webp_to_png"); 10 | const src_path = join(Deno.cwd(), "src"); 11 | 12 | await bundle(project_path); 13 | 14 | // move to src 15 | await Deno.rename( 16 | join(project_path, js_file_name), 17 | join(src_path, js_file_name), 18 | ); 19 | -------------------------------------------------------------------------------- /packages/webp_to_png/src/main.ts: -------------------------------------------------------------------------------- 1 | import init, { webp_to_png as wasm_webp_to_png } from "./wasm_webp_to_png.js"; 2 | 3 | /** 4 | * Convert a webp image to png. 5 | * 6 | * @param data Webp data Uint8Array 7 | * @returns PNG data as Promised Uint8Array 8 | * 9 | * ## Example 10 | * 11 | * ```ts 12 | * import { webp_to_png } from "jsr:@liuxspro/webp-to-png"; 13 | * 14 | * const webp_data = new Uint8Array([...]) 15 | * console.log(await webp_to_png(webp_data)) 16 | * ``` 17 | */ 18 | export async function webp_to_png(data: Uint8Array): Promise { 19 | await init(); 20 | return wasm_webp_to_png(data); 21 | } 22 | -------------------------------------------------------------------------------- /packages/create-shp/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@liuxspro/create-shp", 3 | "version": "0.1.2", 4 | "license": "MIT", 5 | "tasks": { "test-publish": "deno publish --dry-run --allow-dirty" }, 6 | "exports": "./src/index.ts", 7 | "publish": { "exclude": [".github", "tests"] }, 8 | "lint": { 9 | "rules": { 10 | "exclude": ["no-import-prefix", "no-unversioned-import"] 11 | } 12 | }, 13 | "imports": { 14 | "@liuxspro/libs": "jsr:@liuxspro/libs@^0.1.9", 15 | "@mapbox/shp-write": "npm:@mapbox/shp-write@^0.4.3", 16 | "jszip": "npm:jszip@^3.10.1", 17 | "papaparse": "npm:papaparse@^5.5.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/libs/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@liuxspro/libs", 3 | "version": "0.1.9", 4 | "license": "MIT", 5 | "tasks": { 6 | "test-publish": "deno publish --dry-run --allow-dirty" 7 | }, 8 | "exports": { 9 | "./utils": "./src/utils/mod.ts", 10 | "./array": "./src/array.ts", 11 | "./base64": "./src/base64.ts", 12 | "./dbf": "./src/dbf/mod.ts", 13 | "./filetype": "./src/filetype.ts", 14 | "./geo": "./src/geo/mod.ts", 15 | "./ovmap": "./src/ovmap.ts" 16 | }, 17 | "publish": { "exclude": [".github", "tests"] }, 18 | "lint": { 19 | "rules": { 20 | "exclude": ["no-import-prefix", "no-unversioned-import"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-shp/src/source/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Point } from "@liuxspro/libs/geo"; 2 | import { get_digits } from "@liuxspro/libs/utils"; 3 | 4 | /** 5 | * 纠正投影坐标顺序 6 | * proj4 坐标顺序是[东坐标(加带号8位数) , 北坐标(7位)] 7 | * 8 | * 约定X为东坐标(横坐标,需加带号) 9 | * 10 | * 约定Y为北坐标(纵坐标,为恒为正的7位数) 11 | * @param {Point} point 点坐标 12 | * @returns points 13 | */ 14 | export function correct_points_order(point: Point): Point { 15 | const x = point[0]; 16 | const y = point[1]; 17 | let real_x; 18 | let real_y; 19 | if (get_digits(x) == 7) { 20 | // X 为7位数 21 | real_x = y; 22 | real_y = x; 23 | } else { 24 | real_x = x; 25 | real_y = y; 26 | } 27 | return [real_x, real_y]; 28 | } 29 | -------------------------------------------------------------------------------- /packages/libs/tests/geo/xyz_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { XYZ } from "../../src/geo/xyz.ts"; 3 | 4 | Deno.test("Geo: Class XYZ", () => { 5 | const x = 211; 6 | const y = 100; 7 | const z = 8; 8 | const xyz = new XYZ(x, y, z); 9 | const lonlat: [number, number] = [116.71875, 36.5978891330702]; 10 | assertEquals(xyz.to_lonlat(), lonlat); 11 | assertEquals(XYZ.from_lonlat(...lonlat, z), xyz); 12 | 13 | const xyz2 = XYZ.from_xyz(469, 171, 9); 14 | const lonlat2: [number, number] = [150.170857, 50.949096]; 15 | assertEquals(xyz2.to_lonlat(), [149.765625, 51.17934297928927]); 16 | assertEquals(XYZ.from_lonlat(...lonlat2, 9), xyz2); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/webp_to_png/README.md: -------------------------------------------------------------------------------- 1 | # Convert a webp image to png 2 | 3 | [![JSR](https://jsr.io/badges/@liuxspro/webp-to-png)](https://jsr.io/badges/@liuxspro/webp-to-png) 4 | 5 | using [image](https://github.com/image-rs/image) to convert webp image 6 | using [wasm-pack](https://rustwasm.github.io/wasm-pack/) compiled to wasm 7 | using [@libs/bundle](https://jsr.io/@libs/bundle) to bundle wasm 8 | 9 | ## Usage 10 | 11 | ```ts 12 | import { webp_to_png } from "jsr:@liuxspro/webp-to-png"; 13 | const webp_data = new Uint8Array([...]) 14 | console.log(await webp_to_png(webp_data)) 15 | ``` 16 | 17 | ## Build 18 | 19 | Build and bundle wasm 20 | 21 | ```bash 22 | deno task build 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /packages/libs/tests/array_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { array_is_equal, mergeUint8Arrays } from "../src/array.ts"; 3 | 4 | Deno.test("mergeUint8Arrays", () => { 5 | const a = new Uint8Array([1, 2]); 6 | const b = Uint8Array.of(3); 7 | const c = new Uint8Array([4]); 8 | const m = mergeUint8Arrays([a, b, c]); 9 | assertEquals(m, new Uint8Array([1, 2, 3, 4])); 10 | }); 11 | 12 | Deno.test("array_is_equal", () => { 13 | const arr1 = new Uint8Array([1, 2, 3]); 14 | const arr2 = new Uint8Array([1, 2, 3]); 15 | const arr3 = new Uint8Array([1, 2, 4]); 16 | assertEquals(array_is_equal(arr1, arr2), true); 17 | assertEquals(array_is_equal(arr1, arr3), false); 18 | }); 19 | -------------------------------------------------------------------------------- /crates/google_earth/tests/test_decrypt.rs: -------------------------------------------------------------------------------- 1 | use file_format::FileFormat; 2 | use google_earth::decrypt_data; 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | #[test] 7 | fn test_file_decryption() { 8 | let input_file = "./tests/test_data/f1-021-i.1016"; 9 | if Path::new(input_file).exists() { 10 | let encrypted_data = fs::read(input_file).unwrap(); 11 | let data = decrypt_data(&encrypted_data, None).unwrap(); 12 | // let output_file = "./tests/a.jpg"; 13 | // fs::write(output_file, &data).unwrap(); 14 | let fmt = FileFormat::from_bytes(&data); 15 | assert_eq!(fmt, FileFormat::JointPhotographicExpertsGroup) 16 | } else { 17 | eprintln!("跳过文件测试: {} 不存在", input_file); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-shp/src/shp.ts: -------------------------------------------------------------------------------- 1 | import type { MultiPolygon } from "@liuxspro/libs/geo"; 2 | import { write } from "@mapbox/shp-write"; 3 | 4 | type ShpWriteResult = { 5 | shp: DataView; 6 | shx: DataView; 7 | dbf: DataView; 8 | prj: string; 9 | }; 10 | 11 | /** 12 | * 根据多多边形和字段对象创建shp文件 13 | * @param { MultiPolygon } multi_polygon 14 | * @param fields 15 | * @returns {Promise} 16 | */ 17 | export function create_shp( 18 | multi_polygon: MultiPolygon, 19 | fields: Record = {}, 20 | ): Promise { 21 | const coords = multi_polygon.coordinates; 22 | return new Promise((resolve, reject) => { 23 | write([fields], "POLYGON", [coords], (err, result) => { 24 | if (err) { 25 | reject(err); 26 | } else { 27 | resolve(result as ShpWriteResult); 28 | } 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_webp_to_png" 3 | version = "0.1.0" 4 | authors = ["liuxspro "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | webp_to_png = { path = "../webp_to_png" } 15 | wasm-bindgen = "0.2.84" 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.7", optional = true } 22 | 23 | [dev-dependencies] 24 | wasm-bindgen-test = "0.3.34" 25 | 26 | [profile.release] 27 | # Tell `rustc` to optimize for small code size. 28 | opt-level = "s" 29 | -------------------------------------------------------------------------------- /packages/libs/src/filetype.ts: -------------------------------------------------------------------------------- 1 | import { array_is_equal } from "./array.ts"; 2 | 3 | /** 判断数据是否为 PNG 格式 */ 4 | export function is_png(data: Uint8Array): boolean { 5 | // https://zh.wikipedia.org/wiki/PNG 6 | // deno-fmt-ignore 7 | const magic_number = new Uint8Array([ 8 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 9 | ]); 10 | return array_is_equal(data.slice(0, 8), magic_number); 11 | } 12 | 13 | /** 判断数据是否为 RIFF 格式 */ 14 | export function is_riff(data: Uint8Array): boolean { 15 | const header = new Uint8Array([0x52, 0x49, 0x46, 0x46]); // RIFF 16 | return array_is_equal(data.slice(0, 4), header); 17 | } 18 | 19 | /** 判断数据是否为 WEBP 格式 */ 20 | export function is_webp(data: Uint8Array): boolean { 21 | if (!is_riff(data)) { 22 | return false; 23 | } 24 | const webpHeader = new Uint8Array([0x57, 0x45, 0x42, 0x50]); // "WEBP" 25 | return array_is_equal(data.slice(8, 12), webpHeader); 26 | } 27 | -------------------------------------------------------------------------------- /packages/libs/src/dbf/utils.ts: -------------------------------------------------------------------------------- 1 | export type FieldTypeSign = "C" | "N" | "L" | "D"; 2 | export enum FieldType { 3 | string = "C", 4 | number = "N", 5 | boolean = "L", 6 | Date = "D", 7 | } 8 | 9 | export function encode_text(content: string): Uint8Array { 10 | const encoder = new TextEncoder(); 11 | return encoder.encode(content); 12 | } 13 | 14 | export function date_to_str(date: Date): string { 15 | const year = date.getFullYear(); 16 | const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份+1并补零 17 | const day = String(date.getDate()).padStart(2, "0"); // 日期补零 18 | const formattedDate = `${year}${month}${day}`; 19 | return formattedDate; 20 | } 21 | 22 | export function get_update_date(date: Date): Uint8Array { 23 | const year = date.getFullYear() - 1900; 24 | const month = date.getMonth() + 1; 25 | const day = date.getDate(); 26 | return new Uint8Array([year, month, day]); 27 | } 28 | -------------------------------------------------------------------------------- /packages/libs/src/geo/area.ts: -------------------------------------------------------------------------------- 1 | import type { Point } from "./types.ts"; 2 | 3 | /** 4 | * 使用鞋带公式(有向面积法)计算多边形面积 5 | * https://en.wikipedia.org/wiki/Shoelace_formula 6 | * 7 | * 该函数通过遍历多边形顶点坐标,应用鞋带公式计算有向面积。 8 | * 返回值的符号表示顶点排列顺序:正值表示**逆时针**顺序,负值表示**顺时针**顺序。 9 | * 10 | * @param points - 多边形顶点坐标数组,每个顶点为包含两个数字的元组 [x, y] 11 | * @returns 多边形的有向面积 正值表示**逆时针**顺序,负值表示**顺时针**顺序 12 | * 13 | * @example 14 | * ```typescript 15 | * // 计算三角形面积 16 | * const triangle = [[0,0], [4,0], [0,3]]; 17 | * calc_signed_area(triangle); // 返回 6 18 | * 19 | * // 计算矩形面积 20 | * const rectangle = [[1,1], [1,4], [5,4], [5,1]]; 21 | * calc_signed_area(rectangle); // 返回 12 22 | * ``` 23 | */ 24 | export function calc_signed_area(points: Point[]): number { 25 | let area = 0; 26 | const n = points.length; 27 | 28 | for (let i = 0; i < n; i++) { 29 | const j = (i + 1) % n; 30 | const [xi, yi] = points[i]; 31 | const [xj, yj] = points[j]; 32 | area += xi * yj - xj * yi; 33 | } 34 | return area / 2; 35 | } 36 | -------------------------------------------------------------------------------- /packages/libs/tests/geo/cgcs_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "jsr:@std/assert"; 2 | import { get_zone } from "../../src/geo/cgcs.ts"; 3 | import { calc_signed_area } from "../../src/geo/area.ts"; 4 | import type { Point } from "../../src/geo/types.ts"; 5 | 6 | Deno.test("Geo: get_zone", () => { 7 | assertEquals(get_zone(74.59), 25); 8 | assertEquals(get_zone(73.65), 25); 9 | assertThrows(() => get_zone(73.6), Error, "经度不在中国范围内(73.62~135)"); 10 | assertEquals(get_zone(135), 45); 11 | assertEquals(get_zone(76.53), 26); 12 | assertEquals(get_zone(117.19), 39); 13 | }); 14 | 15 | Deno.test("Geo: calc_signed_area", () => { 16 | // 正方形,面积16 17 | const square = [[0, 0], [4, 0], [4, 4], [0, 4]]; 18 | assertEquals(calc_signed_area(square as Point[]), 16); 19 | // 多边形,面积210.5 20 | const polygon = [[5, 0], [7, 10], [-2, 15], [-10, 2], [-5, -6], [5, 0]]; 21 | assertEquals(calc_signed_area(polygon as Point[]), 210.5); 22 | assertEquals(calc_signed_area(polygon.reverse() as Point[]), -210.5); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/libs/src/base64.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encodes a string to Base64 format using TextEncoder and btoa. 3 | * 4 | * @param content - The UTF-8 string to be encoded to Base64 5 | * @returns The Base64 encoded string representation of the input 6 | * 7 | * @example 8 | * ```typescript 9 | * encode("Hello World"); // Returns "SGVsbG8gV29ybGQ=" 10 | * ``` 11 | */ 12 | export function encode(content: string): string { 13 | const encoder = new TextEncoder(); 14 | const data = encoder.encode(content); 15 | return btoa(data.reduce((sum, arr) => sum + String.fromCharCode(arr), "")); 16 | } 17 | 18 | /** 19 | * Decodes a Base64 string back to its original UTF-8 string representation. 20 | * 21 | * @param base64_content - The Base64 encoded string to be decoded 22 | * @returns The original UTF-8 string decoded from Base64 23 | * 24 | * @example 25 | * ```typescript 26 | * decode("SGVsbG8gV29ybGQ="); // Returns "Hello World" 27 | * ``` 28 | */ 29 | export function decode(base64_content: string): string { 30 | return new TextDecoder().decode( 31 | Uint8Array.from(atob(base64_content), (c) => c.charCodeAt(0)), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/libs/src/geo/projection.ts: -------------------------------------------------------------------------------- 1 | import type { Point } from "./types.ts"; 2 | import { degree_to_radius, radius_to_degree } from "./utils.ts"; 3 | 4 | /** 5 | * 将WGS84坐标(经纬度)转换为墨卡托投影坐标。 6 | * @param coordinate [经度, 纬度] 7 | * @returns [x, y] 墨卡托投影坐标 8 | */ 9 | export function wgs84_to_mercator(coordinate: Point): Point { 10 | // https://en.wikipedia.org/wiki/Mercator_projection 11 | const [longitude, latitude] = coordinate; 12 | const R = 6378137; 13 | const x = R * degree_to_radius(longitude); 14 | const y = R * 15 | Math.log(Math.tan(Math.PI / 4 + degree_to_radius(latitude) / 2)); 16 | return [x, y]; 17 | } 18 | 19 | /** 20 | * 将墨卡托投影坐标转换为WGS84坐标(经纬度)。 21 | * @param coordinate [x, y] 墨卡托投影坐标 22 | * @returns [经度, 纬度] WGS84坐标 23 | */ 24 | export function mercator_to_wgs84(coordinate: Point): Point { 25 | // https://en.wikipedia.org/wiki/Mercator_projection 26 | const [x, y] = coordinate; 27 | const R = 6378137; 28 | const longitude = radius_to_degree(x / R); 29 | const latitude = radius_to_degree( 30 | 2 * Math.atan(Math.exp(y / R)) - Math.PI / 2, 31 | ); 32 | return [longitude, latitude]; 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 liuxspro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/capgen/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 liuxspro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | setup: 9 | name: Setup 10 | runs-on: ubuntu-latest 11 | outputs: 12 | packages: ${{ steps.packages.outputs.packages }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: denoland/setup-deno@v2 16 | - name: List packages 17 | id: packages 18 | run: | 19 | deno eval 'console.log(JSON.stringify([...Deno.readDirSync(`./packages`)].map((i) => i.name)))' 20 | echo "packages=$(deno eval 'console.log(JSON.stringify([...Deno.readDirSync(`./packages`)].map((i) => i.name)))')" >> $GITHUB_OUTPUT 21 | 22 | publish: 23 | name: Publish 24 | needs: setup 25 | runs-on: ubuntu-latest 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | package: ${{ fromJson(needs.setup.outputs.packages) }} 30 | 31 | permissions: 32 | contents: read 33 | id-token: write 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Publish package 39 | run: | 40 | cd packages/${{ matrix.package }} 41 | npx jsr publish 42 | -------------------------------------------------------------------------------- /packages/create-shp/src/bjwj.ts: -------------------------------------------------------------------------------- 1 | import type { MultiPolygon } from "@liuxspro/libs/geo"; 2 | import { create_dbf, type Fields } from "./dbf.ts"; 3 | import JSZip from "jszip"; 4 | import { create_shp } from "./shp.ts"; 5 | 6 | /** 7 | * 创建边界文件 ZIP 包 8 | * @param stage 调查阶段 "初步调查" | "详细调查" 9 | * @param fields 字段信息 10 | * @param multi_polygon 多多边形几何对象 11 | * @param prj PRJ 文件内容 12 | * @returns {Promise} ZIP 包内容 13 | */ 14 | export async function create_bjwj( 15 | stage: "初步调查" | "详细调查", 16 | fields: Fields, 17 | multi_polygon: MultiPolygon, 18 | prj: string, 19 | ): Promise { 20 | const filename = `${stage}${fields.DKDM}`; 21 | const shpfile = await create_shp(multi_polygon); 22 | const dbf = create_dbf(fields); 23 | const zip = new JSZip(); 24 | const zip_target = zip.folder(filename); 25 | zip_target?.file(`${filename}.shp`, shpfile.shp.buffer); 26 | zip_target?.file(`${filename}.shx`, shpfile.shx.buffer); 27 | zip_target?.file(`${filename}.dbf`, dbf.buffer); 28 | zip_target?.file(`${filename}.cpg`, "UTF-8"); 29 | zip_target?.file(`${filename}.prj`, prj); 30 | return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" }); 31 | } 32 | -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 liuxspro 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /packages/capgen/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@es-toolkit/es-toolkit@*": "1.38.0", 5 | "jsr:@es-toolkit/es-toolkit@^1.38.0": "1.38.0", 6 | "jsr:@std/assert@1": "1.0.12", 7 | "jsr:@std/internal@^1.0.6": "1.0.6", 8 | "npm:minijinja-js@*": "2.9.0", 9 | "npm:minijinja-js@^2.9.0": "2.9.0" 10 | }, 11 | "jsr": { 12 | "@es-toolkit/es-toolkit@1.38.0": { 13 | "integrity": "7a3fa3bbe873116ec37ed68ef1df6b8ec3d89bac56edd354b7d12ff8cc0d5a0a" 14 | }, 15 | "@std/assert@1.0.12": { 16 | "integrity": "08009f0926dda9cbd8bef3a35d3b6a4b964b0ab5c3e140a4e0351fbf34af5b9a", 17 | "dependencies": [ 18 | "jsr:@std/internal" 19 | ] 20 | }, 21 | "@std/internal@1.0.6": { 22 | "integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4" 23 | } 24 | }, 25 | "npm": { 26 | "minijinja-js@2.9.0": { 27 | "integrity": "sha512-NDrwVXH5c+VfA41LrmINjlKUY+udSUAVSRjafyu0SiRkKJbczceR7DJK17UToZ59zc61dEroH78tesEYDLmegQ==" 28 | } 29 | }, 30 | "workspace": { 31 | "dependencies": [ 32 | "jsr:@es-toolkit/es-toolkit@^1.38.0", 33 | "jsr:@std/assert@1", 34 | "npm:minijinja-js@^2.9.0" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/libs/tests/geo/projection_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { toMercator, toWgs84 } from "npm:@turf/projection"; 3 | import { mercator_to_wgs84, wgs84_to_mercator } from "../../src/geo/mod.ts"; 4 | 5 | const wgs_points: [number, number][] = [ 6 | [120, 30], 7 | [47, 26], 8 | [29.05644, -22.48146], 9 | [-138.15829, 62.05977], 10 | ]; 11 | 12 | const mercator_points: [number, number][] = [ 13 | [13358338.89519283, 3503549.84350438], 14 | [5232016.06728386, 2999080.94347064], 15 | [3234548.10506531, -2569429.28722740], 16 | [-15379710.49166942, 8873329.18203938], 17 | ]; 18 | 19 | Deno.test("Geo: wgs84_to_mercator", () => { 20 | wgs_points.forEach((point) => { 21 | const result = wgs84_to_mercator(point); 22 | const turf = toMercator(point); 23 | assertEquals(result[0].toFixed(6), turf[0].toFixed(6)); 24 | assertEquals(result[1].toFixed(6), turf[1].toFixed(6)); 25 | }); 26 | }); 27 | 28 | Deno.test("Geo: mercator_to_wgs84", () => { 29 | mercator_points.forEach((point) => { 30 | const result = mercator_to_wgs84(point); 31 | const turf = toWgs84(point); 32 | assertEquals(result[0].toFixed(6), turf[0].toFixed(6)); 33 | assertEquals(result[1].toFixed(6), turf[1].toFixed(6)); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/create-shp/src/dbf.ts: -------------------------------------------------------------------------------- 1 | import { DBF, Field } from "@liuxspro/libs/dbf"; 2 | 3 | const DKMC = new Field("DKMC", "C", 254); 4 | const DKDM = new Field("DKDM", "C", 100); 5 | const XZQDM = new Field("XZQDM", "C", 12); 6 | const XZQMC = new Field("XZQMC", "C", 100); 7 | const YDMJ = new Field("YDMJ", "N", 16, 2); 8 | const DH = new Field("DH", "N", 16); 9 | const SCRQ = new Field("SCRQ", "D"); 10 | const SCDW = new Field("SCDW", "C", 254); 11 | const BZ = new Field("BZ", "C", 254); 12 | 13 | // 边界文件字段列表 14 | const BJ_FIELDS = [DKMC, DKDM, XZQDM, XZQMC, YDMJ, DH, SCRQ, SCDW, BZ]; 15 | 16 | /** 17 | * 边界文件字段信息 18 | */ 19 | export interface Fields { 20 | DKMC: string; 21 | DKDM: string; 22 | XZQDM: string; 23 | XZQMC: string; 24 | YDMJ: number; 25 | DH: number; 26 | SCRQ: Date | null; 27 | SCDW: string | null; 28 | BZ: string | null; 29 | } 30 | 31 | /** 32 | * 根据字段信息创建 DBF 文件 33 | * @param {Fields} fields 字段信息 34 | * @returns {Uint8Array} DBF 文件内容 35 | */ 36 | export function create_dbf(fields: Fields): Uint8Array { 37 | let { DKMC, DKDM, XZQDM, XZQMC, YDMJ, DH, SCRQ, SCDW, BZ } = fields; 38 | if (SCRQ !== null) { 39 | SCRQ = new Date(SCRQ); 40 | } 41 | const record = [DKMC, DKDM, XZQDM, XZQMC, YDMJ, DH, SCRQ, SCDW, BZ]; 42 | const dbf = new DBF(BJ_FIELDS, [record]); 43 | return dbf.data; 44 | } 45 | -------------------------------------------------------------------------------- /packages/create-shp/tests/bjwj_test.ts: -------------------------------------------------------------------------------- 1 | import type { Fields } from "../src/dbf.ts"; 2 | import { create_bjwj } from "../src/bjwj.ts"; 3 | import { Ring } from "@liuxspro/libs/geo"; 4 | Deno.test("create_bjwj 创建边界文件", async function () { 5 | const record: Fields = { 6 | "DKMC": "1", 7 | "DKDM": "2", 8 | "XZQMC": "4", 9 | "XZQDM": "3", 10 | "YDMJ": 129068.42, 11 | "DH": 39, 12 | "SCRQ": null, 13 | "SCDW": null, 14 | "BZ": null, 15 | }; 16 | const points = [ 17 | [39547228.491, 3797916.479], 18 | [39547246.449, 3798076.324], 19 | [39547399.598, 3798062.844], 20 | [39547381.907, 3797655.744], 21 | [39546951.091, 3797694.864], 22 | [39546979.970, 3797942.755], 23 | ]; 24 | const ring = new Ring(points); 25 | const prj = 26 | 'PROJCS["CGCS2000_3_Degree_GK_Zone_39",GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Gauss_Kruger"],PARAMETER["False_Easting",39500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",117.0],PARAMETER["Scale_Factor",1.0],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]'; 27 | const bjwj = await create_bjwj( 28 | "初步调查", 29 | record, 30 | ring.to_multipolygon(), 31 | prj, 32 | ); 33 | Deno.writeFileSync( 34 | "./tests/data/bjwj.zip", 35 | bjwj, 36 | ); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/create-shp/tests/csv_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | 3 | import { 4 | get_polygon_from_csv, 5 | parse_csv_file, 6 | parse_csv_file_content, 7 | } from "../src/source/csv.ts"; 8 | import { join } from "jsr:@std/path"; 9 | const csv_dir = join(Deno.cwd(), "tests/data/csv"); 10 | 11 | Deno.test("parse_csv_file_content", function () { 12 | for (const dir_entry of Deno.readDirSync(csv_dir)) { 13 | if (dir_entry.isFile && dir_entry.name.endsWith(".csv")) { 14 | const file_path = join(csv_dir, dir_entry.name); 15 | const text = Deno.readTextFileSync(file_path); 16 | const csv_data = parse_csv_file_content(text); 17 | const polygon = get_polygon_from_csv(csv_data); 18 | assertEquals(polygon.coordinates.length > 0, true); 19 | // console.log( 20 | // `测试文件: ${dir_entry.name} - [${polygon.coordinates[0][0]}]`, 21 | // ); 22 | } 23 | } 24 | }); 25 | 26 | Deno.test("parse_csv_file", async function () { 27 | for (const dir_entry of Deno.readDirSync(csv_dir)) { 28 | if (dir_entry.isFile && dir_entry.name.endsWith(".csv")) { 29 | const file_path = join(csv_dir, dir_entry.name); 30 | const text = Deno.readTextFileSync(file_path); 31 | const file = new File([text], dir_entry.name, { type: "text/csv" }); 32 | const result = await parse_csv_file(file); 33 | const polygon = result.polygon; 34 | assertEquals(polygon.coordinates.length > 0, true); 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /crates/webp_to_png/tests/test_wep_to_png.rs: -------------------------------------------------------------------------------- 1 | // tests/webp_conversion_tests.rs 2 | use image::{ImageBuffer, Rgba}; 3 | use std::io::Cursor; 4 | use webp_to_png::webp_to_png; // 假设你的crate名为image_converter 5 | 6 | fn create_test_webp() -> Vec { 7 | let img = ImageBuffer::, Vec>::from_pixel(1, 1, Rgba([255u8, 0u8, 0u8, 255u8])); 8 | let mut webp_data = Vec::new(); 9 | img.write_to(&mut Cursor::new(&mut webp_data), image::ImageFormat::WebP) 10 | .expect("Failed to create test WebP"); 11 | webp_data 12 | } 13 | 14 | fn is_valid_png(data: &[u8]) -> bool { 15 | let png_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; 16 | data.starts_with(&png_header) 17 | } 18 | 19 | #[test] 20 | fn test_valid_webp_conversion() { 21 | let webp_data = create_test_webp(); 22 | let result = webp_to_png(webp_data); 23 | assert!(result.is_ok()); 24 | 25 | let png_data = result.unwrap(); 26 | assert!(!png_data.is_empty()); 27 | assert!(is_valid_png(&png_data)); 28 | 29 | // 验证是否能被image库加载 30 | let img = image::load_from_memory(&png_data); 31 | assert!(img.is_ok()); 32 | } 33 | 34 | #[test] 35 | fn test_invalid_webp() { 36 | let invalid_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 37 | let result = webp_to_png(invalid_data); 38 | assert!(result.is_err()); 39 | } 40 | 41 | #[test] 42 | fn test_empty_input() { 43 | let empty_data = Vec::new(); 44 | let result = webp_to_png(empty_data); 45 | assert!(result.is_err()); 46 | } 47 | -------------------------------------------------------------------------------- /crates/google_earth/src/decrypt.rs: -------------------------------------------------------------------------------- 1 | pub fn decrypt_data_impl(encrypted_data: &[u8], key: &[u8]) -> Result, &'static str> { 2 | if key.len() != 1024 { 3 | return Err("Key length must be 1024 bytes"); 4 | } 5 | let mut decrypted = Vec::with_capacity(encrypted_data.len()); 6 | let mut key_index: usize = 16; // 从密钥的第16个字节开始 7 | 8 | for &encrypted_byte in encrypted_data { 9 | // 计算实际密钥索引,确保不越界 10 | let actual_key_index = key_index + 8; 11 | if actual_key_index >= key.len() { 12 | return Err("Key index out of bounds"); 13 | } 14 | 15 | // 异或解密 16 | let decrypted_byte = encrypted_byte ^ key[actual_key_index]; 17 | decrypted.push(decrypted_byte); 18 | 19 | // 更新密钥索引 20 | key_index += 1; 21 | 22 | // 如果key_index是8的倍数,则增加16 23 | if key_index % 8 == 0 { 24 | key_index += 16; 25 | } 26 | 27 | // 如果key_index超过1016,则调整 28 | if key_index >= 1016 { 29 | key_index = (key_index + 8) % 24; 30 | } 31 | } 32 | 33 | Ok(decrypted) 34 | } 35 | 36 | pub fn decrypt_data_with_default_key(encrypted_data: &[u8]) -> Vec { 37 | let key: &[u8] = include_bytes!("key.bin"); 38 | decrypt_data_impl(encrypted_data, key).expect("error in decrypt data") 39 | } 40 | 41 | pub fn decrypt_data(data: &[u8], key: Option<&[u8]>) -> Result, &'static str> { 42 | let key_slice = match key { 43 | Some(k) => k, 44 | None => include_bytes!("key.bin"), 45 | }; 46 | return decrypt_data_impl(data, key_slice); 47 | } 48 | -------------------------------------------------------------------------------- /packages/libs/src/geo/cgcs.ts: -------------------------------------------------------------------------------- 1 | //deno-fmt-ignore 2 | const CGCS2000_3_Degree_Zone_List = [ 3 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 4 | 44, 45, 5 | ] as const; 6 | 7 | export type CGCS2000_3_Degree_Zone = 8 | (typeof CGCS2000_3_Degree_Zone_List)[number]; 9 | 10 | /** 11 | * 返回带号对应的 ESRI WKT 12 | * @param zone 3度带带号,25~45 13 | * @returns ESRI WKT 14 | * 15 | * https://epsg.io/4527 16 | */ 17 | export function get_cgcs2000_wkt(zone: CGCS2000_3_Degree_Zone): string { 18 | const PROJCS_name = `CGCS2000_3_Degree_GK_Zone_${zone}`; 19 | const GEOGCS_name = "GCS_China_Geodetic_Coordinate_System_2000"; 20 | const GEOGCS = 21 | `GEOGCS["${GEOGCS_name}",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]`; 22 | const East = zone * 1000000 + 500000; 23 | const cm = 75 + (zone - 25) * 3; // Central_Meridian 24 | const PROJECTION = 25 | `PROJECTION["Gauss_Kruger"],PARAMETER["False_Easting",${East}.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",${cm}.0],PARAMETER["Scale_Factor",1.0],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]`; 26 | return `PROJCS["${PROJCS_name}",${GEOGCS},${PROJECTION}]`; 27 | } 28 | 29 | /** 30 | * 根据经度获取带号(3度带) 31 | * @param longitude 经度 32 | * @returns 3度带带号(25~45) 33 | * @throws {Error} 如果经度不在中国范围内(73.62~135)则抛出错误 34 | */ 35 | export function get_zone(longitude: number): number { 36 | if (longitude < 73.62 || longitude > 135) { 37 | throw new Error("经度不在中国范围内(73.62~135)"); 38 | } 39 | return Math.round(longitude / 3); 40 | } 41 | -------------------------------------------------------------------------------- /packages/libs/src/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 合并多个 Uint8Array 数组为一个 Uint8Array 3 | * @param {Uint8Array[]} arrays - 要合并的 Uint8Array 实例数组 4 | * @returns {Uint8Array} 合并后的 Uint8Array 5 | * @example 6 | * ```typescript 7 | * const arr1 = new Uint8Array([1, 2]); 8 | * const arr2 = new Uint8Array([3, 4]); 9 | * const merged = mergeUint8Arrays([arr1, arr2]); 10 | * console.log(merged); // 输出: Uint8Array [1, 2, 3, 4] 11 | * ``` 12 | */ 13 | export function mergeUint8Arrays(arrays: Uint8Array[]): Uint8Array { 14 | // 计算总长度 15 | const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0); 16 | 17 | // 创建目标TypedArray 18 | const result = new Uint8Array(totalLength); 19 | let offset = 0; 20 | 21 | // 复制每个数组内容到结果数组 22 | for (const arr of arrays) { 23 | result.set(arr, offset); 24 | offset += arr.length; 25 | } 26 | 27 | return result; 28 | } 29 | 30 | /** 31 | * 比较两个 Uint8Array 数组是否完全相等 32 | * 33 | * @param a - 第一个要比较的 Uint8Array 数组 34 | * @param b - 第二个要比较的 Uint8Array 数组 35 | * @returns 如果两个数组长度相同且所有对应位置的元素都相等则返回 true,否则返回 false 36 | * 37 | * @example 38 | * ```typescript 39 | * const arr1 = new Uint8Array([1, 2, 3]); 40 | * const arr2 = new Uint8Array([1, 2, 3]); 41 | * const arr3 = new Uint8Array([1, 2, 4]); 42 | * 43 | * array_is_equal(arr1, arr2); // true 44 | * array_is_equal(arr1, arr3); // false 45 | * ``` 46 | * 47 | * @remarks 48 | * 此函数专门用于比较 Uint8Array 类型,对于其他类型的数组可能需要不同的实现 49 | */ 50 | export function array_is_equal(a: Uint8Array, b: Uint8Array): boolean { 51 | if (a.length !== b.length) return false; 52 | return a.every((value, index) => value === b[index]); 53 | } 54 | -------------------------------------------------------------------------------- /packages/libs/tests/geo/types_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | 3 | import { 4 | MultiPolygon, 5 | type Point, 6 | Polygon, 7 | Ring, 8 | } from "../../src/geo/types.ts"; 9 | 10 | Deno.test("Geo: types", () => { 11 | const p1: Point = [1, 1]; 12 | const p2: Point = [1, 2]; 13 | const p3 = [1, 3]; 14 | const points = [ 15 | [117.513070, 34.307738], 16 | [117.513274, 34.309178], 17 | [117.514937, 34.309049], 18 | [117.514722, 34.305380], 19 | [117.510045, 34.305752], 20 | [117.510372, 34.307986], 21 | ]; 22 | 23 | const ring = new Ring([p1, p2, p3]); 24 | const ring2 = new Ring([p1, p2, p3 as Point, p1]); 25 | const ring3 = new Ring([[0, 0], [1, 1], [0, 2], [0, 0]]); 26 | const ring4 = new Ring(points); 27 | 28 | const t = function ([x, y]: Point): Point { 29 | return [x + 10, y + 5]; 30 | }; 31 | const trans_ring = ring.transform(t); 32 | assertEquals(ring, ring2); 33 | assertEquals(ring.points[0], ring.points.at(-1)); 34 | assertEquals(ring2.points[0], ring2.points.at(-1)); 35 | assertEquals(ring3.points.length, 4); 36 | assertEquals(trans_ring.points[0][0], 11); 37 | 38 | const polygon = new Polygon([ring]); 39 | 40 | const mpolygon = new MultiPolygon([polygon, new Polygon([ring4])]); 41 | const trans_polygon = polygon.transform(t); 42 | const trans_mpolygon = mpolygon.transform(t); 43 | assertEquals(trans_polygon.rings[0].points[0][0], 11); 44 | assertEquals(trans_mpolygon.coordinates[0][0][0][0], 11); 45 | assertEquals(mpolygon.polygons.length, 2); 46 | assertEquals(polygon.coordinates[0], ring.points); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/libs/tests/dbf/field_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import { Field } from "../../src/dbf/mod.ts"; 3 | 4 | Deno.test("Field - 布尔型", function () { 5 | const r = new Field("NAME", "L"); 6 | assertEquals(r.record_define.slice(0, 4), new Uint8Array([78, 65, 77, 69])); 7 | assertEquals(r.record_define.slice(11, 12), new Uint8Array([76])); 8 | assertEquals(r.record_define.slice(16, 17), new Uint8Array([1])); 9 | assertEquals(r.record_define.slice(17, 18), new Uint8Array([0])); 10 | }); 11 | 12 | Deno.test("Field - 文本型", function () { 13 | const r = new Field("NAME", "C", 4); 14 | assertEquals(r.record_define.slice(0, 4), new Uint8Array([78, 65, 77, 69])); 15 | assertEquals(r.record_define.slice(11, 12), new Uint8Array([67])); 16 | assertEquals(r.record_define.slice(16, 17), new Uint8Array([4])); 17 | assertEquals(r.record_define.slice(17, 18), new Uint8Array([0])); 18 | }); 19 | 20 | Deno.test("Field - 整数", function () { 21 | const r = new Field("NAME", "N", 10, 0); 22 | assertEquals(r.record_define.slice(0, 4), new Uint8Array([78, 65, 77, 69])); 23 | assertEquals(r.record_define.slice(11, 12), new Uint8Array([78])); 24 | assertEquals(r.record_define.slice(16, 17), new Uint8Array([10])); 25 | assertEquals(r.record_define.slice(17, 18), new Uint8Array([0])); 26 | }); 27 | 28 | Deno.test("Field - 浮点数", function () { 29 | const r = new Field("NAME", "N", 10, 2); 30 | assertEquals(r.record_define.slice(0, 4), new Uint8Array([78, 65, 77, 69])); 31 | assertEquals(r.record_define.slice(11, 12), new Uint8Array([78])); 32 | assertEquals(r.record_define.slice(16, 17), new Uint8Array([11])); 33 | assertEquals(r.record_define.slice(17, 18), new Uint8Array([2])); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/libs/src/geo/xyz.ts: -------------------------------------------------------------------------------- 1 | import type { Point } from "./types.ts"; 2 | import { d2r, r2d } from "./utils.ts"; 3 | 4 | export class XYZ { 5 | /** 6 | * 构造函数,创建一个XYZ坐标点 7 | * @param x - X坐标(瓦片列索引) 8 | * @param y - Y坐标(瓦片行索引) 9 | * @param z - 缩放级别(zoom level) 10 | */ 11 | constructor(public x: number, public y: number, public z: number) {} 12 | 13 | /** 14 | * 将XYZ瓦片坐标转换为地理经纬度坐标 15 | * @returns 返回包含经度和纬度的二元组 [longitude, latitude](单位:度) 16 | * 17 | * @remarks 18 | * 转换公式: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames 19 | */ 20 | to_lonlat(): Point { 21 | const n = Math.pow(2, this.z); 22 | const lon = (this.x / n) * 360 - 180; 23 | const lat = r2d(Math.atan(Math.sinh(Math.PI - (this.y / n) * 2 * Math.PI))); 24 | return [lon, lat]; 25 | } 26 | 27 | /** 28 | * 从地理经纬度坐标生成对应的XYZ瓦片坐标 29 | * @param lon - 经度(单位:度) 30 | * @param lat - 纬度(单位:度) 31 | * @param z - 目标缩放级别 32 | * @returns 对应缩放级别的XYZ瓦片坐标对象 33 | * @remarks 34 | * 转换公式: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames 35 | */ 36 | static from_lonlat(lon: number, lat: number, z: number): XYZ { 37 | const n = Math.pow(2, z); 38 | const x = Math.floor(((lon + 180) / 360) * n); 39 | const lat_rad = d2r(lat); 40 | const y = Math.floor( 41 | ((1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math.PI) / 42 | 2) * 43 | n, 44 | ); 45 | return new XYZ(x, y, z); 46 | } 47 | /** 48 | * 从XYZ坐标生成对应的XYZ瓦片坐标对象 49 | * @param x - X坐标(瓦片列索引) 50 | * @param y - Y坐标(瓦片行索引) 51 | * @param z - 缩放级别(zoom level) 52 | * @returns 对应的XYZ瓦片坐标对象 53 | * @remarks 54 | * 该方法仅作为构造函数的别名,便于语义化调用。 55 | */ 56 | static from_xyz(x: number, y: number, z: number): XYZ { 57 | return new XYZ(x, y, z); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/create-shp/tests/shp_test.ts: -------------------------------------------------------------------------------- 1 | // import { assertEquals } from "jsr:@std/assert"; 2 | import { create_shp } from "../src/shp.ts"; 3 | import { MultiPolygon, Polygon, Ring } from "@liuxspro/libs/geo"; 4 | 5 | Deno.test("create_shp", async function () { 6 | const points = [ 7 | [117.513070, 34.307738], 8 | [117.513274, 34.309178], 9 | [117.514937, 34.309049], 10 | [117.514722, 34.305380], 11 | [117.510045, 34.305752], 12 | [117.510372, 34.307986], 13 | ]; 14 | const hole = [ 15 | [117.511613226582298, 34.307186887974709], 16 | [117.511504859493698, 34.306493338607623], 17 | [117.514149016455704, 34.306233257594961], 18 | [117.514398260759506, 34.308292232278504], 19 | [117.513347100000018, 34.308346415822804], 20 | [117.513119529113936, 34.307132704430408], 21 | [117.511613226582298, 34.307186887974709], 22 | ]; 23 | 24 | const side_points = [ 25 | [117.515262, 34.309026], 26 | [117.516878, 34.308909], 27 | [117.516515, 34.305307], 28 | [117.515020, 34.305386], 29 | ]; 30 | 31 | const ring = new Ring(points); 32 | const hole_ring = new Ring(hole); 33 | const polygon = new Polygon([ring, hole_ring]); 34 | const polygon_side = new Polygon([new Ring(side_points)]); 35 | const m = new MultiPolygon([polygon, polygon_side]); 36 | 37 | const shpfile = await create_shp(m, { DKMC: "DKMC" }); 38 | Deno.writeFileSync( 39 | "./tests/data/test.shp", 40 | new Uint8Array(shpfile.shp.buffer), 41 | ); 42 | Deno.writeFileSync( 43 | "./tests/data/test.shx", 44 | new Uint8Array(shpfile.shx.buffer), 45 | ); 46 | Deno.writeFileSync( 47 | "./tests/data/test.dbf", 48 | new Uint8Array(shpfile.dbf.buffer), 49 | ); 50 | Deno.writeTextFileSync("./tests/data/test.prj", shpfile.prj); 51 | Deno.writeTextFileSync("./tests/data/test.cpg", "UTF-8"); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/libs/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@liuxspro/utils@0.1.12": "0.1.12", 5 | "jsr:@std/assert@*": "1.0.13", 6 | "jsr:@std/internal@^1.0.6": "1.0.8", 7 | "npm:@turf/projection@*": "7.2.0" 8 | }, 9 | "jsr": { 10 | "@liuxspro/utils@0.1.12": { 11 | "integrity": "fc46c186baad8022b7a57a1a6ca0c3c9ebb584a81f59fa7fac55ca8e44fd8f63" 12 | }, 13 | "@std/assert@1.0.13": { 14 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 15 | "dependencies": [ 16 | "jsr:@std/internal" 17 | ] 18 | }, 19 | "@std/internal@1.0.8": { 20 | "integrity": "fc66e846d8d38a47cffd274d80d2ca3f0de71040f855783724bb6b87f60891f5" 21 | } 22 | }, 23 | "npm": { 24 | "@turf/clone@7.2.0": { 25 | "integrity": "sha512-JlGUT+/5qoU5jqZmf6NMFIoLDY3O7jKd53Up+zbpJ2vzUp6QdwdNzwrsCeONhynWM13F0MVtPXH4AtdkrgFk4g==", 26 | "dependencies": [ 27 | "@turf/helpers", 28 | "@types/geojson", 29 | "tslib" 30 | ] 31 | }, 32 | "@turf/helpers@7.2.0": { 33 | "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", 34 | "dependencies": [ 35 | "@types/geojson", 36 | "tslib" 37 | ] 38 | }, 39 | "@turf/meta@7.2.0": { 40 | "integrity": "sha512-igzTdHsQc8TV1RhPuOLVo74Px/hyPrVgVOTgjWQZzt3J9BVseCdpfY/0cJBdlSRI4S/yTmmHl7gAqjhpYH5Yaw==", 41 | "dependencies": [ 42 | "@turf/helpers", 43 | "@types/geojson" 44 | ] 45 | }, 46 | "@turf/projection@7.2.0": { 47 | "integrity": "sha512-/qke5vJScv8Mu7a+fU3RSChBRijE6EVuFHU3RYihMuYm04Vw8dBMIs0enEpoq0ke/IjSbleIrGQNZIMRX9EwZQ==", 48 | "dependencies": [ 49 | "@turf/clone", 50 | "@turf/helpers", 51 | "@turf/meta", 52 | "@types/geojson", 53 | "tslib" 54 | ] 55 | }, 56 | "@types/geojson@7946.0.16": { 57 | "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" 58 | }, 59 | "tslib@2.8.1": { 60 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/create-shp/src/source/csv.ts: -------------------------------------------------------------------------------- 1 | import Papa from "papaparse"; 2 | import { 3 | MultiPolygon, 4 | type Point, 5 | type Polygon, 6 | Ring, 7 | } from "@liuxspro/libs/geo"; 8 | import { correct_points_order } from "./utils.ts"; 9 | 10 | type CSVRow = Record; 11 | 12 | /** 13 | * 读取 CSV 数据, 14 | * 返回 json 对象数组 15 | * @param {string} text csv文本内容 16 | * @returns {CSVRow[]} 返回 json 对象数组 17 | */ 18 | export function parse_csv_file_content(text: string): CSVRow[] { 19 | // https://www.papaparse.com/docs#csv-to-json 20 | const csv_parse_option = { 21 | skipEmptyLines: true, // 跳过空行 22 | header: true, // 如果CSV文件包含标题行,请设置为 true 23 | dynamicTyping: true, // 尝试将字段自动转换为数值类型 24 | }; 25 | const csv_data = Papa.parse(text, csv_parse_option); 26 | return csv_data.data as CSVRow[]; 27 | } 28 | 29 | export function get_points_from_csv_record(record: CSVRow): Point { 30 | // 解析每一个 csv 记录,判断是经纬度还是投影坐标系, 并调整投影坐标 X Y 的顺序 31 | const keys = Object.keys(record); 32 | if (keys.length >= 3) { 33 | const x = record[keys[1]]; 34 | const y = record[keys[2]]; 35 | let real_x; 36 | let real_y; 37 | if (x > 200) { 38 | //投影坐标 39 | return correct_points_order([x, y]); 40 | } else { 41 | real_x = x; 42 | real_y = y; 43 | } 44 | return [real_x, real_y]; 45 | } else { 46 | throw new Error("CSV 记录字段数不足, 无法解析坐标"); 47 | } 48 | } 49 | 50 | /** 51 | * 从所有 CSV 记录中读取点坐标信息, 一个 CSV 文件对应一个多边形 52 | * @param {CSVRow[]} csv_data 53 | * @returns {Polygon} 多边形 54 | */ 55 | export function get_polygon_from_csv(csv_data: CSVRow[]): Polygon { 56 | const points = csv_data.map((record) => get_points_from_csv_record(record)); 57 | const ring = new Ring(points); 58 | return ring.to_polygon(); 59 | } 60 | 61 | interface CSVFileParseResult { 62 | name: string; 63 | type: string; 64 | polygon: Polygon; 65 | } 66 | 67 | /** 68 | * 处理 Input File CSV 文件 69 | * @param {File} csv_file 70 | * @returns {Promise} 文件信息和多边形对象 71 | */ 72 | export async function parse_csv_file( 73 | csv_file: File, 74 | ): Promise { 75 | if (!csv_file.name.endsWith(".csv") && !csv_file.name.endsWith(".txt")) { 76 | throw new Error("仅支持 CSV 文件"); 77 | } 78 | const text_data = await csv_file.text(); 79 | const polygon = get_polygon_from_csv(parse_csv_file_content(text_data)); 80 | 81 | return { name: csv_file.name, type: csv_file.type, polygon }; 82 | } 83 | 84 | export function merge_ploygon( 85 | csv_parse_result: CSVFileParseResult[], 86 | ): MultiPolygon { 87 | return new MultiPolygon(csv_parse_result.map((f) => f.polygon)); 88 | } 89 | -------------------------------------------------------------------------------- /packages/libs/src/dbf/field.ts: -------------------------------------------------------------------------------- 1 | import { encode_text, type FieldType, type FieldTypeSign } from "./utils.ts"; 2 | 3 | const FieldSize = { 4 | "C": 254, 5 | "N": 18, 6 | "L": 1, 7 | "D": 8, 8 | }; 9 | 10 | export class Field { 11 | constructor( 12 | name: string, 13 | type: "N", 14 | length: number, 15 | precision?: number, 16 | ); 17 | constructor( 18 | name: string, 19 | type: "C", 20 | length: number, 21 | ); 22 | constructor( 23 | name: string, 24 | type: "D" | "L", 25 | length?: number, 26 | precision?: number, 27 | ); 28 | constructor( 29 | public name: string, 30 | public type: FieldTypeSign | FieldType, 31 | public length?: number, 32 | public precision?: number, 33 | ) { 34 | // 检查 字段名称是否超过了11 35 | if (this.name.length > 11) { 36 | throw new Error("字段名称长度应小于11比特"); 37 | } 38 | // 根据字段类型赋值长度和精度 39 | // 对于布尔和日期型,长度固定为1和8,精度为0 40 | // 对于文本型,精度为0 41 | if (this.type === "L") { 42 | this.length = 1; 43 | this.precision = 0; 44 | } 45 | if (this.type === "D") { 46 | this.length = 8; 47 | this.precision = 0; 48 | } 49 | if (this.type === "C") { 50 | this.precision = 0; 51 | } 52 | // 当数据类型为浮点型或双精度的时候(Number类型),如果设置了小数位数(精度),则字段长度要加 1 (小数点占 1 位) 53 | if (this.type === "N") { 54 | if (this.precision) { 55 | this.length = this.length as number + 1; 56 | } else { 57 | this.precision = 0; 58 | } 59 | } 60 | } 61 | 62 | get record_define(): Uint8Array { 63 | const data = new Uint8Array(32); 64 | const max_field_length = FieldSize[this.type]; 65 | // 设置字段名称和字段类型 66 | const encoded_name = encode_text(this.name); 67 | data.set(encoded_name, 0); 68 | data.set(encode_text(this.type), 11); 69 | 70 | // 设置字段长度、精度 71 | // 布尔型和日期型长度为固定(1和8) 72 | if (this.type === "L" || this.type === "D") { 73 | data.set( 74 | new Uint8Array([max_field_length]), 75 | 16, 76 | ); 77 | } 78 | 79 | if (this.type === "C") { 80 | if (this.length) { 81 | data.set(new Uint8Array([this.length]), 16); 82 | } else { 83 | // data.set(new Uint8Array([(this.value as string).length]), 16); 84 | } 85 | } 86 | // 对于数值型,需要设置精度 87 | // arcmap 中“小数位数”为此处的精度,“精度”为字段长度 88 | // 当数据类型为浮点型或双精度的时候(其实都是Number类型),如果设置了小数位数,则字段长度要加 1(小数点占 1 位) 89 | if (this.type === "N") { 90 | data.set(new Uint8Array([this.length as number]), 16); 91 | if (this.precision) { 92 | data.set(new Uint8Array([this.precision]), 17); 93 | } 94 | } 95 | 96 | return data; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/libs/src/utils/math.ts: -------------------------------------------------------------------------------- 1 | /** 获取某个数字的整数部分位数 */ 2 | export function get_digits(n: number): number { 3 | const integerPart = Math.floor(Math.abs(n)); // 取绝对值并向下取整 4 | return integerPart.toString().length; 5 | } 6 | 7 | /** 生成[n,m]的随机整数 */ 8 | export function random(minNum: number, maxNum?: number): number { 9 | // 生成[n,m]的随机整数 10 | const by_min = Math.random() * minNum + 1; 11 | if (maxNum) { 12 | const by_min_and_max = Math.random() * (maxNum - minNum + 1) + minNum; 13 | return parseInt(by_min_and_max.toString(), 10); 14 | } else { 15 | return parseInt(by_min.toString(), 10); 16 | } 17 | } 18 | 19 | /** 20 | * 将数字四舍五入到指定精度 21 | * 22 | * from: https://github.com/sindresorhus/round-to/blob/main/index.js 23 | * 24 | * 该函数支持处理特殊数值(NaN/Infinity)、边界情况(-0)和浮点数精度问题, 25 | * 遵循传统的四舍五入规则(0.5向远离零的方向舍入)。 26 | * 27 | * @param number 要四舍五入的数字,允许特殊数值(NaN/Infinity) 28 | * @param precision 要保留的小数位数(必须为整数) 29 | * 30 | * @returns 四舍五入后的数字。特殊处理: 31 | * - 非有限数(NaN/Infinity)直接返回原值 32 | * - 精度为`Number.POSITIVE_INFINITY`时返回原值 33 | * - 输入为`-0`时保留负零 34 | * - 计算结果为`-0`时自动转为`0` 35 | * 36 | * @throws {TypeError} 当`number`不是数字类型时抛出 37 | * @throws {TypeError} 当`precision`不是整数时抛出 38 | * 39 | * @example 40 | * round_to(3.14159, 2); // 3.14 41 | * round_to(1.005, 2); // 1.01 (正确处理浮点精度) 42 | * round_to(-2.675, 2); // -2.68 43 | * round_to(Infinity, 2); // Infinity 44 | * round_to(-0, 5); // -0 45 | */ 46 | export function round_to(number: number, precision: number): number { 47 | if (typeof number !== "number") { 48 | throw new TypeError("Expected value to be a number"); 49 | } 50 | 51 | // Handle non-finite values explicitly 52 | if (!Number.isFinite(number)) { 53 | return number; // NaN, Infinity, -Infinity pass through unchanged 54 | } 55 | 56 | if (precision === Number.POSITIVE_INFINITY) { 57 | return number; 58 | } 59 | 60 | if (!Number.isInteger(precision)) { 61 | throw new TypeError("Expected precision to be an integer"); 62 | } 63 | 64 | // If the input is already -0, preserve it 65 | if (Object.is(number, -0)) { 66 | return number; 67 | } 68 | 69 | const power = 10 ** precision; 70 | const scaledNumber = number * power; 71 | const scaledPrecise = Number.parseFloat(scaledNumber.toPrecision(15)); 72 | 73 | // Traditional rounding - away from zero for 0.5 74 | const rounded = scaledPrecise < 0 75 | ? -Math.round(Math.abs(scaledPrecise)) 76 | : Math.round(scaledPrecise); 77 | 78 | let result = rounded / power; 79 | 80 | // Ensure rounding operations never return -0, always return 0 81 | if (Object.is(result, -0)) { 82 | result = 0; 83 | } 84 | 85 | return result; 86 | } 87 | -------------------------------------------------------------------------------- /packages/capgen/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | 138 | # Rust Ignore 139 | /target -------------------------------------------------------------------------------- /packages/create-shp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | 138 | tests/data/* -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

wasm-pack-template

4 | 5 | A template for kick starting a Rust and WebAssembly project using wasm-pack. 6 | 7 |

8 | Build Status 9 |

10 | 11 |

12 | Tutorial 13 | | 14 | Chat 15 |

16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
19 | 20 | ## About 21 | 22 | [**📚 Read this template tutorial! 📚**][template-docs] 23 | 24 | This template is designed for compiling Rust libraries into WebAssembly and 25 | publishing the resulting package to NPM. 26 | 27 | Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other 28 | templates and usages of `wasm-pack`. 29 | 30 | [tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html 31 | [template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html 32 | 33 | ## 🚴 Usage 34 | 35 | ### 🐑 Use `cargo generate` to Clone this Template 36 | 37 | [Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) 38 | 39 | ``` 40 | cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project 41 | cd my-project 42 | ``` 43 | 44 | ### 🛠️ Build with `wasm-pack build` 45 | 46 | ``` 47 | wasm-pack build 48 | ``` 49 | 50 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 51 | 52 | ``` 53 | wasm-pack test --headless --firefox 54 | ``` 55 | 56 | ### 🎁 Publish to NPM with `wasm-pack publish` 57 | 58 | ``` 59 | wasm-pack publish 60 | ``` 61 | 62 | ## 🔋 Batteries Included 63 | 64 | * [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating 65 | between WebAssembly and JavaScript. 66 | * [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) 67 | for logging panic messages to the developer console. 68 | * `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | ### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally 82 | submitted for inclusion in the work by you, as defined in the Apache-2.0 83 | license, shall be dual licensed as above, without any additional terms or 84 | conditions. 85 | -------------------------------------------------------------------------------- /packages/capgen/src/cangen.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from "minijinja-js"; 2 | import { template } from "./template.ts"; 3 | import type { TileMatrixSet } from "./matrix.ts"; 4 | 5 | export interface Service { 6 | title: string; 7 | abstract: string; 8 | keywords: string[]; 9 | } 10 | 11 | export type GeoPoint = { lon: number; lat: number }; 12 | 13 | export const default_service: Service = { 14 | title: "Simple WMTS", 15 | abstract: "Simple WMTS", 16 | keywords: ["WMTS"], 17 | }; 18 | 19 | export const mercator_bbox: [GeoPoint, GeoPoint] = [ 20 | { lon: -180.0, lat: -85.051129 }, // 西南角 (LowerCorner) 21 | { lon: 180.0, lat: 85.051129 }, // 东北角 (UpperCorner) 22 | ]; 23 | 24 | export const world_mercator_bbox: [GeoPoint, GeoPoint] = [ 25 | { lon: -180.0, lat: -85.08405903 }, // 西南角 (LowerCorner) 26 | { lon: 180.0, lat: 85.08405903 }, // 东北角 (UpperCorner) 27 | ]; 28 | 29 | export class MapLayer { 30 | wmts_url: string; 31 | tile_matrix_set: string; 32 | /** 33 | * @param title 地图名称 34 | * @param abstract 摘要 35 | * @param id 唯一id 36 | * @param bbox 边界框 37 | * @param tile_matrix_set 瓦片矩阵集 38 | * @param url 原始的url 包含{z}/{x}/{y} 39 | */ 40 | constructor( 41 | public title: string, 42 | public abstract: string, 43 | public id: string, 44 | public bbox: [GeoPoint, GeoPoint], 45 | public matrix: TileMatrixSet, 46 | public url: string, 47 | public format?: string, 48 | ) { 49 | this.wmts_url = this.trans_url(url); 50 | this.tile_matrix_set = this.matrix.id; 51 | } 52 | 53 | set_title(title: string) { 54 | this.title = title; 55 | } 56 | 57 | set_url(new_url: string) { 58 | this.url = this.trans_url(new_url); 59 | } 60 | 61 | set_token(tk_name: string = "tk", token: string) { 62 | // Add new token parameter 63 | this.wmts_url = this.trans_url(this.url + `&${tk_name}=${token}`); 64 | } 65 | 66 | protected trans_url(url: string): string { 67 | return url 68 | .replace(/\{z\}/g, "{TileMatrix}") 69 | .replace(/\{x\}/g, "{TileCol}") 70 | .replace(/\{y\}/g, "{TileRow}") 71 | .replace(/&/g, "&") 72 | .replace(/\|/g, "%7C"); 73 | } 74 | } 75 | 76 | export class Capabilities { 77 | constructor(public service: Service, public layers: MapLayer[]) { 78 | } 79 | 80 | get xml(): string { 81 | const env = new Environment(); 82 | env.trimBlocks = true; 83 | env.lstripBlocks = true; 84 | env.debug = true; 85 | const service = this.service; 86 | const layers = this.layers; 87 | 88 | const used_matrix = layers.map((layer) => { 89 | return layer.matrix; 90 | }); 91 | 92 | const unique_matrix_map = new Map(); 93 | used_matrix.forEach((item) => unique_matrix_map.set(item.id, item)); 94 | const tile_matrix_sets = Array.from(unique_matrix_map.values()); 95 | 96 | const result = env.renderStr(template, { 97 | service, 98 | layers, 99 | tile_matrix_sets, 100 | }); 101 | 102 | return result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/capgen/src/template.ts: -------------------------------------------------------------------------------- 1 | export const template = ` 2 | 9 | 10 | {{ service.title | default("Untitled WMTS Service") }} 11 | {{ service.abstract }} 12 | 13 | {% for keyword in service.keywords %} 14 | {{ keyword }} 15 | {% endfor %} 16 | 17 | OGC WMTS 18 | 1.0.0 19 | none 20 | none 21 | 22 | 23 | {% for layer in layers %} 24 | 25 | {{ layer.title }} 26 | {{ layer.abstract }} 27 | 28 | {{ layer.bbox[0].lon }} {{ layer.bbox[0].lat }} 29 | {{ layer.bbox[1].lon }} {{ layer.bbox[1].lat }} 30 | 31 | {{ layer.id }} 32 | 35 | {{ layer.format | default("image/png")}} 36 | 37 | {{ layer.tile_matrix_set }} 38 | 39 | 40 | 41 | {% endfor %} 42 | {% for tile_matrix_set in tile_matrix_sets %} 43 | 44 | {{ tile_matrix_set.title }} 45 | {{ tile_matrix_set.id }} 46 | {{ tile_matrix_set.supported_crs }} 47 | {{ tile_matrix_set.wellknown_scale_set }} 48 | {% for tile_matrix in tile_matrix_set.tile_matrixs %} 49 | 50 | {{ tile_matrix.identifier }} 51 | {{ tile_matrix.scale_denominator }} 52 | {{ tile_matrix.top_left_corner[0] }} {{tile_matrix.top_left_corner[1]}} 53 | {{ tile_matrix.tile_width }} 54 | {{ tile_matrix.tile_height }} 55 | {{ tile_matrix.matrix_width }} 56 | {{ tile_matrix.matrix_height }} 57 | 58 | {% endfor %} 59 | 60 | {% endfor %} 61 | 62 | 63 | `; 64 | -------------------------------------------------------------------------------- /packages/libs/src/dbf/dbf.ts: -------------------------------------------------------------------------------- 1 | import type { Field } from "./field.ts"; 2 | import { get_update_date } from "./utils.ts"; 3 | import { record_to_uint8array, type RecordValue } from "./record.ts"; 4 | import { mergeUint8Arrays } from "../array.ts"; 5 | 6 | const DBF_FILE_VERSION = Uint8Array.of(0x03); // DBF 文件版本号 7 | const RECORD_END = Uint8Array.of(0x0d); //字段定义终止符 8 | 9 | const EOF = Uint8Array.of(0x1a); 10 | 11 | /** 12 | * DBF Class,表示一个 DBF 文件实例 13 | * @example 14 | * ```typescript 15 | * const c = new Field("NAME", "C", 4); 16 | * const n = new Field("AGE", "N", 3, 0); 17 | * const dbf = new DBF([c, n], [["张三", 18], ["李四", 20]]); 18 | * const dbfData = dbf.data; // 获取完整的 DBF 文件二进制数据 19 | * ``` 20 | */ 21 | export class DBF { 22 | /** DBF 文件中创建时间 */ 23 | create_date: Date; 24 | 25 | /** 26 | * 构造一个DBF文件实例 27 | * @param {Field[]} fields - 字段定义数组,描述DBF文件的结构 28 | * @param {RecordValue[]} [records] - 可选记录数组,包含DBF文件的数据行 29 | */ 30 | constructor(public fields: Field[], public records?: RecordValue[]) { 31 | this.create_date = new Date(); 32 | } 33 | 34 | /** 35 | * 设置DBF文件的创建日期 36 | * @param {Date} newdate - 新的创建日期 37 | * @returns {DBF} 返回当前实例以支持链式调用 38 | */ 39 | set_create_date(newdate: Date): DBF { 40 | this.create_date = newdate; 41 | return this; 42 | } 43 | 44 | /** 45 | * 获取DBF文件头的二进制表示 46 | * @returns {Uint8Array} 包含文件头信息的二进制数据 47 | * @description 48 | * 计算并生成符合DBF格式的文件头,包含: 49 | * - 版本号 (位置0) 50 | * - 更新日期 (位置1-3) 51 | * - 记录数量 (位置4-7) 52 | * - 文件头长度 (位置8-9) 53 | * - 每条记录长度 (位置10-11) 54 | * - 字段定义记录 55 | * - 记录结束标记 56 | */ 57 | get header(): Uint8Array { 58 | const header_define = new Uint8Array(32); 59 | // 文件头的总字节数量 = 字段数量 * 32 + 32 + 1 60 | const header_length = this.fields.length * 32 + 32 + 1; 61 | const record_num = this.records ? this.records.length : 0; 62 | const total_record_length = this.fields.reduce((sum, record) => { 63 | return sum + (record.length as number); 64 | }, 0) + 1; 65 | // 0 版本号 66 | header_define.set(DBF_FILE_VERSION, 0); 67 | // 1~3 更新日期 68 | header_define.set(get_update_date(this.create_date), 1); 69 | // 4~7 记录数量 Uint32 70 | header_define.set(new Uint8Array(Uint32Array.of(record_num).buffer), 4); 71 | // 8~9 当前DBF的文件头占用的字节长度 Uint16 72 | header_define.set(new Uint8Array(Uint16Array.of(header_length).buffer), 8); 73 | // 10~11 每条记录的长度 Uint16 74 | header_define.set( 75 | new Uint8Array(Int16Array.of(total_record_length).buffer), 76 | 10, 77 | ); 78 | 79 | const record_define = mergeUint8Arrays( 80 | this.fields.map((field) => field.record_define), 81 | ); 82 | 83 | return mergeUint8Arrays([header_define, record_define, RECORD_END]); 84 | } 85 | 86 | /** 87 | * 获取完整的DBF文件二进制数据 88 | * @returns {Uint8Array} 包含文件头和数据记录的完整DBF文件二进制 89 | * @description 90 | * 当无记录时返回仅含文件头和EOF标记的空文件 91 | * 有记录时返回格式: [文件头|记录1|记录2|...|EOF] 92 | */ 93 | get data(): Uint8Array { 94 | if (!this.records) { 95 | return mergeUint8Arrays([this.header, EOF]); 96 | } 97 | 98 | const records_array = this.records.map((record) => 99 | record_to_uint8array(this.fields, record) 100 | ); 101 | 102 | return mergeUint8Arrays([this.header, ...records_array, EOF]); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/libs/src/ovmap.ts: -------------------------------------------------------------------------------- 1 | import { encode } from "./base64.ts"; 2 | 3 | // ov base64 编码函数 4 | // 移除"=" | 替换'/'为'-' | 替换'+'为'_' 5 | const ov_encode = (str: string): string => { 6 | const encoded = encode(str); 7 | return encoded.replace(/=/g, "").replace(/\//g, "-").replace(/\+/g, "_"); 8 | }; 9 | 10 | export interface OVMapOptions { 11 | /** 12 | * 分组名称 13 | * 对应参数 gp 14 | */ 15 | group?: string; 16 | 17 | /** 18 | * 地图投影类型 19 | * 对应参数 pn 20 | * pn = 0 或未设置 - `'EPSG:3857'`: Web Mercator (墨卡托全球) - 默认值 21 | * pn = 1 - `'GCJ02'`: GCJ02 (墨卡托中国) 22 | * pn = 2- `'EPSG:4326'`: WGS84 (经纬度投影) 23 | */ 24 | crs?: "EPSG:3857" | "GCJ02" | "EPSG:4326"; 25 | 26 | /** 27 | * 瓦片大小 256px或 512px 28 | * 对应参数 ms 29 | * ms = 256(默认 可不提供此参数) 30 | * ms = 512 31 | */ 32 | size?: 256 | 512; 33 | /** 34 | * 子域名起始值 35 | * 对应参数 hs 36 | */ 37 | 38 | subdomain_start?: number; 39 | 40 | /** 41 | * 子域名结束值 42 | * 对应参数 he 43 | */ 44 | subdomain_end?: number; 45 | 46 | /** 47 | * 瓦片图像格式 48 | * 对应参数 mf 49 | * 未设置时,默认为 PNG 格式 50 | * `mf=3` 表示 JPG 格式 51 | */ 52 | format?: "PNG" | "JPG"; 53 | } 54 | 55 | export class OVMap { 56 | id: number; 57 | name: string; 58 | url: URL; 59 | crs: "EPSG:3857" | "GCJ02" | "EPSG:4326"; 60 | size: 256 | 512; 61 | subdomain_start: number; 62 | subdomain_end: number; 63 | host: string; 64 | path: string; 65 | protocol: string; 66 | port: string; 67 | options: OVMapOptions; 68 | 69 | constructor( 70 | id: number, 71 | name: string, 72 | url: URL | string, 73 | options: OVMapOptions = {}, 74 | ) { 75 | this.id = id; 76 | this.name = name; 77 | this.url = typeof url === "string" ? new URL(url) : url; 78 | this.options = options; 79 | // 设置选项和默认值 80 | this.crs = options.crs ?? "EPSG:3857"; 81 | this.size = options.size ?? 256; 82 | this.subdomain_start = options.subdomain_start ?? 0; 83 | this.subdomain_end = options.subdomain_end ?? 0; 84 | 85 | // 根据 URL 提取主机、路径、协议等信息 86 | this.host = this.url.hostname || ""; 87 | this.path = (this.url.pathname || "") + (this.url.search || ""); 88 | this.protocol = (this.url.protocol || "https").replace(":", ""); 89 | // 猜测端口 90 | const gussing_port = this.protocol === "https" ? "443" : "80"; 91 | this.port = this.url.port || gussing_port; 92 | } 93 | 94 | get qr_data(): string { 95 | const params = new URLSearchParams(); 96 | params.set("t", "37"); 97 | params.set("id", `${this.id}`); 98 | params.set("na", ov_encode(this.name)); 99 | params.set("hn", ov_encode(this.host)); 100 | params.set("ul", ov_encode(this.path)); 101 | params.set("pt", this.port); 102 | params.set("po", "1"); 103 | if (this.protocol === "http") { 104 | params.set("po", "0"); 105 | } 106 | 107 | // 分组名称 108 | if (this.options.group) { 109 | params.set("gp", ov_encode(this.options.group)); 110 | } 111 | 112 | // 瓦片尺寸 默认256px可不提供此参数 113 | if (this.options.size === 512) { 114 | params.set("ms", "512"); 115 | } 116 | 117 | // 瓦片图像格式 默认PNG可不提供此参数 118 | if (this.options.format === "JPG") { 119 | params.set("mf", "3"); 120 | } 121 | 122 | if (this.options.subdomain_start) { 123 | params.set("hs", this.options.subdomain_start.toString()); 124 | } 125 | if (this.options.subdomain_end) { 126 | params.set("hs", this.options.subdomain_end.toString()); 127 | } 128 | 129 | const crs_value = { 130 | "EPSG:3857": "0", 131 | "GCJ02": "1", 132 | "EPSG:4326": "2", 133 | }; 134 | params.set("pn", crs_value[this.crs]); 135 | 136 | return `ovobj?${params.toString()}`; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /packages/libs/src/dbf/record.ts: -------------------------------------------------------------------------------- 1 | import { mergeUint8Arrays } from "../array.ts"; 2 | import type { Field } from "./field.ts"; 3 | import { date_to_str, encode_text } from "./utils.ts"; 4 | 5 | /** 6 | * 一条记录,由不同字段类型值组成的数组 7 | */ 8 | export type RecordValue = (string | number | boolean | Date | null)[]; 9 | 10 | /** 11 | * Record deleted flag 12 | * `0x20`:有效, `0x2A`:删除 13 | */ 14 | const RECORD_FLAG = Uint8Array.of(0x20); 15 | 16 | export function get_value_info(value: string | number | boolean | Date | null) { 17 | if (typeof value === "boolean") { 18 | return { type: "L", value: value ? "T" : "F" }; 19 | } 20 | if (typeof value === "string") { 21 | return { type: "C", value }; 22 | } 23 | if (typeof value === "number") { 24 | return { type: "N", value: value.toString() }; 25 | } 26 | if (value instanceof Date) { 27 | return { type: "D", value: date_to_str(value) }; 28 | } 29 | return { type: "null", value: "" }; 30 | } 31 | 32 | /** 33 | * 创建 DBF 字段值数据 34 | * @param {string} value - 字段值的字符串表示 35 | * @param {number} length - 字段长度 36 | * @param {'left'|'right'} alignment - 对齐方式: 37 | * 'left': 左对齐(值在开头,空格在结尾) - DBF 文本字段常用 38 | * 'right': 右对齐(空格在开头,值在结尾) - DBF 数字字段常用 39 | * @returns {Uint8Array} 字段值 40 | * @throws 当字段值编码后超过字段宽度时 41 | */ 42 | export function create_record_data( 43 | value: string, 44 | length: number, 45 | alignment: "left" | "right" = "left", 46 | ): Uint8Array { 47 | const string_length = value.length; 48 | if (string_length > length) { 49 | throw new Error(`String length exceeds limit: ${length}`); 50 | } 51 | 52 | const stringData = encode_text(value); 53 | 54 | // 创建主数组(全部初始化为0x20) 55 | const uint8Array = new Uint8Array(length).fill(0x20); 56 | 57 | if (alignment === "left") { 58 | // 左对齐:值在开头,空格在结尾 59 | uint8Array.set(stringData, 0); 60 | } else { 61 | // 右对齐:空格在开头,值在结尾 62 | uint8Array.set(stringData, length - string_length); 63 | } 64 | 65 | return uint8Array; 66 | } 67 | 68 | export function record_to_uint8array(fields: Field[], record: RecordValue) { 69 | if (fields.length !== record.length) { 70 | throw new Error("记录所包含的值数量与字段定义不同"); 71 | } 72 | const data_array = record.map((value, index) => { 73 | const field = fields[index]; 74 | const value_info = get_value_info(value); 75 | if (value_info.type === "null") { 76 | // 对于数值型字段,空值用 * 填充 77 | if (field.type === "N") { 78 | return create_record_data( 79 | "*".repeat(field.length as number), 80 | field.length as number, 81 | "right", 82 | ); 83 | // 对于日期型字段,空值用 00000000 填充 84 | } else if (field.type === "D") { 85 | return create_record_data("00000000", field.length as number); 86 | } else { 87 | // 对于其他类型字段,空值用 空格 填充 88 | return create_record_data( 89 | " ".repeat(field.length as number), 90 | field.length as number, 91 | ); 92 | } 93 | } else if (field.type !== value_info.type) { 94 | throw new Error("记录类型与字段定义不符合"); 95 | } 96 | if (value_info.type === "N") { 97 | if ((field.precision as number) === 0) { 98 | return create_record_data( 99 | parseInt((value as number).toString()).toString(), 100 | field.length as number, 101 | "right", 102 | ); 103 | } 104 | if ((field.precision as number) > 0) { 105 | const number_value = (value as number).toFixed(field.precision); 106 | return create_record_data( 107 | number_value, 108 | field.length as number, 109 | "right", 110 | ); 111 | } 112 | } 113 | return create_record_data(value_info.value, field.length as number); 114 | }); 115 | 116 | return mergeUint8Arrays([RECORD_FLAG, ...data_array]); 117 | } 118 | -------------------------------------------------------------------------------- /packages/create-shp/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@liuxspro/libs@~0.1.9": "0.1.9", 5 | "jsr:@std/assert@*": "1.0.16", 6 | "jsr:@std/fs@*": "1.0.18", 7 | "jsr:@std/internal@^1.0.12": "1.0.12", 8 | "jsr:@std/path@*": "1.1.0", 9 | "jsr:@std/path@^1.1.0": "1.1.0", 10 | "npm:@mapbox/shp-write@~0.4.3": "0.4.3", 11 | "npm:jszip@^3.10.1": "3.10.1", 12 | "npm:papaparse@^5.5.3": "5.5.3" 13 | }, 14 | "jsr": { 15 | "@liuxspro/libs@0.1.9": { 16 | "integrity": "75b06a61ee9028c9152f5af4cb5695feccaae433b75fbb0d9cc743a2e58bf26a" 17 | }, 18 | "@std/assert@1.0.16": { 19 | "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", 20 | "dependencies": [ 21 | "jsr:@std/internal" 22 | ] 23 | }, 24 | "@std/fs@1.0.18": { 25 | "integrity": "24bcad99eab1af4fde75e05da6e9ed0e0dce5edb71b7e34baacf86ffe3969f3a", 26 | "dependencies": [ 27 | "jsr:@std/path@^1.1.0" 28 | ] 29 | }, 30 | "@std/internal@1.0.12": { 31 | "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 32 | }, 33 | "@std/path@1.1.0": { 34 | "integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886" 35 | } 36 | }, 37 | "npm": { 38 | "@mapbox/shp-write@0.4.3": { 39 | "integrity": "sha512-mkKIHgtnytyP+cXfk+joYeWwk+SODZ7COQusTcO1rZQVUn68MjzW2F74XMsNIusabnwj5aoKNI8B3mYq9KZ9AQ==", 40 | "dependencies": [ 41 | "dbf", 42 | "file-saver", 43 | "jszip" 44 | ] 45 | }, 46 | "core-util-is@1.0.3": { 47 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 48 | }, 49 | "dbf@0.2.0": { 50 | "integrity": "sha512-JMeGCJzFcVGsfnkIuqrnuiSkJpTu6c4AKJg3LXDnfW7zU/2PSIue3KG4fz9c+/mmlDzT+rVCwyEHzzhxzrzPiA==", 51 | "dependencies": [ 52 | "jdataview" 53 | ] 54 | }, 55 | "file-saver@2.0.5": { 56 | "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" 57 | }, 58 | "immediate@3.0.6": { 59 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 60 | }, 61 | "inherits@2.0.4": { 62 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 63 | }, 64 | "isarray@1.0.0": { 65 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 66 | }, 67 | "jdataview@2.5.0": { 68 | "integrity": "sha512-ZJop3D5nyDcWPBPv4NPnhCvx3HgQNsCXMfw8gpNKY16BobgxmVF+kJ08aHuqk6bJQVeL2mkf6nDCcZPMompalw==" 69 | }, 70 | "jszip@3.10.1": { 71 | "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 72 | "dependencies": [ 73 | "lie", 74 | "pako", 75 | "readable-stream", 76 | "setimmediate" 77 | ] 78 | }, 79 | "lie@3.3.0": { 80 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 81 | "dependencies": [ 82 | "immediate" 83 | ] 84 | }, 85 | "pako@1.0.11": { 86 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 87 | }, 88 | "papaparse@5.5.3": { 89 | "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==" 90 | }, 91 | "process-nextick-args@2.0.1": { 92 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 93 | }, 94 | "readable-stream@2.3.8": { 95 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 96 | "dependencies": [ 97 | "core-util-is", 98 | "inherits", 99 | "isarray", 100 | "process-nextick-args", 101 | "safe-buffer", 102 | "string_decoder", 103 | "util-deprecate" 104 | ] 105 | }, 106 | "safe-buffer@5.1.2": { 107 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 108 | }, 109 | "setimmediate@1.0.5": { 110 | "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" 111 | }, 112 | "string_decoder@1.1.1": { 113 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 114 | "dependencies": [ 115 | "safe-buffer" 116 | ] 117 | }, 118 | "util-deprecate@1.0.2": { 119 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 120 | } 121 | }, 122 | "workspace": { 123 | "dependencies": [ 124 | "jsr:@liuxspro/libs@~0.1.9", 125 | "npm:@mapbox/shp-write@~0.4.3", 126 | "npm:jszip@^3.10.1", 127 | "npm:papaparse@^5.5.3" 128 | ] 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /packages/webp_to_png/src/main_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | import { webp_to_png } from "./main.ts"; 3 | 4 | Deno.test("converted data should be a png file", async () => { 5 | // deno-fmt-ignore 6 | const webp_data = new Uint8Array([ 7 | 0x52, 0x49, 0x46, 0x46, 0xd8, 0x02, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 8 | 0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 9 | 0x47, 0x00, 0x00, 0x47, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x7d, 0x02, 10 | 0x00, 0x00, 0x01, 0x80, 0x55, 0x6d, 0x5b, 0x10, 0xe9, 0x8b, 0xf0, 0x37, 11 | 0x98, 0xbf, 0xc1, 0xd2, 0x40, 0x1a, 0x8c, 0x0d, 0xb4, 0x81, 0x36, 0xc0, 12 | 0x06, 0x6c, 0x03, 0xb6, 0x81, 0x11, 0x88, 0x60, 0x84, 0x3f, 0x02, 0x11, 13 | 0xbe, 0x0b, 0xd0, 0x41, 0xe7, 0x6e, 0xaf, 0x22, 0x62, 0x02, 0xf0, 0x4f, 14 | 0x5a, 0x01, 0x68, 0x12, 0x00, 0xfa, 0x5d, 0xca, 0x0d, 0x48, 0xdc, 0x81, 15 | 0x8d, 0xfe, 0x6b, 0xd6, 0x09, 0x48, 0xe4, 0xa6, 0x24, 0xfd, 0x46, 0x46, 16 | 0x60, 0x8d, 0x5f, 0x41, 0x26, 0x67, 0xbc, 0x5a, 0xdc, 0x1f, 0x29, 0x4f, 17 | 0x1b, 0x01, 0xc7, 0xbe, 0x0e, 0x32, 0xca, 0x83, 0x46, 0xda, 0xf6, 0x7b, 18 | 0x61, 0xc4, 0x7a, 0x21, 0x6e, 0x85, 0xd3, 0x83, 0x3c, 0x2f, 0x1b, 0x80, 19 | 0x0b, 0xf5, 0x63, 0x9c, 0x5c, 0xb2, 0x68, 0x5c, 0xb1, 0xd2, 0xd2, 0x71, 20 | 0x61, 0x80, 0xb8, 0x27, 0x28, 0x99, 0x73, 0xab, 0x84, 0x17, 0xa0, 0x46, 21 | 0xd2, 0x14, 0xd0, 0x60, 0x2d, 0xcb, 0xa4, 0x7b, 0x00, 0x0a, 0xdb, 0x25, 22 | 0x08, 0x6a, 0x2d, 0x34, 0x45, 0x73, 0xb6, 0xaa, 0xc6, 0x13, 0x73, 0x6b, 23 | 0x17, 0x9c, 0xea, 0x22, 0x38, 0xd5, 0xd4, 0xca, 0xf7, 0xcd, 0xb3, 0x3f, 24 | 0x1a, 0x0b, 0x7a, 0xaf, 0x8d, 0x63, 0x9c, 0x47, 0xb9, 0xc5, 0xf1, 0x74, 25 | 0x42, 0x7f, 0x57, 0xd8, 0x7e, 0xdf, 0x02, 0x6b, 0x1d, 0xa1, 0xeb, 0xa8, 26 | 0x15, 0x7c, 0xcb, 0x70, 0x6f, 0x68, 0x75, 0xcf, 0x0a, 0x00, 0x6b, 0x63, 27 | 0xb9, 0x45, 0xdc, 0xef, 0x4d, 0xe4, 0x06, 0x00, 0x7b, 0x95, 0xbc, 0xf4, 28 | 0x0b, 0x7c, 0xe2, 0x0a, 0x40, 0x0a, 0x9b, 0x7b, 0xb7, 0xf1, 0x11, 0x45, 29 | 0x00, 0x65, 0x7b, 0xea, 0x86, 0xf8, 0x04, 0x06, 0x20, 0xb5, 0x12, 0xfa, 30 | 0x3b, 0xf2, 0x10, 0x35, 0x2e, 0x53, 0x3a, 0x4b, 0xa1, 0x99, 0x2e, 0x24, 31 | 0x28, 0xed, 0xc7, 0x19, 0xe9, 0xba, 0xf9, 0x58, 0xc8, 0x09, 0x88, 0x5c, 32 | 0x30, 0x9f, 0x0d, 0x68, 0xfb, 0x2b, 0x33, 0x17, 0x20, 0x90, 0x25, 0xf9, 33 | 0x3e, 0xc2, 0x7a, 0x87, 0x18, 0xf7, 0x2b, 0xee, 0x04, 0x67, 0x13, 0x32, 34 | 0x33, 0x70, 0xb0, 0x7e, 0x75, 0x41, 0xa8, 0x58, 0x0a, 0x8b, 0xbb, 0xf2, 35 | 0x6a, 0xc9, 0xef, 0x89, 0x01, 0x2b, 0x69, 0x85, 0x75, 0x40, 0xe7, 0xb5, 36 | 0x62, 0x9e, 0x80, 0x2b, 0xd6, 0x2c, 0x3c, 0x35, 0x05, 0xa0, 0xc1, 0x58, 37 | 0x2f, 0xe8, 0xed, 0xab, 0x88, 0xfa, 0xc2, 0xa7, 0xa6, 0x68, 0xc7, 0x6a, 38 | 0xea, 0x24, 0xbf, 0xac, 0xa7, 0x7b, 0x1c, 0x4e, 0xe7, 0x8a, 0x49, 0xbb, 39 | 0x44, 0x3e, 0xc1, 0xf4, 0x03, 0xa6, 0x2e, 0x1a, 0x1b, 0xcb, 0x3d, 0xb4, 40 | 0x86, 0xfa, 0xd8, 0x88, 0xd2, 0x05, 0xd0, 0x2a, 0x7c, 0xb2, 0x0c, 0x75, 41 | 0x3a, 0xa3, 0xaf, 0x12, 0x9b, 0x03, 0x7a, 0x6f, 0x55, 0xfa, 0x64, 0x40, 42 | 0x73, 0x3d, 0x4b, 0xd5, 0xde, 0xda, 0x7a, 0x6d, 0xac, 0xed, 0x93, 0x57, 43 | 0x0b, 0xe5, 0xc4, 0x00, 0x88, 0xb5, 0xb8, 0xf5, 0x51, 0xb6, 0x43, 0xbd, 44 | 0x9f, 0xbd, 0xbd, 0xf7, 0x1e, 0xc0, 0x7e, 0xc2, 0x34, 0x2f, 0x07, 0xcf, 45 | 0xa5, 0x0b, 0x34, 0x59, 0xd5, 0x15, 0xc0, 0x72, 0x76, 0xb5, 0xc4, 0x01, 46 | 0xbd, 0xf5, 0x0e, 0xdf, 0xc3, 0xa1, 0x7f, 0xba, 0x43, 0x7a, 0xec, 0xfd, 47 | 0x66, 0xde, 0x81, 0xdc, 0x81, 0x6b, 0xb7, 0xf5, 0x1e, 0xb5, 0x0e, 0xb1, 48 | 0x1b, 0xe0, 0x62, 0x23, 0xa7, 0x4f, 0x2b, 0x68, 0x4c, 0xed, 0xdc, 0x88, 49 | 0x4e, 0x70, 0xe7, 0xdc, 0x02, 0x00, 0x41, 0xf7, 0xbd, 0x11, 0x70, 0xaf, 50 | 0x35, 0xb8, 0xc7, 0x5c, 0xb8, 0x76, 0xd8, 0x78, 0xec, 0xcb, 0x1f, 0x9b, 51 | 0x45, 0x6e, 0x19, 0x79, 0x5e, 0xc8, 0xa3, 0x83, 0xf1, 0xf2, 0xfb, 0x16, 52 | 0xbc, 0x87, 0x61, 0xaf, 0x76, 0xd1, 0xa3, 0xc8, 0x47, 0x4a, 0x13, 0xb5, 53 | 0xea, 0x18, 0x86, 0xb7, 0xdc, 0x03, 0x20, 0x57, 0x13, 0x10, 0xe8, 0x3e, 54 | 0x1a, 0xb9, 0x00, 0xa1, 0xca, 0x78, 0x62, 0xa9, 0x0e, 0x55, 0x63, 0xfc, 55 | 0x28, 0x31, 0x8b, 0x58, 0x45, 0x79, 0x80, 0xb2, 0xe4, 0xcc, 0xb6, 0x45, 56 | 0x7f, 0xc1, 0x6f, 0x85, 0x64, 0x29, 0x64, 0xce, 0x85, 0xee, 0x01, 0x10, 57 | 0xc0, 0x9f, 0xd4, 0x79, 0x4f, 0x21, 0xa5, 0xa3, 0xf0, 0xe2, 0x1b, 0x78, 58 | 0xe1, 0xa1, 0xd7, 0x7a, 0x4e, 0x78, 0xee, 0xc8, 0x12, 0x62, 0xa7, 0x38, 59 | 0x1b, 0xdf, 0x0f, 0xc2, 0x00, 0x68, 0xa7, 0x01, 0x78, 0xcb, 0x93, 0x00, 60 | 0x08, 0x19, 0x5f, 0xe5, 0x92, 0x69, 0x24, 0x05, 0x5f, 0x18, 0x26, 0x20, 61 | 0x90, 0xc1, 0x93, 0xfc, 0x99, 0xc9, 0x04, 0xcc, 0x0b, 0xbe, 0xd5, 0x33, 62 | 0x00, 0x91, 0x09, 0x08, 0x1c, 0xf0, 0xd5, 0x02, 0x40, 0xa2, 0x02, 0x10, 63 | 0xfc, 0xb7, 0x07, 0x00, 0x56, 0x50, 0x38, 0x20, 0x34, 0x00, 0x00, 0x00, 64 | 0x10, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x48, 0x00, 0x48, 0x00, 0x3e, 0x91, 65 | 0x48, 0xa1, 0x4c, 0xa5, 0xa4, 0x23, 0x22, 0x21, 0x48, 0x00, 0xb0, 0x12, 66 | 0x09, 0x69, 0x00, 0x00, 0x09, 0xf1, 0xa3, 0x46, 0x8d, 0x1a, 0x34, 0x68, 67 | 0xd1, 0xa3, 0x46, 0x7e, 0x00, 0x00, 0xfe, 0xf8, 0xce, 0xf1, 0x48, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 69 | ]); 70 | const png_data = await webp_to_png(webp_data); 71 | assertEquals( 72 | png_data.slice(0, 8), 73 | new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), 74 | ); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/webp_to_png/bench/bench.ts: -------------------------------------------------------------------------------- 1 | import { webp_to_png } from "../src/main.ts"; 2 | import sharp from "npm:sharp"; 3 | 4 | async function sharp_webp_to_png(webpdata: Uint8Array) { 5 | try { 6 | const png_data = await sharp(webpdata).png().toBuffer(); 7 | return new Uint8Array(png_data); 8 | } catch (error) { 9 | console.error("Error converting image:", error); 10 | } 11 | } 12 | // deno-fmt-ignore 13 | const webp_data = new Uint8Array([ 14 | 0x52, 0x49, 0x46, 0x46, 0xd8, 0x02, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 15 | 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 16 | 0x00, 0x47, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x7d, 0x02, 0x00, 0x00, 0x01, 17 | 0x80, 0x55, 0x6d, 0x5b, 0x10, 0xe9, 0x8b, 0xf0, 0x37, 0x98, 0xbf, 0xc1, 0xd2, 18 | 0x40, 0x1a, 0x8c, 0x0d, 0xb4, 0x81, 0x36, 0xc0, 0x06, 0x6c, 0x03, 0xb6, 0x81, 19 | 0x11, 0x88, 0x60, 0x84, 0x3f, 0x02, 0x11, 0xbe, 0x0b, 0xd0, 0x41, 0xe7, 0x6e, 20 | 0xaf, 0x22, 0x62, 0x02, 0xf0, 0x4f, 0x5a, 0x01, 0x68, 0x12, 0x00, 0xfa, 0x5d, 21 | 0xca, 0x0d, 0x48, 0xdc, 0x81, 0x8d, 0xfe, 0x6b, 0xd6, 0x09, 0x48, 0xe4, 0xa6, 22 | 0x24, 0xfd, 0x46, 0x46, 0x60, 0x8d, 0x5f, 0x41, 0x26, 0x67, 0xbc, 0x5a, 0xdc, 23 | 0x1f, 0x29, 0x4f, 0x1b, 0x01, 0xc7, 0xbe, 0x0e, 0x32, 0xca, 0x83, 0x46, 0xda, 24 | 0xf6, 0x7b, 0x61, 0xc4, 0x7a, 0x21, 0x6e, 0x85, 0xd3, 0x83, 0x3c, 0x2f, 0x1b, 25 | 0x80, 0x0b, 0xf5, 0x63, 0x9c, 0x5c, 0xb2, 0x68, 0x5c, 0xb1, 0xd2, 0xd2, 0x71, 26 | 0x61, 0x80, 0xb8, 0x27, 0x28, 0x99, 0x73, 0xab, 0x84, 0x17, 0xa0, 0x46, 0xd2, 27 | 0x14, 0xd0, 0x60, 0x2d, 0xcb, 0xa4, 0x7b, 0x00, 0x0a, 0xdb, 0x25, 0x08, 0x6a, 28 | 0x2d, 0x34, 0x45, 0x73, 0xb6, 0xaa, 0xc6, 0x13, 0x73, 0x6b, 0x17, 0x9c, 0xea, 29 | 0x22, 0x38, 0xd5, 0xd4, 0xca, 0xf7, 0xcd, 0xb3, 0x3f, 0x1a, 0x0b, 0x7a, 0xaf, 30 | 0x8d, 0x63, 0x9c, 0x47, 0xb9, 0xc5, 0xf1, 0x74, 0x42, 0x7f, 0x57, 0xd8, 0x7e, 31 | 0xdf, 0x02, 0x6b, 0x1d, 0xa1, 0xeb, 0xa8, 0x15, 0x7c, 0xcb, 0x70, 0x6f, 0x68, 32 | 0x75, 0xcf, 0x0a, 0x00, 0x6b, 0x63, 0xb9, 0x45, 0xdc, 0xef, 0x4d, 0xe4, 0x06, 33 | 0x00, 0x7b, 0x95, 0xbc, 0xf4, 0x0b, 0x7c, 0xe2, 0x0a, 0x40, 0x0a, 0x9b, 0x7b, 34 | 0xb7, 0xf1, 0x11, 0x45, 0x00, 0x65, 0x7b, 0xea, 0x86, 0xf8, 0x04, 0x06, 0x20, 35 | 0xb5, 0x12, 0xfa, 0x3b, 0xf2, 0x10, 0x35, 0x2e, 0x53, 0x3a, 0x4b, 0xa1, 0x99, 36 | 0x2e, 0x24, 0x28, 0xed, 0xc7, 0x19, 0xe9, 0xba, 0xf9, 0x58, 0xc8, 0x09, 0x88, 37 | 0x5c, 0x30, 0x9f, 0x0d, 0x68, 0xfb, 0x2b, 0x33, 0x17, 0x20, 0x90, 0x25, 0xf9, 38 | 0x3e, 0xc2, 0x7a, 0x87, 0x18, 0xf7, 0x2b, 0xee, 0x04, 0x67, 0x13, 0x32, 0x33, 39 | 0x70, 0xb0, 0x7e, 0x75, 0x41, 0xa8, 0x58, 0x0a, 0x8b, 0xbb, 0xf2, 0x6a, 0xc9, 40 | 0xef, 0x89, 0x01, 0x2b, 0x69, 0x85, 0x75, 0x40, 0xe7, 0xb5, 0x62, 0x9e, 0x80, 41 | 0x2b, 0xd6, 0x2c, 0x3c, 0x35, 0x05, 0xa0, 0xc1, 0x58, 0x2f, 0xe8, 0xed, 0xab, 42 | 0x88, 0xfa, 0xc2, 0xa7, 0xa6, 0x68, 0xc7, 0x6a, 0xea, 0x24, 0xbf, 0xac, 0xa7, 43 | 0x7b, 0x1c, 0x4e, 0xe7, 0x8a, 0x49, 0xbb, 0x44, 0x3e, 0xc1, 0xf4, 0x03, 0xa6, 44 | 0x2e, 0x1a, 0x1b, 0xcb, 0x3d, 0xb4, 0x86, 0xfa, 0xd8, 0x88, 0xd2, 0x05, 0xd0, 45 | 0x2a, 0x7c, 0xb2, 0x0c, 0x75, 0x3a, 0xa3, 0xaf, 0x12, 0x9b, 0x03, 0x7a, 0x6f, 46 | 0x55, 0xfa, 0x64, 0x40, 0x73, 0x3d, 0x4b, 0xd5, 0xde, 0xda, 0x7a, 0x6d, 0xac, 47 | 0xed, 0x93, 0x57, 0x0b, 0xe5, 0xc4, 0x00, 0x88, 0xb5, 0xb8, 0xf5, 0x51, 0xb6, 48 | 0x43, 0xbd, 0x9f, 0xbd, 0xbd, 0xf7, 0x1e, 0xc0, 0x7e, 0xc2, 0x34, 0x2f, 0x07, 49 | 0xcf, 0xa5, 0x0b, 0x34, 0x59, 0xd5, 0x15, 0xc0, 0x72, 0x76, 0xb5, 0xc4, 0x01, 50 | 0xbd, 0xf5, 0x0e, 0xdf, 0xc3, 0xa1, 0x7f, 0xba, 0x43, 0x7a, 0xec, 0xfd, 0x66, 51 | 0xde, 0x81, 0xdc, 0x81, 0x6b, 0xb7, 0xf5, 0x1e, 0xb5, 0x0e, 0xb1, 0x1b, 0xe0, 52 | 0x62, 0x23, 0xa7, 0x4f, 0x2b, 0x68, 0x4c, 0xed, 0xdc, 0x88, 0x4e, 0x70, 0xe7, 53 | 0xdc, 0x02, 0x00, 0x41, 0xf7, 0xbd, 0x11, 0x70, 0xaf, 0x35, 0xb8, 0xc7, 0x5c, 54 | 0xb8, 0x76, 0xd8, 0x78, 0xec, 0xcb, 0x1f, 0x9b, 0x45, 0x6e, 0x19, 0x79, 0x5e, 55 | 0xc8, 0xa3, 0x83, 0xf1, 0xf2, 0xfb, 0x16, 0xbc, 0x87, 0x61, 0xaf, 0x76, 0xd1, 56 | 0xa3, 0xc8, 0x47, 0x4a, 0x13, 0xb5, 0xea, 0x18, 0x86, 0xb7, 0xdc, 0x03, 0x20, 57 | 0x57, 0x13, 0x10, 0xe8, 0x3e, 0x1a, 0xb9, 0x00, 0xa1, 0xca, 0x78, 0x62, 0xa9, 58 | 0x0e, 0x55, 0x63, 0xfc, 0x28, 0x31, 0x8b, 0x58, 0x45, 0x79, 0x80, 0xb2, 0xe4, 59 | 0xcc, 0xb6, 0x45, 0x7f, 0xc1, 0x6f, 0x85, 0x64, 0x29, 0x64, 0xce, 0x85, 0xee, 60 | 0x01, 0x10, 0xc0, 0x9f, 0xd4, 0x79, 0x4f, 0x21, 0xa5, 0xa3, 0xf0, 0xe2, 0x1b, 61 | 0x78, 0xe1, 0xa1, 0xd7, 0x7a, 0x4e, 0x78, 0xee, 0xc8, 0x12, 0x62, 0xa7, 0x38, 62 | 0x1b, 0xdf, 0x0f, 0xc2, 0x00, 0x68, 0xa7, 0x01, 0x78, 0xcb, 0x93, 0x00, 0x08, 63 | 0x19, 0x5f, 0xe5, 0x92, 0x69, 0x24, 0x05, 0x5f, 0x18, 0x26, 0x20, 0x90, 0xc1, 64 | 0x93, 0xfc, 0x99, 0xc9, 0x04, 0xcc, 0x0b, 0xbe, 0xd5, 0x33, 0x00, 0x91, 0x09, 65 | 0x08, 0x1c, 0xf0, 0xd5, 0x02, 0x40, 0xa2, 0x02, 0x10, 0xfc, 0xb7, 0x07, 0x00, 66 | 0x56, 0x50, 0x38, 0x20, 0x34, 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x9d, 0x01, 67 | 0x2a, 0x48, 0x00, 0x48, 0x00, 0x3e, 0x91, 0x48, 0xa1, 0x4c, 0xa5, 0xa4, 0x23, 68 | 0x22, 0x21, 0x48, 0x00, 0xb0, 0x12, 0x09, 0x69, 0x00, 0x00, 0x09, 0xf1, 0xa3, 69 | 0x46, 0x8d, 0x1a, 0x34, 0x68, 0xd1, 0xa3, 0x46, 0x7e, 0x00, 0x00, 0xfe, 0xf8, 70 | 0xce, 0xf1, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | ]); 72 | 73 | Deno.bench("webp_to_png", async () => { 74 | await webp_to_png(webp_data); 75 | }); 76 | 77 | Deno.bench("sharp_webp_to_png", async () => { 78 | await sharp_webp_to_png(webp_data); 79 | }); 80 | -------------------------------------------------------------------------------- /packages/capgen/src/matrix.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from "@es-toolkit/es-toolkit"; 2 | 3 | interface TileMatrix { 4 | identifier: string; 5 | scale_denominator: number; 6 | top_left_corner: [number, number]; 7 | tile_width: number; 8 | tile_height: number; 9 | matrix_width: number; 10 | matrix_height: number; 11 | } 12 | 13 | type MatrixConfig = { 14 | type: "3857" | "4326"; 15 | tileSize?: number; // 瓦片尺寸(默认256) 16 | baseScale?: number; // 可选自定义基准比例 17 | hdMode?: boolean; // 是否HD模式(自动调整基准比例) 18 | }; 19 | 20 | export function generate_tile_matrixs( 21 | minZoom: number, 22 | maxZoom: number, 23 | config: MatrixConfig = { type: "3857" }, 24 | ): TileMatrix[] { 25 | if (!Number.isInteger(minZoom) || !Number.isInteger(maxZoom)) { 26 | throw new Error("Zoom levels must be integers"); 27 | } 28 | 29 | const tileSize = config.tileSize || 256; 30 | let baseScale = config.baseScale ?? 559_082_264.028_7178; 31 | 32 | // HD模式自动调整基准比例 33 | if (config.hdMode && !config.baseScale) { 34 | baseScale /= 2; 35 | } 36 | 37 | return Array.from({ length: maxZoom - minZoom + 1 }, (_, index) => { 38 | const zoom = minZoom + index; 39 | const scale = baseScale / Math.pow(2, zoom); 40 | const matrixWidth = Math.pow(2, zoom); 41 | 42 | // CRS特定配置 43 | const isCRS4326 = config.type === "4326"; 44 | const matrixHeight = isCRS4326 ? matrixWidth / 2 : matrixWidth; 45 | const topLeftCorner: [number, number] = isCRS4326 46 | ? [90, -180] // EPSG:4326 47 | : [-20037508.3427892, 20037508.3427892]; // EPSG:3857 48 | 49 | return { 50 | identifier: zoom.toString(), 51 | scale_denominator: Number(scale), 52 | top_left_corner: topLeftCorner, 53 | tile_width: tileSize, 54 | tile_height: tileSize, 55 | matrix_width: matrixWidth, 56 | matrix_height: matrixHeight, 57 | }; 58 | }); 59 | } 60 | 61 | export class TileMatrixSet { 62 | public tile_matrixs: TileMatrix[]; 63 | /** 64 | * Creates a new TileMatrixSet instance 65 | * @param {string} title - Human-readable title of the tile matrix set 66 | * @param {string} id - Unique identifier for the tile matrix set 67 | * @param {string} supported_crs - Supported coordinate reference system 68 | * @param {string} wellknown_scale_set - Identifier for the well-known scale set 69 | * @param {number} min_zoom - Minimum zoom level (inclusive, non-negative) 70 | * @param {number} max_zoom - Maximum zoom level (inclusive, must be >= min_zoom) 71 | */ 72 | constructor( 73 | public title: string, 74 | public id: string, 75 | public supported_crs: string, 76 | public wellknown_scale_set: string, 77 | public min_zoom: number, 78 | public max_zoom: number, 79 | ) { 80 | this.tile_matrixs = generate_tile_matrixs(this.min_zoom, this.max_zoom, { 81 | type: "3857", 82 | }); 83 | } 84 | 85 | /** 86 | * Updates the zoom level range and regenerates tile matrices 87 | * @param {number} min_zoom - New minimum zoom level 88 | * @param {number} max_zoom - New maximum zoom level 89 | * @returns {this} Returns the instance for method chaining 90 | */ 91 | setZoom(min_zoom: number, max_zoom: number): this { 92 | this.min_zoom = min_zoom; 93 | this.max_zoom = max_zoom; 94 | this.tile_matrixs = this.generateMatrixs(min_zoom, max_zoom); 95 | if (min_zoom != 0 || max_zoom != 18) { 96 | this.setId(`${this.id}F${min_zoom}T${max_zoom}`); 97 | } 98 | return this; 99 | } 100 | /** 101 | * Set identifier of tilematrix set 102 | * @param {string} id identifier of tilematrix set 103 | * @returns {this} Returns the instance for method chaining 104 | */ 105 | setId(id: string): this { 106 | this.id = id; 107 | return this; 108 | } 109 | 110 | protected generateMatrixs(min: number, max: number): TileMatrix[] { 111 | return generate_tile_matrixs(min, max); 112 | } 113 | 114 | clone(): this { 115 | return cloneDeep(this); 116 | } 117 | } 118 | 119 | export class TileMatrixSetHd extends TileMatrixSet { 120 | constructor( 121 | title: string, 122 | id: string, 123 | supported_crs: string, 124 | wellknown_scale_set: string, 125 | min_zoom: number, 126 | max_zoom: number, 127 | ) { 128 | super(title, id, supported_crs, wellknown_scale_set, min_zoom, max_zoom); 129 | this.tile_matrixs = this.generateMatrixs(min_zoom, max_zoom); 130 | } 131 | 132 | protected override generateMatrixs(min: number, max: number): TileMatrix[] { 133 | return generate_tile_matrixs(min, max, { 134 | "type": "3857", 135 | tileSize: 512, 136 | hdMode: true, 137 | }); 138 | } 139 | } 140 | 141 | export class CRS84TileMatrixSet extends TileMatrixSet { 142 | constructor( 143 | title: string, 144 | id: string, 145 | supported_crs: string, 146 | wellknown_scale_set: string, 147 | min_zoom: number, 148 | max_zoom: number, 149 | ) { 150 | super(title, id, supported_crs, wellknown_scale_set, min_zoom, max_zoom); 151 | this.tile_matrixs = this.generateMatrixs(min_zoom, max_zoom); 152 | } 153 | 154 | protected override generateMatrixs(min: number, max: number): TileMatrix[] { 155 | return generate_tile_matrixs(min, max, { "type": "4326" }); 156 | } 157 | } 158 | 159 | export class CRS84LessTileMatrixSet extends CRS84TileMatrixSet { 160 | constructor( 161 | title: string, 162 | id: string, 163 | supported_crs: string, 164 | wellknown_scale_set: string, 165 | min_zoom: number, 166 | max_zoom: number, 167 | ) { 168 | super(title, id, supported_crs, wellknown_scale_set, min_zoom, max_zoom); 169 | this.tile_matrixs = this.generateMatrixs(min_zoom, max_zoom); 170 | } 171 | protected override generateMatrixs(min: number, max: number): TileMatrix[] { 172 | const matrix = generate_tile_matrixs(min, max, { "type": "4326" }); 173 | const matrix_less = matrix.map((t) => { 174 | const n = t.identifier; 175 | const less = parseInt(n) - 1; 176 | t.identifier = String(less); 177 | return t; 178 | }); 179 | return matrix_less; 180 | } 181 | } 182 | 183 | // 常用瓦片矩阵集 184 | // WebMercator (256px) 185 | // https://docs.ogc.org/is/17-083r2/17-083r2.html#73 186 | export const web_mercator_quad: TileMatrixSet = new TileMatrixSet( 187 | "Google Maps Compatible for the World", 188 | "WebMercatorQuad", 189 | "EPSG:3857", 190 | "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible", 191 | 0, 192 | 18, 193 | ); 194 | 195 | // web_mercator_quad (512px) 196 | export const web_mercator_quad_hd: TileMatrixSetHd = new TileMatrixSetHd( 197 | "Google Maps Compatible for the World", 198 | "WebMercatorQuadHd", 199 | "EPSG:3857", 200 | "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible", 201 | 0, 202 | 18, 203 | ); 204 | 205 | // https://docs.ogc.org/is/17-083r2/17-083r2.html#76 206 | // 此处使用了 EPSG:4326 变体 207 | export const world_crs84_quad: CRS84TileMatrixSet = new CRS84TileMatrixSet( 208 | "CRS84 for the World", 209 | "WorldCRS84Quad", 210 | "EPSG:4326", 211 | "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad", 212 | 1, 213 | 18, 214 | ); 215 | 216 | // 比正常的 4326 瓦片层级少 1 217 | export const world_crs84_quad_less: CRS84LessTileMatrixSet = 218 | new CRS84LessTileMatrixSet( 219 | "CRS84 for the World", 220 | "WorldCRS84QuadLess", 221 | "EPSG:4326", 222 | "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad", 223 | 1, 224 | 18, 225 | ); 226 | 227 | // 与 EPSG:4326 基本一样 228 | export const cgcs2000_quad: CRS84TileMatrixSet = new CRS84TileMatrixSet( 229 | "CRS84 for the World", 230 | "CGCS2000Quad", 231 | "EPSG:4490", 232 | "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad", 233 | 1, 234 | 18, 235 | ); 236 | 237 | // EPSG:3395 238 | // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51 239 | export const world_mercator_quad: TileMatrixSet = new TileMatrixSet( 240 | "CRS84 for the World", 241 | "WorldMercatorWGS84Quad", 242 | "EPSG:3395", 243 | "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84", 244 | 0, 245 | 18, 246 | ); 247 | -------------------------------------------------------------------------------- /packages/libs/src/geo/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示地理坐标点的数据类型 3 | * 4 | * 定义为包含两个数字的元组类型: 5 | * - 第一个元素为经度(longitude) 6 | * - 第二个元素为纬度(latitude) 7 | * 8 | * @example 9 | * const location: Point = [116.4074, 39.9042]; 10 | */ 11 | export type Point = number[] | [number, number]; 12 | 13 | /** 14 | * 环是点的数组 15 | * 环必须至少包含4个点(形成闭合图形) 16 | * 环会自动确保首尾点相同以形成闭合 17 | * @example 18 | * const ring = new Ring([[1,1], [1,2], [1,3], [1,1]]); 19 | */ 20 | export class Ring { 21 | /** 22 | * 组成环的点数组 23 | * 24 | * 每个点可以是 `[number, number]` 格式(推荐)或任意长度的 `number[]`, 25 | * 但只有前两个元素(x, y坐标)会被使用。点数组是原始点的浅拷贝。 26 | */ 27 | points: Point[]; 28 | /** 29 | * 创建一个环实例 30 | * 31 | * @param points 组成环的点数组 32 | * 33 | * @example 34 | * ```typescript 35 | * // 创建一个简单的矩形环 36 | * const ring = new Ring([ 37 | * [0, 0], // 起点 38 | * [10, 0], 39 | * [10, 10], 40 | * [0, 10], 41 | * [0, 0] // 终点(会自动闭合,可省略) 42 | * ]); 43 | * ``` 44 | * 45 | * @throws {Error} 当点数小于等于 3 时抛出错误 46 | */ 47 | constructor(points: Point[]) { 48 | // 创建浅拷贝,避免修改原数组 49 | this.points = [...points]; 50 | this.ensure_closed(); 51 | this.check_points_number(); 52 | } 53 | 54 | /** 55 | * 检查点数是否满足最小要求 56 | * 57 | * 一个有效的环至少需要3个不同的点(形成闭合后至少4个点) 58 | * 59 | * @throws {Error} 点数不足时抛出错误 60 | */ 61 | private check_points_number(): void { 62 | if (this.points.length <= 3) { 63 | throw new Error( 64 | `组成环的点数至少要 3 个, 当前只有 ${this.points.length} 个`, 65 | ); 66 | } 67 | } 68 | /** 69 | * 确保环是闭合的 70 | * 71 | * 如果首尾点坐标不同,自动在末尾添加与起点相同的点 72 | */ 73 | private ensure_closed() { 74 | const first_point = this.points[0]; 75 | const last_point = this.points[this.points.length - 1]; 76 | if (first_point[0] !== last_point[0] || first_point[1] !== last_point[1]) { 77 | this.points.push([first_point[0], first_point[1]]); 78 | } 79 | } 80 | 81 | /** 82 | * 对环的所有点进行坐标转换 83 | * 84 | * @param transformFn 坐标转换函数,接收一个点并返回转换后的点 85 | * @returns 返回新的 Ring 实例,包含转换后的点 86 | * 87 | * @example 88 | * ```typescript 89 | * const ring = new Ring([[0, 0], [10, 0], [10, 10], [0, 10]]); 90 | * 91 | * // 平移环 92 | * const translated = ring.transform(([x, y]) => [x + 5, y + 5]); 93 | * 94 | * // 缩放环 95 | * const scaled = ring.transform(([x, y]) => [x * 2, y * 2]); 96 | * ``` 97 | */ 98 | transform( 99 | transformFn: (point: Point) => Point, 100 | ): Ring { 101 | const transformedPoints = this.points.map(transformFn); 102 | return new Ring(transformedPoints); 103 | } 104 | 105 | /** 106 | * 将环转换为多边形 107 | * 108 | * 一个多边形由一个外环和零个或多个内环组成,此方法创建只包含当前环的多边形 109 | * 110 | * @returns 包含当前环的新Polygon实例 111 | * 112 | * @example 113 | * ```typescript 114 | * const ring = new Ring([[0, 0], [10, 0], [10, 10], [0, 10]]); 115 | * const polygon = ring.to_polygon(); 116 | * ``` 117 | */ 118 | to_polygon(): Polygon { 119 | return new Polygon([this]); 120 | } 121 | 122 | /** 123 | * 将环转换为多多边形 124 | * 125 | * 创建一个只包含当前环对应多边形的MultiPolygon实例 126 | * 127 | * @returns 包含当前环的新MultiPolygon实例 128 | * 129 | * @example 130 | * ```typescript 131 | * const ring = new Ring([[0, 0], [10, 0], [10, 10], [0, 10]]); 132 | * const multipolygon = ring.to_multipolygon(); 133 | * ``` 134 | */ 135 | to_multipolygon(): MultiPolygon { 136 | return new MultiPolygon([this.to_polygon()]); 137 | } 138 | } 139 | 140 | /** 141 | * 表示一个地理多边形区域 142 | * 143 | * 一个多边形由至少一个外环和零个或多个内环(孔洞)组成。外环定义多边形的边界, 144 | * 内环定义多边形内部的孔洞。外环需要顺时针方向,内环需要逆时针方向(按约定)。 145 | * 第一个环通常是外环,后续环是内环。 146 | */ 147 | export class Polygon { 148 | /** 149 | * 组成多边形的环数组 150 | * 151 | * 数组的第一个元素是外环(定义多边形边界),后续元素是内环(定义多边形内部的孔洞)。 152 | * 内环必须在几何上完全位于外环内部,且内环之间不能相交。 153 | */ 154 | rings: Ring[]; 155 | /** 156 | * 创建一个多边形实例 157 | * 158 | * @param rings 组成多边形的环数组。至少需要一个外环,可以有零个或多个内环。 159 | * 160 | * @example 161 | * ```typescript 162 | * // 创建一个包含外环和内环的多边形(带孔洞的矩形) 163 | * const outerRing = new Ring([[0, 0], [100, 0], [100, 100], [0, 100]]); 164 | * const innerRing = new Ring([[20, 20], [80, 20], [80, 80], [20, 80]]); 165 | * const polygon = new Polygon([outerRing, innerRing]); 166 | * 167 | * // 创建一个简单多边形(无孔洞) 168 | * const simplePolygon = new Polygon([new Ring([[0, 0], [50, 0], [50, 50], [0, 50]])]); 169 | * ``` 170 | */ 171 | constructor(rings: Ring[]) { 172 | this.rings = rings; 173 | } 174 | 175 | /** 176 | * 向多边形添加一个环 177 | * 178 | * 通常用于添加内环(孔洞),但也可以添加外环(如果多边形是空的)。 179 | * 注意:调用此方法后,需要确保多边形的几何有效性(如环的方向、环之间的关系)。 180 | * 181 | * @param ring 要添加的环 182 | * 183 | * @example 184 | * ```typescript 185 | * const polygon = new Polygon([outerRing]); 186 | * polygon.add_ring(innerRing); // 添加一个孔洞 187 | * ``` 188 | */ 189 | add_ring(ring: Ring): void { 190 | this.rings.push(ring); 191 | } 192 | 193 | /** 194 | * 获取多边形的坐标数组 195 | * 196 | * 返回一个三维数组,表示多边形的几何结构: 197 | * - 第一维:环的数组 198 | * - 第二维:每个环的点数组 199 | * - 第三维:每个点的[x, y]坐标 200 | * 201 | * @returns 多边形的坐标数组,格式为 `Point[][]` 202 | * 203 | * @example 204 | * ```typescript 205 | * const polygon = new Polygon([new Ring([[0, 0], [10, 0], [10, 10], [0, 10]])]); 206 | * const coords = polygon.coordinates; 207 | * // 结果: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]] 208 | * ``` 209 | * 210 | * @readonly 211 | */ 212 | get coordinates(): Point[][] { 213 | return this.rings.map((ring) => ring.points); 214 | } 215 | 216 | /** 217 | * 对多边形的所有点进行坐标转换 218 | * 219 | * @param transformFn 坐标转换函数,接收一个点并返回转换后的点 220 | * @returns 返回新的 Polygon 实例 221 | */ 222 | transform( 223 | transformFn: (point: Point) => Point, 224 | ): Polygon { 225 | const transformedRings = this.rings.map((ring) => 226 | ring.transform(transformFn) 227 | ); 228 | return new Polygon(transformedRings); 229 | } 230 | } 231 | 232 | /** 233 | * 表示一个 多多边形 几何集合 234 | * 235 | * 多多边形是多个独立多边形的集合,用于表示包含多个不连续区域或包含多个岛屿的地理要素。 236 | * 每个多边形可以有自己的外环和内环(孔洞),多边形之间在几何上相互独立。 237 | * 通常用于表示地理信息系统中的复杂多边形要素。 238 | */ 239 | export class MultiPolygon { 240 | /** 241 | * 组成多多边形的多边形数组 242 | * 243 | * 每个多边形都是独立的几何要素,可以包含一个外环和零个或多个内环。 244 | * 多边形之间不应在几何上重叠(但可以相邻),这通常是数据一致性的要求。 245 | */ 246 | polygons: Polygon[]; 247 | /** 248 | * 创建一个多多边形实例 249 | * 250 | * @param polygons 组成多多边形的多边形数组。可以为空数组,表示一个空的多多边形。 251 | * 252 | * @example 253 | * ```typescript 254 | * // 创建一个包含两个分离矩形的多多边形 255 | * const poly1 = new Polygon([new Ring([[0, 0], [10, 0], [10, 10], [0, 10]])]); 256 | * const poly2 = new Polygon([new Ring([[20, 20], [30, 20], [30, 30], [20, 30]])]); 257 | * const multiPolygon = new MultiPolygon([poly1, poly2]); 258 | * 259 | * // 创建一个带孔洞的多边形组成的多多边形 260 | * const outerRing = new Ring([[0, 0], [100, 0], [100, 100], [0, 100]]); 261 | * const innerRing = new Ring([[20, 20], [80, 20], [80, 80], [20, 80]]); 262 | * const holePolygon = new Polygon([outerRing, innerRing]); 263 | * const multiWithHoles = new MultiPolygon([holePolygon]); 264 | * ``` 265 | */ 266 | constructor(polygons: Polygon[]) { 267 | this.polygons = polygons; 268 | } 269 | 270 | /** 271 | * 向多多边形添加一个多边形 272 | * 273 | * 用于动态构建多多边形集合,添加的多边形在几何上应与其他多边形独立。 274 | * 注意:添加多边形时不会检查多边形之间是否重叠,这是使用者的责任。 275 | * 276 | * @param polygon 要添加的多边形 277 | * 278 | * @example 279 | * ```typescript 280 | * const multiPolygon = new MultiPolygon([]); 281 | * const polygon = new Polygon([new Ring([[0, 0], [5, 0], [5, 5], [0, 5]])]); 282 | * multiPolygon.add_polygon(polygon); 283 | * ``` 284 | */ 285 | add_polygon(polygon: Polygon): void { 286 | this.polygons.push(polygon); 287 | } 288 | 289 | /** 290 | * 获取多多边形的坐标数组 291 | * 292 | * 返回一个四维数组,表示多多边形的完整几何结构: 293 | * - 第一维:多边形数组 294 | * - 第二维:每个多边形的环数组 295 | * - 第三维:每个环的点数组 296 | * - 第四维:每个点的[x, y]坐标 297 | * 298 | * 这个结构符合GeoJSON等标准格式中MultiPolygon的坐标表示。 299 | * 300 | * @returns 多多边形的坐标数组,格式为 `Point[][][]` 301 | * 302 | * @example 303 | * ```typescript 304 | * const poly1 = new Polygon([new Ring([[0, 0], [1, 0], [1, 1], [0, 1]])]); 305 | * const poly2 = new Polygon([new Ring([[2, 2], [3, 2], [3, 3], [2, 3]])]); 306 | * const multiPolygon = new MultiPolygon([poly1, poly2]); 307 | * 308 | * const coords = multiPolygon.coordinates; 309 | * // 结果: [ 310 | * // [ // 第一个多边形 311 | * // [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]] 312 | * // ], 313 | * // [ // 第二个多边形 314 | * // [[2, 2], [3, 2], [3, 3], [2, 3], [2, 2]] 315 | * // ] 316 | * // ] 317 | * ``` 318 | * 319 | * @readonly 320 | */ 321 | get coordinates(): Point[][][] { 322 | return this.polygons.map((polygon) => polygon.coordinates); 323 | } 324 | /** 325 | * 对多多边形的所有点进行坐标转换 326 | * 327 | * @param transformFn 坐标转换函数,接收一个点并返回转换后的点 328 | * @returns 返回新的 MultiPolygon 实例 329 | */ 330 | transform( 331 | transformFn: (point: Point) => Point, 332 | ): MultiPolygon { 333 | const transformedPolygons = this.polygons.map((polygon) => 334 | polygon.transform(transformFn) 335 | ); 336 | return new MultiPolygon(transformedPolygons); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /packages/libs/tests/dbf/dbf_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "jsr:@std/assert"; 2 | import { DBF, Field } from "../../src/dbf/mod.ts"; 3 | 4 | // 测试DBF类的基本构造函数和属性 5 | Deno.test("DBF构造函数 - 基本功能", function () { 6 | const fields = [ 7 | new Field("NAME", "C", 10), 8 | new Field("AGE", "N", 3), 9 | new Field("ACTIVE", "L"), 10 | ]; 11 | 12 | const dbf = new DBF(fields); 13 | 14 | assertEquals(dbf.fields.length, 3); 15 | assertEquals(dbf.fields[0].name, "NAME"); 16 | assertEquals(dbf.fields[1].name, "AGE"); 17 | assertEquals(dbf.fields[2].name, "ACTIVE"); 18 | assertEquals(dbf.records, undefined); 19 | }); 20 | 21 | Deno.test("DBF构造函数 - 带记录", function () { 22 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 23 | 24 | const records = [ 25 | ["张三", 25], 26 | ["李四", 30], 27 | ]; 28 | 29 | const dbf = new DBF(fields, records); 30 | 31 | assertEquals(dbf.fields.length, 2); 32 | assertEquals(dbf.records?.length, 2); 33 | assertEquals(dbf.records?.[0], ["张三", 25]); 34 | assertEquals(dbf.records?.[1], ["李四", 30]); 35 | }); 36 | 37 | // 测试DBF类的header属性生成 38 | Deno.test("DBF header - 基本结构", function () { 39 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 40 | 41 | const dbf = new DBF(fields); 42 | const header = dbf.header; 43 | 44 | // 检查header长度:32字节文件头 + 2个字段*32字节 + 1字节终止符 45 | assertEquals(header.length, 32 + 2 * 32 + 1); 46 | 47 | // 检查版本号(第0字节) 48 | assertEquals(header[0], 0x03); 49 | 50 | // 检查记录数量(第4-7字节,应该是0) 51 | const recordCount = new Uint32Array(header.slice(4, 8).buffer)[0]; 52 | assertEquals(recordCount, 0); 53 | 54 | // 检查文件头长度(第8-9字节) 55 | const headerLength = new Uint16Array(header.slice(8, 10).buffer)[0]; 56 | assertEquals(headerLength, 32 + 2 * 32 + 1); 57 | 58 | // 检查记录长度(第10-11字节) 59 | const recordLength = new Uint16Array(header.slice(10, 12).buffer)[0]; 60 | assertEquals(recordLength, 1 + 10 + 3); // 删除标志 + NAME字段 + AGE字段 61 | 62 | // 检查终止符(最后1字节) 63 | assertEquals(header[header.length - 1], 0x0d); 64 | }); 65 | 66 | Deno.test("DBF header - 带记录的header", function () { 67 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 68 | 69 | const records = [ 70 | ["张三", 25], 71 | ["李四", 30], 72 | ]; 73 | 74 | const dbf = new DBF(fields, records); 75 | const header = dbf.header; 76 | 77 | // 检查记录数量(第4-7字节,应该是2) 78 | const recordCount = new Uint32Array(header.slice(4, 8).buffer)[0]; 79 | assertEquals(recordCount, 2); 80 | }); 81 | 82 | Deno.test("DBF header - 不同字段类型的记录长度计算", function () { 83 | const fields = [ 84 | new Field("NAME", "C", 20), // 20字节 85 | new Field("AGE", "N", 5, 2), // 6字节(5+1小数点) 86 | new Field("ACTIVE", "L"), // 1字节 87 | new Field("BIRTH", "D"), // 8字节 88 | ]; 89 | 90 | const dbf = new DBF(fields); 91 | const header = dbf.header; 92 | 93 | // 检查记录长度(第10-11字节) 94 | const recordLength = new Uint16Array(header.slice(10, 12).buffer)[0]; 95 | assertEquals(recordLength, 1 + 20 + 6 + 1 + 8); // 删除标志 + 各字段长度 96 | }); 97 | 98 | // 测试DBF类的data属性生成(无记录) 99 | Deno.test("DBF data - 无记录情况", function () { 100 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 101 | 102 | const dbf = new DBF(fields); 103 | const data = dbf.data; 104 | 105 | // 数据应该包含header + EOF 106 | assertEquals(data.length, dbf.header.length + 1); 107 | 108 | // 检查EOF标志(最后1字节) 109 | assertEquals(data[data.length - 1], 0x1a); 110 | 111 | // 检查header部分是否相同 112 | const headerPart = data.slice(0, dbf.header.length); 113 | assertEquals(headerPart, dbf.header); 114 | }); 115 | 116 | // 测试DBF类的data属性生成(有记录) 117 | Deno.test("DBF data - 有记录情况", function () { 118 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 119 | 120 | const records = [ 121 | ["张三", 25], 122 | ["李四", 30], 123 | ]; 124 | 125 | const dbf = new DBF(fields, records); 126 | const data = dbf.data; 127 | 128 | // 数据应该包含header + 2条记录 + EOF 129 | const expectedLength = dbf.header.length + 2 * (1 + 10 + 3) + 1; // header + 2条记录 + EOF 130 | assertEquals(data.length, expectedLength); 131 | 132 | // 检查EOF标志(最后1字节) 133 | assertEquals(data[data.length - 1], 0x1a); 134 | 135 | // 检查header部分 136 | const headerPart = data.slice(0, dbf.header.length); 137 | assertEquals(headerPart, dbf.header); 138 | 139 | // 检查记录数量 140 | const recordCount = new Uint32Array(data.slice(4, 8).buffer)[0]; 141 | assertEquals(recordCount, 2); 142 | }); 143 | 144 | Deno.test("DBF data - 复杂记录类型", function () { 145 | const fields = [ 146 | new Field("NAME", "C", 10), 147 | new Field("AGE", "N", 3), 148 | new Field("ACTIVE", "L"), 149 | new Field("BIRTH", "D"), 150 | ]; 151 | 152 | const testDate = new Date("1990-01-01"); 153 | const records = [ 154 | ["张三", 25, true, testDate], 155 | ["李四", 30, false, testDate], 156 | ]; 157 | 158 | const dbf = new DBF(fields, records); 159 | const data = dbf.data; 160 | 161 | // 检查数据长度 162 | const recordLength = 1 + 10 + 3 + 1 + 8; // 删除标志 + 各字段长度 163 | const expectedLength = dbf.header.length + 2 * recordLength + 1; 164 | assertEquals(data.length, expectedLength); 165 | 166 | // 检查记录数量 167 | const recordCount = new Uint32Array(data.slice(4, 8).buffer)[0]; 168 | assertEquals(recordCount, 2); 169 | }); 170 | 171 | // 测试边界情况和错误处理 172 | Deno.test("DBF - 空字段数组", function () { 173 | const dbf = new DBF([]); 174 | 175 | // 应该能正常创建 176 | assertEquals(dbf.fields.length, 0); 177 | assertEquals(dbf.records, undefined); 178 | 179 | // header应该只包含基本的32字节 + 终止符 180 | const header = dbf.header; 181 | assertEquals(header.length, 32 + 1); 182 | 183 | // 记录长度应该是1(只有删除标志) 184 | const recordLength = new Uint16Array(header.slice(10, 12).buffer)[0]; 185 | assertEquals(recordLength, 1); 186 | }); 187 | 188 | Deno.test("DBF - 大量字段", function () { 189 | const fields = []; 190 | for (let i = 0; i < 100; i++) { 191 | fields.push(new Field(`FIELD${i}`, "C", 10)); 192 | } 193 | 194 | const dbf = new DBF(fields); 195 | 196 | assertEquals(dbf.fields.length, 100); 197 | 198 | // 检查header长度 199 | const header = dbf.header; 200 | assertEquals(header.length, 32 + 100 * 32 + 1); 201 | 202 | // 检查记录长度 203 | const recordLength = new Uint16Array(header.slice(10, 12).buffer)[0]; 204 | assertEquals(recordLength, 1 + 100 * 10); // 删除标志 + 100个字段 205 | }); 206 | 207 | Deno.test("DBF - 记录与字段数量不匹配", function () { 208 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 209 | 210 | // 记录数量与字段数量不匹配 211 | const records = [ 212 | ["张三"], // 只有1个值,但字段有2个 213 | ]; 214 | 215 | const dbf = new DBF(fields, records); 216 | 217 | // 创建DBF对象应该成功,但在生成data时会出错 218 | assertThrows( 219 | () => { 220 | dbf.data; 221 | }, 222 | Error, 223 | "记录所包含的值数量与字段定义不同", 224 | ); 225 | }); 226 | 227 | Deno.test("DBF - 记录类型与字段类型不匹配", function () { 228 | const fields = [new Field("NAME", "C", 10), new Field("AGE", "N", 3)]; 229 | 230 | // 记录类型与字段类型不匹配 231 | const records = [ 232 | [123, "不是数字"], // NAME应该是字符串,AGE应该是数字 233 | ]; 234 | 235 | const dbf = new DBF(fields, records); 236 | 237 | // 创建DBF对象应该成功,但在生成data时会出错 238 | assertThrows( 239 | () => { 240 | dbf.data; 241 | }, 242 | Error, 243 | "记录类型与字段定义不符合", 244 | ); 245 | }); 246 | 247 | Deno.test("DBF - 日期字段处理", function () { 248 | const fields = [new Field("NAME", "C", 10), new Field("BIRTH", "D")]; 249 | 250 | const testDate = new Date("1990-05-15"); 251 | const records = [["张三", testDate]]; 252 | 253 | const dbf = new DBF(fields, records); 254 | const data = dbf.data; 255 | 256 | // 应该能正常生成数据 257 | assertEquals(data.length > 0, true); 258 | 259 | // 检查记录数量 260 | const recordCount = new Uint32Array(data.slice(4, 8).buffer)[0]; 261 | assertEquals(recordCount, 1); 262 | }); 263 | 264 | Deno.test("DBF - 布尔字段处理", function () { 265 | const fields = [new Field("NAME", "C", 10), new Field("ACTIVE", "L")]; 266 | 267 | const records = [ 268 | ["张三", true], 269 | ["李四", false], 270 | ]; 271 | 272 | const dbf = new DBF(fields, records); 273 | const data = dbf.data; 274 | 275 | // 应该能正常生成数据 276 | assertEquals(data.length > 0, true); 277 | 278 | // 检查记录数量 279 | const recordCount = new Uint32Array(data.slice(4, 8).buffer)[0]; 280 | assertEquals(recordCount, 2); 281 | }); 282 | 283 | Deno.test("DBF - 数值字段精度处理", function () { 284 | const fields = [ 285 | new Field("PRICE", "N", 8, 2), // 8位长度,2位小数 286 | ]; 287 | 288 | const records = [[123.45], [999.99]]; 289 | 290 | const dbf = new DBF(fields, records); 291 | const data = dbf.data; 292 | 293 | // 应该能正常生成数据 294 | assertEquals(data.length > 0, true); 295 | 296 | // 检查记录数量 297 | const recordCount = new Uint32Array(data.slice(4, 8).buffer)[0]; 298 | assertEquals(recordCount, 2); 299 | }); 300 | 301 | Deno.test("DBF - 与Arcmap生成的DBF文件对比", async function () { 302 | const dxz = new Field("短整型", "N", 5); 303 | const cxz = new Field("长整型", "N", 10); 304 | const fdx = new Field("浮点型", "N", 16, 2); 305 | const sjd = new Field("双精度", "N", 16, 2); 306 | const wb = new Field("文本", "C", 50); 307 | const rq = new Field("日期", "D"); 308 | 309 | const record_1 = [1, 1, 1.23, 4.56, "text1", new Date("2025-9-6")]; 310 | const record_2 = [ 311 | 99999, 312 | 999999999, 313 | 999.99, 314 | 9999.99, 315 | "text2", 316 | new Date("2025-9-7"), 317 | ]; 318 | 319 | const dbf = new DBF([dxz, cxz, fdx, sjd, wb, rq], [record_1, record_2]); 320 | dbf.set_create_date(new Date("2025-9-6")); 321 | const arcmap = await Deno.readFile("./tests/dbf/data/arcmap_gen.dbf"); 322 | assertEquals(dbf.data, arcmap); 323 | }); 324 | 325 | Deno.test("DBF - 空值处理", function () { 326 | const dxz = new Field("短整型", "N", 5); 327 | const cxz = new Field("长整型", "N", 10); 328 | const fdx = new Field("浮点型", "N", 16, 2); 329 | const sjd = new Field("双精度", "N", 16, 2); 330 | const wb = new Field("文本", "C", 50); 331 | const rq = new Field("日期", "D"); 332 | 333 | const record_1 = [1, null, 1.23, null, null, null]; 334 | const dbf = new DBF([dxz, cxz, fdx, sjd, wb, rq], [record_1]); 335 | dbf.set_create_date(new Date("2025-9-6")); 336 | Deno.writeFileSync("./tests/dbf/data/null_value.dbf", dbf.data); 337 | }); 338 | 339 | Deno.test("DBF - 测试边界文件生成", function () { 340 | const DKMC = new Field("DKMC", "C", 254); 341 | const DKDM = new Field("DKDM", "C", 100); 342 | const XZQDM = new Field("XZQDM", "C", 12); 343 | const XZQMC = new Field("XZQMC", "C", 100); 344 | const YDMJ = new Field("YDMJ", "N", 16, 2); 345 | const DH = new Field("DH", "N", 16); 346 | const SCRQ = new Field("SCRQ", "D"); 347 | const SCDW = new Field("SCDW", "C", 254); 348 | const BZ = new Field("BZ", "C", 254); 349 | const fields = [DKMC, DKDM, XZQDM, XZQMC, YDMJ, DH, SCRQ, SCDW, BZ]; 350 | 351 | const record_1 = ["1", "2", "3", "4", 1092.39, 39, null, null, null]; 352 | const record_2 = ["1", "2", "3", null, null, null, new Date(), null, null]; 353 | const dbf = new DBF(fields, [record_1, record_2]); 354 | Deno.writeFileSync("./tests/dbf/data/边界文件.dbf", dbf.data); 355 | }); 356 | -------------------------------------------------------------------------------- /crates/wasm_webp_to_png/LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.4.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bumpalo" 25 | version = "3.18.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" 28 | 29 | [[package]] 30 | name = "bytemuck" 31 | version = "1.23.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" 34 | 35 | [[package]] 36 | name = "byteorder-lite" 37 | version = "0.1.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.2.27" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" 46 | dependencies = [ 47 | "shlex", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "1.0.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 55 | 56 | [[package]] 57 | name = "console_error_panic_hook" 58 | version = "0.1.7" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 61 | dependencies = [ 62 | "cfg-if", 63 | "wasm-bindgen", 64 | ] 65 | 66 | [[package]] 67 | name = "crc32fast" 68 | version = "1.4.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 71 | dependencies = [ 72 | "cfg-if", 73 | ] 74 | 75 | [[package]] 76 | name = "fdeflate" 77 | version = "0.3.7" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 80 | dependencies = [ 81 | "simd-adler32", 82 | ] 83 | 84 | [[package]] 85 | name = "file-format" 86 | version = "0.27.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "0ff8badf6e72ff15e42c9ade15d01375837173b17d10c228ab41d821082619db" 89 | 90 | [[package]] 91 | name = "flate2" 92 | version = "1.1.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" 95 | dependencies = [ 96 | "crc32fast", 97 | "miniz_oxide", 98 | ] 99 | 100 | [[package]] 101 | name = "google_earth" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "file-format", 105 | ] 106 | 107 | [[package]] 108 | name = "image" 109 | version = "0.25.6" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" 112 | dependencies = [ 113 | "bytemuck", 114 | "byteorder-lite", 115 | "image-webp", 116 | "num-traits", 117 | "png", 118 | ] 119 | 120 | [[package]] 121 | name = "image-webp" 122 | version = "0.2.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "14d75c7014ddab93c232bc6bb9f64790d3dfd1d605199acd4b40b6d69e691e9f" 125 | dependencies = [ 126 | "byteorder-lite", 127 | "quick-error", 128 | ] 129 | 130 | [[package]] 131 | name = "js-sys" 132 | version = "0.3.77" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 135 | dependencies = [ 136 | "once_cell", 137 | "wasm-bindgen", 138 | ] 139 | 140 | [[package]] 141 | name = "log" 142 | version = "0.4.27" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 145 | 146 | [[package]] 147 | name = "minicov" 148 | version = "0.3.7" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" 151 | dependencies = [ 152 | "cc", 153 | "walkdir", 154 | ] 155 | 156 | [[package]] 157 | name = "miniz_oxide" 158 | version = "0.8.9" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 161 | dependencies = [ 162 | "adler2", 163 | "simd-adler32", 164 | ] 165 | 166 | [[package]] 167 | name = "num-traits" 168 | version = "0.2.19" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 171 | dependencies = [ 172 | "autocfg", 173 | ] 174 | 175 | [[package]] 176 | name = "once_cell" 177 | version = "1.21.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 180 | 181 | [[package]] 182 | name = "png" 183 | version = "0.17.16" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 186 | dependencies = [ 187 | "bitflags", 188 | "crc32fast", 189 | "fdeflate", 190 | "flate2", 191 | "miniz_oxide", 192 | ] 193 | 194 | [[package]] 195 | name = "proc-macro2" 196 | version = "1.0.95" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 199 | dependencies = [ 200 | "unicode-ident", 201 | ] 202 | 203 | [[package]] 204 | name = "quick-error" 205 | version = "2.0.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 208 | 209 | [[package]] 210 | name = "quote" 211 | version = "1.0.40" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 214 | dependencies = [ 215 | "proc-macro2", 216 | ] 217 | 218 | [[package]] 219 | name = "rustversion" 220 | version = "1.0.21" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 223 | 224 | [[package]] 225 | name = "same-file" 226 | version = "1.0.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 229 | dependencies = [ 230 | "winapi-util", 231 | ] 232 | 233 | [[package]] 234 | name = "shlex" 235 | version = "1.3.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 238 | 239 | [[package]] 240 | name = "simd-adler32" 241 | version = "0.3.7" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 244 | 245 | [[package]] 246 | name = "syn" 247 | version = "2.0.103" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" 250 | dependencies = [ 251 | "proc-macro2", 252 | "quote", 253 | "unicode-ident", 254 | ] 255 | 256 | [[package]] 257 | name = "unicode-ident" 258 | version = "1.0.18" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 261 | 262 | [[package]] 263 | name = "walkdir" 264 | version = "2.5.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 267 | dependencies = [ 268 | "same-file", 269 | "winapi-util", 270 | ] 271 | 272 | [[package]] 273 | name = "wasm-bindgen" 274 | version = "0.2.100" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 277 | dependencies = [ 278 | "cfg-if", 279 | "once_cell", 280 | "rustversion", 281 | "wasm-bindgen-macro", 282 | ] 283 | 284 | [[package]] 285 | name = "wasm-bindgen-backend" 286 | version = "0.2.100" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 289 | dependencies = [ 290 | "bumpalo", 291 | "log", 292 | "proc-macro2", 293 | "quote", 294 | "syn", 295 | "wasm-bindgen-shared", 296 | ] 297 | 298 | [[package]] 299 | name = "wasm-bindgen-futures" 300 | version = "0.4.50" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 303 | dependencies = [ 304 | "cfg-if", 305 | "js-sys", 306 | "once_cell", 307 | "wasm-bindgen", 308 | "web-sys", 309 | ] 310 | 311 | [[package]] 312 | name = "wasm-bindgen-macro" 313 | version = "0.2.100" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 316 | dependencies = [ 317 | "quote", 318 | "wasm-bindgen-macro-support", 319 | ] 320 | 321 | [[package]] 322 | name = "wasm-bindgen-macro-support" 323 | version = "0.2.100" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 326 | dependencies = [ 327 | "proc-macro2", 328 | "quote", 329 | "syn", 330 | "wasm-bindgen-backend", 331 | "wasm-bindgen-shared", 332 | ] 333 | 334 | [[package]] 335 | name = "wasm-bindgen-shared" 336 | version = "0.2.100" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 339 | dependencies = [ 340 | "unicode-ident", 341 | ] 342 | 343 | [[package]] 344 | name = "wasm-bindgen-test" 345 | version = "0.3.50" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" 348 | dependencies = [ 349 | "js-sys", 350 | "minicov", 351 | "wasm-bindgen", 352 | "wasm-bindgen-futures", 353 | "wasm-bindgen-test-macro", 354 | ] 355 | 356 | [[package]] 357 | name = "wasm-bindgen-test-macro" 358 | version = "0.3.50" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "syn", 365 | ] 366 | 367 | [[package]] 368 | name = "wasm_webp_to_png" 369 | version = "0.1.0" 370 | dependencies = [ 371 | "console_error_panic_hook", 372 | "wasm-bindgen", 373 | "wasm-bindgen-test", 374 | "webp_to_png", 375 | ] 376 | 377 | [[package]] 378 | name = "web-sys" 379 | version = "0.3.77" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 382 | dependencies = [ 383 | "js-sys", 384 | "wasm-bindgen", 385 | ] 386 | 387 | [[package]] 388 | name = "webp_to_png" 389 | version = "0.1.0" 390 | dependencies = [ 391 | "image", 392 | ] 393 | 394 | [[package]] 395 | name = "winapi-util" 396 | version = "0.1.9" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 399 | dependencies = [ 400 | "windows-sys", 401 | ] 402 | 403 | [[package]] 404 | name = "windows-sys" 405 | version = "0.59.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 408 | dependencies = [ 409 | "windows-targets", 410 | ] 411 | 412 | [[package]] 413 | name = "windows-targets" 414 | version = "0.52.6" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 417 | dependencies = [ 418 | "windows_aarch64_gnullvm", 419 | "windows_aarch64_msvc", 420 | "windows_i686_gnu", 421 | "windows_i686_gnullvm", 422 | "windows_i686_msvc", 423 | "windows_x86_64_gnu", 424 | "windows_x86_64_gnullvm", 425 | "windows_x86_64_msvc", 426 | ] 427 | 428 | [[package]] 429 | name = "windows_aarch64_gnullvm" 430 | version = "0.52.6" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 433 | 434 | [[package]] 435 | name = "windows_aarch64_msvc" 436 | version = "0.52.6" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 439 | 440 | [[package]] 441 | name = "windows_i686_gnu" 442 | version = "0.52.6" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 445 | 446 | [[package]] 447 | name = "windows_i686_gnullvm" 448 | version = "0.52.6" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 451 | 452 | [[package]] 453 | name = "windows_i686_msvc" 454 | version = "0.52.6" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 457 | 458 | [[package]] 459 | name = "windows_x86_64_gnu" 460 | version = "0.52.6" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 463 | 464 | [[package]] 465 | name = "windows_x86_64_gnullvm" 466 | version = "0.52.6" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 469 | 470 | [[package]] 471 | name = "windows_x86_64_msvc" 472 | version = "0.52.6" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 475 | -------------------------------------------------------------------------------- /packages/webp_to_png/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@libs/bundle@*": "12.6.5", 5 | "jsr:@libs/logger@^3.1.4": "3.1.4", 6 | "jsr:@libs/run@^3.0.2": "3.0.3", 7 | "jsr:@luca/esbuild-deno-loader@~0.11.0-rc.1": "0.11.1", 8 | "jsr:@std/assert@1": "1.0.13", 9 | "jsr:@std/async@1": "1.0.13", 10 | "jsr:@std/bytes@^1.0.2": "1.0.6", 11 | "jsr:@std/bytes@^1.0.6": "1.0.6", 12 | "jsr:@std/encoding@1": "1.0.10", 13 | "jsr:@std/encoding@^1.0.5": "1.0.10", 14 | "jsr:@std/fs@1": "1.0.18", 15 | "jsr:@std/internal@^1.0.6": "1.0.8", 16 | "jsr:@std/path@*": "1.1.0", 17 | "jsr:@std/path@1": "1.1.0", 18 | "jsr:@std/path@^1.0.6": "1.1.0", 19 | "jsr:@std/path@^1.1.0": "1.1.0", 20 | "jsr:@std/streams@1": "1.0.10", 21 | "jsr:@std/streams@^1.0.9": "1.0.10", 22 | "jsr:@std/tar@~0.1.4": "0.1.6", 23 | "npm:esbuild@~0.25.5": "0.25.5", 24 | "npm:sharp@*": "0.33.5", 25 | "npm:terser@^5.36.0": "5.42.0" 26 | }, 27 | "jsr": { 28 | "@libs/bundle@12.6.5": { 29 | "integrity": "5bc151959bbc4ec9e21c871c67eb52459b85c5445a91c820fe157d3235c33144", 30 | "dependencies": [ 31 | "jsr:@libs/logger", 32 | "jsr:@libs/run", 33 | "jsr:@luca/esbuild-deno-loader", 34 | "jsr:@std/assert", 35 | "jsr:@std/async", 36 | "jsr:@std/encoding@1", 37 | "jsr:@std/fs", 38 | "jsr:@std/path@1", 39 | "jsr:@std/tar", 40 | "npm:esbuild", 41 | "npm:terser" 42 | ] 43 | }, 44 | "@libs/logger@3.1.4": { 45 | "integrity": "595880dc7d31ddc9ebe5c395665a02862bc4920737c5284918849d03b9a77a1f" 46 | }, 47 | "@libs/run@3.0.3": { 48 | "integrity": "332b991b2b404fbc03e8aa97ffebe949771d56f26dd94486231fcbfaf5aaf112", 49 | "dependencies": [ 50 | "jsr:@libs/logger", 51 | "jsr:@std/async", 52 | "jsr:@std/streams@1" 53 | ] 54 | }, 55 | "@luca/esbuild-deno-loader@0.11.1": { 56 | "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", 57 | "dependencies": [ 58 | "jsr:@std/bytes@^1.0.2", 59 | "jsr:@std/encoding@^1.0.5", 60 | "jsr:@std/path@^1.0.6" 61 | ] 62 | }, 63 | "@std/assert@1.0.13": { 64 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 65 | "dependencies": [ 66 | "jsr:@std/internal" 67 | ] 68 | }, 69 | "@std/async@1.0.13": { 70 | "integrity": "1d76ca5d324aef249908f7f7fe0d39aaf53198e5420604a59ab5c035adc97c96" 71 | }, 72 | "@std/bytes@1.0.6": { 73 | "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" 74 | }, 75 | "@std/encoding@1.0.10": { 76 | "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 77 | }, 78 | "@std/fs@1.0.18": { 79 | "integrity": "24bcad99eab1af4fde75e05da6e9ed0e0dce5edb71b7e34baacf86ffe3969f3a", 80 | "dependencies": [ 81 | "jsr:@std/path@^1.1.0" 82 | ] 83 | }, 84 | "@std/internal@1.0.8": { 85 | "integrity": "fc66e846d8d38a47cffd274d80d2ca3f0de71040f855783724bb6b87f60891f5" 86 | }, 87 | "@std/path@1.1.0": { 88 | "integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886" 89 | }, 90 | "@std/streams@1.0.10": { 91 | "integrity": "75c0b1431873cd0d8b3d679015220204d36d3c7420d93b60acfc379eb0dc30af", 92 | "dependencies": [ 93 | "jsr:@std/bytes@^1.0.6" 94 | ] 95 | }, 96 | "@std/tar@0.1.6": { 97 | "integrity": "7f06a5359518262207e1c28ea965fd552dff3ba2047d2e9225339af287b1f3a4", 98 | "dependencies": [ 99 | "jsr:@std/streams@^1.0.9" 100 | ] 101 | } 102 | }, 103 | "npm": { 104 | "@emnapi/runtime@1.3.1": { 105 | "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", 106 | "dependencies": [ 107 | "tslib" 108 | ] 109 | }, 110 | "@esbuild/aix-ppc64@0.25.5": { 111 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==" 112 | }, 113 | "@esbuild/android-arm64@0.25.5": { 114 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==" 115 | }, 116 | "@esbuild/android-arm@0.25.5": { 117 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==" 118 | }, 119 | "@esbuild/android-x64@0.25.5": { 120 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==" 121 | }, 122 | "@esbuild/darwin-arm64@0.25.5": { 123 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==" 124 | }, 125 | "@esbuild/darwin-x64@0.25.5": { 126 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==" 127 | }, 128 | "@esbuild/freebsd-arm64@0.25.5": { 129 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==" 130 | }, 131 | "@esbuild/freebsd-x64@0.25.5": { 132 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==" 133 | }, 134 | "@esbuild/linux-arm64@0.25.5": { 135 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==" 136 | }, 137 | "@esbuild/linux-arm@0.25.5": { 138 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==" 139 | }, 140 | "@esbuild/linux-ia32@0.25.5": { 141 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==" 142 | }, 143 | "@esbuild/linux-loong64@0.25.5": { 144 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==" 145 | }, 146 | "@esbuild/linux-mips64el@0.25.5": { 147 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==" 148 | }, 149 | "@esbuild/linux-ppc64@0.25.5": { 150 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==" 151 | }, 152 | "@esbuild/linux-riscv64@0.25.5": { 153 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==" 154 | }, 155 | "@esbuild/linux-s390x@0.25.5": { 156 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==" 157 | }, 158 | "@esbuild/linux-x64@0.25.5": { 159 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==" 160 | }, 161 | "@esbuild/netbsd-arm64@0.25.5": { 162 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==" 163 | }, 164 | "@esbuild/netbsd-x64@0.25.5": { 165 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==" 166 | }, 167 | "@esbuild/openbsd-arm64@0.25.5": { 168 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==" 169 | }, 170 | "@esbuild/openbsd-x64@0.25.5": { 171 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==" 172 | }, 173 | "@esbuild/sunos-x64@0.25.5": { 174 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==" 175 | }, 176 | "@esbuild/win32-arm64@0.25.5": { 177 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==" 178 | }, 179 | "@esbuild/win32-ia32@0.25.5": { 180 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==" 181 | }, 182 | "@esbuild/win32-x64@0.25.5": { 183 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==" 184 | }, 185 | "@img/sharp-darwin-arm64@0.33.5": { 186 | "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", 187 | "dependencies": [ 188 | "@img/sharp-libvips-darwin-arm64" 189 | ] 190 | }, 191 | "@img/sharp-darwin-x64@0.33.5": { 192 | "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", 193 | "dependencies": [ 194 | "@img/sharp-libvips-darwin-x64" 195 | ] 196 | }, 197 | "@img/sharp-libvips-darwin-arm64@1.0.4": { 198 | "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==" 199 | }, 200 | "@img/sharp-libvips-darwin-x64@1.0.4": { 201 | "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==" 202 | }, 203 | "@img/sharp-libvips-linux-arm64@1.0.4": { 204 | "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==" 205 | }, 206 | "@img/sharp-libvips-linux-arm@1.0.5": { 207 | "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==" 208 | }, 209 | "@img/sharp-libvips-linux-s390x@1.0.4": { 210 | "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==" 211 | }, 212 | "@img/sharp-libvips-linux-x64@1.0.4": { 213 | "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==" 214 | }, 215 | "@img/sharp-libvips-linuxmusl-arm64@1.0.4": { 216 | "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==" 217 | }, 218 | "@img/sharp-libvips-linuxmusl-x64@1.0.4": { 219 | "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==" 220 | }, 221 | "@img/sharp-linux-arm64@0.33.5": { 222 | "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", 223 | "dependencies": [ 224 | "@img/sharp-libvips-linux-arm64" 225 | ] 226 | }, 227 | "@img/sharp-linux-arm@0.33.5": { 228 | "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", 229 | "dependencies": [ 230 | "@img/sharp-libvips-linux-arm" 231 | ] 232 | }, 233 | "@img/sharp-linux-s390x@0.33.5": { 234 | "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", 235 | "dependencies": [ 236 | "@img/sharp-libvips-linux-s390x" 237 | ] 238 | }, 239 | "@img/sharp-linux-x64@0.33.5": { 240 | "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", 241 | "dependencies": [ 242 | "@img/sharp-libvips-linux-x64" 243 | ] 244 | }, 245 | "@img/sharp-linuxmusl-arm64@0.33.5": { 246 | "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", 247 | "dependencies": [ 248 | "@img/sharp-libvips-linuxmusl-arm64" 249 | ] 250 | }, 251 | "@img/sharp-linuxmusl-x64@0.33.5": { 252 | "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", 253 | "dependencies": [ 254 | "@img/sharp-libvips-linuxmusl-x64" 255 | ] 256 | }, 257 | "@img/sharp-wasm32@0.33.5": { 258 | "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", 259 | "dependencies": [ 260 | "@emnapi/runtime" 261 | ] 262 | }, 263 | "@img/sharp-win32-ia32@0.33.5": { 264 | "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==" 265 | }, 266 | "@img/sharp-win32-x64@0.33.5": { 267 | "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==" 268 | }, 269 | "@jridgewell/gen-mapping@0.3.8": { 270 | "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", 271 | "dependencies": [ 272 | "@jridgewell/set-array", 273 | "@jridgewell/sourcemap-codec", 274 | "@jridgewell/trace-mapping" 275 | ] 276 | }, 277 | "@jridgewell/resolve-uri@3.1.2": { 278 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" 279 | }, 280 | "@jridgewell/set-array@1.2.1": { 281 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" 282 | }, 283 | "@jridgewell/source-map@0.3.6": { 284 | "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 285 | "dependencies": [ 286 | "@jridgewell/gen-mapping", 287 | "@jridgewell/trace-mapping" 288 | ] 289 | }, 290 | "@jridgewell/sourcemap-codec@1.5.0": { 291 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 292 | }, 293 | "@jridgewell/trace-mapping@0.3.25": { 294 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 295 | "dependencies": [ 296 | "@jridgewell/resolve-uri", 297 | "@jridgewell/sourcemap-codec" 298 | ] 299 | }, 300 | "acorn@8.15.0": { 301 | "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" 302 | }, 303 | "buffer-from@1.1.2": { 304 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 305 | }, 306 | "color-convert@2.0.1": { 307 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 308 | "dependencies": [ 309 | "color-name" 310 | ] 311 | }, 312 | "color-name@1.1.4": { 313 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 314 | }, 315 | "color-string@1.9.1": { 316 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 317 | "dependencies": [ 318 | "color-name", 319 | "simple-swizzle" 320 | ] 321 | }, 322 | "color@4.2.3": { 323 | "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 324 | "dependencies": [ 325 | "color-convert", 326 | "color-string" 327 | ] 328 | }, 329 | "commander@2.20.3": { 330 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 331 | }, 332 | "detect-libc@2.0.4": { 333 | "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" 334 | }, 335 | "esbuild@0.25.5": { 336 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 337 | "dependencies": [ 338 | "@esbuild/aix-ppc64", 339 | "@esbuild/android-arm", 340 | "@esbuild/android-arm64", 341 | "@esbuild/android-x64", 342 | "@esbuild/darwin-arm64", 343 | "@esbuild/darwin-x64", 344 | "@esbuild/freebsd-arm64", 345 | "@esbuild/freebsd-x64", 346 | "@esbuild/linux-arm", 347 | "@esbuild/linux-arm64", 348 | "@esbuild/linux-ia32", 349 | "@esbuild/linux-loong64", 350 | "@esbuild/linux-mips64el", 351 | "@esbuild/linux-ppc64", 352 | "@esbuild/linux-riscv64", 353 | "@esbuild/linux-s390x", 354 | "@esbuild/linux-x64", 355 | "@esbuild/netbsd-arm64", 356 | "@esbuild/netbsd-x64", 357 | "@esbuild/openbsd-arm64", 358 | "@esbuild/openbsd-x64", 359 | "@esbuild/sunos-x64", 360 | "@esbuild/win32-arm64", 361 | "@esbuild/win32-ia32", 362 | "@esbuild/win32-x64" 363 | ] 364 | }, 365 | "is-arrayish@0.3.2": { 366 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 367 | }, 368 | "semver@7.7.1": { 369 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" 370 | }, 371 | "sharp@0.33.5": { 372 | "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", 373 | "dependencies": [ 374 | "@img/sharp-darwin-arm64", 375 | "@img/sharp-darwin-x64", 376 | "@img/sharp-libvips-darwin-arm64", 377 | "@img/sharp-libvips-darwin-x64", 378 | "@img/sharp-libvips-linux-arm", 379 | "@img/sharp-libvips-linux-arm64", 380 | "@img/sharp-libvips-linux-s390x", 381 | "@img/sharp-libvips-linux-x64", 382 | "@img/sharp-libvips-linuxmusl-arm64", 383 | "@img/sharp-libvips-linuxmusl-x64", 384 | "@img/sharp-linux-arm", 385 | "@img/sharp-linux-arm64", 386 | "@img/sharp-linux-s390x", 387 | "@img/sharp-linux-x64", 388 | "@img/sharp-linuxmusl-arm64", 389 | "@img/sharp-linuxmusl-x64", 390 | "@img/sharp-wasm32", 391 | "@img/sharp-win32-ia32", 392 | "@img/sharp-win32-x64", 393 | "color", 394 | "detect-libc", 395 | "semver" 396 | ] 397 | }, 398 | "simple-swizzle@0.2.2": { 399 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 400 | "dependencies": [ 401 | "is-arrayish" 402 | ] 403 | }, 404 | "source-map-support@0.5.21": { 405 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 406 | "dependencies": [ 407 | "buffer-from", 408 | "source-map" 409 | ] 410 | }, 411 | "source-map@0.6.1": { 412 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 413 | }, 414 | "terser@5.42.0": { 415 | "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", 416 | "dependencies": [ 417 | "@jridgewell/source-map", 418 | "acorn", 419 | "commander", 420 | "source-map-support" 421 | ] 422 | }, 423 | "tslib@2.8.1": { 424 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" 425 | } 426 | }, 427 | "remote": { 428 | "https://deno.land/std@0.177.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", 429 | "https://deno.land/std@0.177.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", 430 | "https://deno.land/std@0.177.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", 431 | "https://deno.land/std@0.177.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab" 432 | }, 433 | "workspace": { 434 | "dependencies": [ 435 | "jsr:@std/assert@1" 436 | ] 437 | } 438 | } 439 | --------------------------------------------------------------------------------