Skill: nestone/supermap-wasi-geo (version 1.1.0)

--- name: supermap-wasi-geo description: 使用SuperMap WebAssembly 进行几何分析，支持缓冲区、凸包、相交、合并、擦除、裁剪等 20+ 种几何操作 version: 1.0.0 author: NestOne tags: - gis - geometry - buffer - convex-hull - intersection - union - spatial-analysis - wasm - geojson --- # SuperMap WASI Geometry Skill 基于 SuperMap WebAssembly 的高性能几何分析工具。支持 20+ 种几何分析操作，包括缓冲区分析、拓扑运算、距离计算等。 ## 安装 ```bash # 进入项目目录 cd C:\supermap-wasi-geo # 安装为全局命令（可选） npm link ``` 安装后可以直接使用 `wasi-geo` 命令，否则需要使用 `node bin/supermap-wasi-geo.js`。 ## 支持的操作 ### 单几何操作 | 操作 | 说明 | 参数 | |------|------|------| | `buffer` | 缓冲区分析 | `--input`, `--radius` | | `convex-hull` | 凸包计算 | `--input` | | `resample` | 重采样分析 | `--input`, `--tolerance` | | `smooth` | 线要素光滑 | `--input`, `--smoothness` | | `geodesic-distance` | 测地线距离 | `--input`, `[--major-axis]`, `[--flatten]` | | `geodesic-area` | 测地线面积 | `--input`, `[--major-axis]`, `[--flatten]` | | `parallel` | 计算平行线 | `--input`, `--distance` | ### 双几何操作 | 操作 | 说明 | 参数 | |------|------|------| | `intersect` | 相交分析 | `--input1`, `--input2` | | `union` | 合并分析 | `--input1`, `--input2` | | `erase` | 擦除分析 | `--input1`, `--input2` | | `xor` | 异或分析 | `--input1`, `--input2` | | `clip` | 裁剪分析 | `--input1`, `--input2` | | `has-intersection` | 判断是否相交 | `--input1`, `--input2`, `[--tolerance]` | | `has-touch` | 判断边界是否接触 | `--input1`, `--input2`, `[--tolerance]` | | `is-identical` | 判断是否相等 | `--input1`, `--input2`, `[--tolerance]` | | `distance` | 计算两几何距离 | `--input1`, `--input2` | ### 点线关系分析 | 操作 | 说明 | 参数 | |------|------|------| | `is-left` | 点是否在线左侧 | `--point`, `--line-start`, `--line-end` | | `is-right` | 点是否在线右侧 | `--point`, `--line-start`, `--line-end` | | `is-point-on-line` | 点是否在线段上 | `--point`, `--line-start`, `--line-end`, `[--extended]` | | `is-parallel` | 两线是否平行 | `--line1-start`, `--line1-end`, `--line2-start`, `--line2-end` | | `distance-to-line` | 点到线段距离 | `--point`, `--line-start`, `--line-end` | ## 使用方式 ### Windows 命令行 (CMD) ```cmd # 基本用法：操作名 + 参数 node bin\supermap-wasi-geo.js buffer --input "{\"type\":\"Point\",\"coordinates\":[120,30]}" --radius 2 # 显示帮助 node bin\supermap-wasi-geo.js --help # 显示特定操作帮助 node bin\supermap-wasi-geo.js buffer --help ``` ### Windows PowerShell ```powershell # 基本用法（使用单引号包裹） node bin/supermap-wasi-geo.js buffer --input '{"type":"Point","coordinates":[120,30]}' --radius 2 # 管道输入 echo '{"type":"Point","coordinates":[120,30]}' | node bin/supermap-wasi-geo.js buffer --radius 2 ``` ### 通用参数 | 参数 | 说明 | |------|------| | `--input <json>` | GeoJSON 字符串输入 | | `--input-file <path>` | 从文件读取 GeoJSON | | `--output <path>` | 输出到文件（默认 stdout） | | `--pretty` | 美化 JSON 输出 | | `--help, -h` | 显示帮助信息 | | `--version, -v` | 显示版本信息 | ## 示例 ### 1. 缓冲区分析 (Windows CMD) ```cmd rem 点缓冲区 node bin\supermap-wasi-geo.js buffer --input "{\"type\":\"Point\",\"coordinates\":[120,30]}" --radius 2 --pretty rem 线缓冲区 node bin\supermap-wasi-geo.js buffer --input "{\"type\":\"LineString\",\"coordinates\":[[0,0],[10,0]]}" --radius 0.5 --pretty rem 多边形缓冲区 node bin\supermap-wasi-geo.js buffer --input "{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]}" --radius 0.1 --pretty ``` ### 2. 距离计算 (Windows CMD) ```cmd rem 两点距离 node bin\supermap-wasi-geo.js distance --input1 "{\"type\":\"Point\",\"coordinates\":[0,0]}" --input2 "{\"type\":\"Point\",\"coordinates\":[3,4]}" rem 输出: {"type":"Result","distance":5} ``` ### 3. 相交判断 (Windows CMD) ```cmd node bin\supermap-wasi-geo.js has-intersection --input1 "{\"type\":\"Point\",\"coordinates\":[0,0]}" --input2 "{\"type\":\"Point\",\"coordinates\":[1,1]}" rem 输出: {"type":"Result","hasIntersection":false} ``` ### 4. 点线关系 (Windows CMD) ```cmd rem 判断点是否在线左侧 node bin\supermap-wasi-geo.js is-left --point "[0,1]" --line-start "[0,0]" --line-end "[1,0]" rem 判断点是否在线段上 node bin\supermap-wasi-geo.js is-point-on-line --point "[0.5,0]" --line-start "[0,0]" --line-end "[1,0]" rem 计算点到线段距离 node bin\supermap-wasi-geo.js distance-to-line --point "[0,1]" --line-start "[0,0]" --line-end "[1,0]" ``` ### 5. 测地线计算 (Windows CMD) ```cmd rem 测地线距离（单位：米） node bin\supermap-wasi-geo.js geodesic-distance --input "{\"type\":\"LineString\",\"coordinates\":[[0,0],[1,1]]}" rem 测地线面积（单位：平方米） node bin\supermap-wasi-geo.js geodesic-area --input "{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]}" ``` ### 6. 文件输入输出 ```cmd rem 从文件读取，输出到文件 node bin\supermap-wasi-geo.js buffer --input-file input.geojson --radius 2 --output buffer.geojson rem 双几何操作 node bin\supermap-wasi-geo.js intersect --input1-file polygon1.geojson --input2-file polygon2.geojson --output intersect.geojson ``` ### 7. PowerShell 示例 ```powershell # 缓冲区分析 node bin/supermap-wasi-geo.js buffer --input '{"type":"Point","coordinates":[120,30]}' --radius 2 --pretty # 距离计算 node bin/supermap-wasi-geo.js distance --input1 '{"type":"Point","coordinates":[0,0]}' --input2 '{"type":"Point","coordinates":[3,4]}' # 管道操作 echo '{"type":"Point","coordinates":[0,0]}' | node bin/supermap-wasi-geo.js buffer --radius 1 ``` ## 支持的几何类型 - **Point** - 点 - **LineString** - 线 - **Polygon** - 多边形 - **MultiPoint** - 多点 - **MultiLineString** - 多线 - **MultiPolygon** - 多多边形 ## 快速测试 ```cmd cd e:\wasi_test rem 显示帮助 node bin\supermap-wasi-geo.js --help rem 测试缓冲区 node bin\supermap-wasi-geo.js buffer --input "{\"type\":\"Point\",\"coordinates\":[120,30]}" --radius 2 --pretty rem 测试距离 node bin\supermap-wasi-geo.js distance --input1 "{\"type\":\"Point\",\"coordinates\":[0,0]}" --input2 "{\"type\":\"Point\",\"coordinates\":[3,4]}" rem 测试点线关系 node bin\supermap-wasi-geo.js is-left --point "[0,1]" --line-start "[0,0]" --line-end "[1,0]" ``` ## Node.js 模块调用 也可以作为 Node.js 模块使用： ```javascript const GeometryAnalysis = require('./src/geometry-analysis.js'); async function main() { // 缓冲区分析 const buffer = await GeometryAnalysis.buffer( { type: 'Point', coordinates: [120, 30] }, 2 ); console.log('Buffer:', buffer); // 距离计算 const dist = await GeometryAnalysis.distance( { type: 'Point', coordinates: [0, 0] }, { type: 'Point', coordinates: [3, 4] } ); console.log('Distance:', dist); // 点线关系 const isLeft = await GeometryAnalysis.isLeft([0, 1], [0, 0], [1, 0]); console.log('Is Left:', isLeft); } main(); ``` ## 注意事项 1. **坐标单位**： - 缓冲区半径的单位与输入坐标系一致 - 经纬度坐标：单位为度 - 投影坐标：单位为米或其他投影单位 2. **性能**：首次调用需要初始化 WASM 模块，约需 1-2 秒 3. **Windows 引号问题**： - CMD: 使用双引号 `"`，内部双引号用 `\"` 转义 - PowerShell: 使用单引号 `'` 包裹，内部使用双引号 4. **路径分隔符**： - CMD: 使用反斜杠 `\` 或正斜杠 `/` - PowerShell: 推荐使用正斜杠 `/` ## 错误处理 CLI 在出错时会返回非零退出码： ```cmd rem 测试错误情况 node bin\supermap-wasi-geo.js buffer --input "invalid" --radius 2 rem 检查退出码 (CMD) echo %ERRORLEVEL% ``` ## API 参考 ### 单几何操作 ```javascript // 缓冲区分析 await GeometryAnalysis.buffer(input, radius); // 凸包计算 await GeometryAnalysis.computeConvexHull(input); // 重采样 await GeometryAnalysis.resample(input, tolerance); // 光滑 await GeometryAnalysis.smooth(input, smoothness); // 测地线距离 await GeometryAnalysis.computeGeodesicDistance(input, majorAxis?, flatten?); // 测地线面积 await GeometryAnalysis.computeGeodesicArea(input, majorAxis?, flatten?); // 平行线 await GeometryAnalysis.computeParallel(input, distance); ``` ### 双几何操作 ```javascript // 相交 await GeometryAnalysis.intersect(input1, input2); // 合并 await GeometryAnalysis.union(input1, input2); // 擦除 await GeometryAnalysis.erase(input1, input2); // 异或 await GeometryAnalysis.xor(input1, input2); // 裁剪 await GeometryAnalysis.clip(input1, input2); // 判断相交 await GeometryAnalysis.hasIntersection(input1, input2, tolerance?); // 判断接触 await GeometryAnalysis.hasTouch(input1, input2, tolerance?); // 判断相等 await GeometryAnalysis.isIdentical(input1, input2, tolerance?); // 计算距离 await GeometryAnalysis.distance(input1, input2); ``` ### 点线关系 ```javascript // 点是否在线左侧 await GeometryAnalysis.isLeft(point, lineStart, lineEnd); // 点是否在线右侧 await GeometryAnalysis.isRight(point, lineStart, lineEnd); // 点是否在线段上 await GeometryAnalysis.isPointOnLine(point, lineStart, lineEnd, extended?); // 两线是否平行 await GeometryAnalysis.isParallel(line1Start, line1End, line2Start, line2End); // 点到线段距离 await GeometryAnalysis.distanceToLineSegment(point, lineStart, lineEnd); ```

path: package.json
content: {
  "name": "supermap-wasi-geo",
  "version": "1.0.0",
  "description": "SuperMap WASM Geometry Analysis CLI - 使用SuperMap WebAssembly 进行几何分析，支持缓冲区、凸包、相交、合并等 20+ 种操作，openClaw Skill 包",
  "bin": {
    "wasi-geo": "./scripts/bin/supermap-wasi-geo.js"
  },
  "main": "./scripts/src/geometry-analysis.js",
  "type": "commonjs",
  "scripts": {
    "test": "node scripts/bin/supermap-wasi-geo.js --help",
    "geo": "node scripts/bin/supermap-wasi-geo.js"
  },
  "keywords": [
    "wasm",
    "buffer",
    "geojson",
    "gis",
    "geometry",
    "spatial-analysis",
    "convex-hull",
    "intersection",
    "union",
    "openclaw",
    "skill"
  ],
  "author": "NestOne",
  "license": "MIT",
  "engines": {
    "node": ">=14.0.0"
  },
  "os": [
    "win32",
    "linux",
    "darwin"
  ]
}


path: scripts/src/geojson.js
content: /**
 * GeoJSON 解析和序列化模块
 * 处理 GeoJSON 与 WASM 内存之间的数据转换
 */

/**
 * 几何类型枚举
 */
const GeometryType = {
  POINT: 1,
  LINESTRING: 2,
  POLYGON: 3,
  MULTIPOINT: 4,
  MULTILINESTRING: 5,
  MULTIPOLYGON: 6
};

/**
 * 从 WASM 内存读取点坐标
 * @param {Object} Module - WASM 模块实例
 * @param {number} ptr - 内存指针
 * @param {number} offset - 偏移量
 * @returns {[number, number]} [x, y] 坐标
 */
function readPoint(Module, ptr, offset = 0) {
  const x = Module.HEAPF64[ptr / 8 + offset * 2];
  const y = Module.HEAPF64[ptr / 8 + offset * 2 + 1];
  return [x, y];
}

/**
 * 从 WASM 内存读取坐标数组
 * @param {Object} Module - WASM 模块实例
 * @param {number} ptr - 内存指针
 * @param {number} count - 点数量
 * @returns {Array<Array<number>>} 坐标数组
 */
function readPoints(Module, ptr, count) {
  const points = [];
  for (let i = 0; i < count; i++) {
    points.push(readPoint(Module, ptr, i));
  }
  return points;
}

/**
 * 将坐标写入 WASM 内存
 * @param {Object} Module - WASM 模块实例
 * @param {number} ptr - 内存指针
 * @param {Array<Array<number>>} points - 坐标数组
 */
function writePoints(Module, ptr, points) {
  for (let i = 0; i < points.length; i++) {
    const [x, y] = points[i];
    Module.HEAPF64[ptr / 8 + i * 2] = x;
    Module.HEAPF64[ptr / 8 + i * 2 + 1] = y;
  }
}

/**
 * 从 GeoRegion 对象提取多边形坐标
 * @param {Object} Module - WASM 模块实例
 * @param {number} regionPtr - GeoRegion 指针
 * @returns {Array<Array<Array<number>>>} 多边形坐标（支持带洞）
 */
function extractPolygonFromRegion(Module, regionPtr) {
  if (!regionPtr) return null;

  const partCount = Module._UGCWasm_GeoRegion_GetPartCount(regionPtr);
  if (partCount === 0) return null;

  const coordinates = [];

  for (let part = 0; part < partCount; part++) {
    const pointCount = Module._UGCWasm_GeoRegion_GetPartPointCount(regionPtr, part);
    if (pointCount === 0) continue;

    // 使用 GetSubPoints 获取点坐标数组的指针
    const pointsPtr = Module._UGCWasm_GeoRegion_GetSubPoints(regionPtr);
    
    if (!pointsPtr) {
      console.error('GetSubPoints returned null');
      continue;
    }

    // 从内存读取点坐标
    // 注意：GetSubPoints 返回的是所有部分的点，需要根据 part 索引计算偏移
    // 简化处理：假设第一部分从索引 0 开始
    const ring = [];
    for (let i = 0; i < pointCount; i++) {
      const x = Module.HEAPF64[pointsPtr / 8 + i * 2];
      const y = Module.HEAPF64[pointsPtr / 8 + i * 2 + 1];
      ring.push([x, y]);
    }

    // 确保首尾点相同（闭合多边形）
    if (ring.length > 0) {
      const first = ring[0];
      const last = ring[ring.length - 1];
      if (first[0] !== last[0] || first[1] !== last[1]) {
        ring.push([...first]);
      }
    }

    coordinates.push(ring);
  }

  return coordinates.length > 0 ? coordinates : null;
}

/**
 * 从 GeoLine 对象提取线坐标
 * @param {Object} Module - WASM 模块实例
 * @param {number} linePtr - GeoLine 指针
 * @returns {Array<Array<Array<number>>>} 多线坐标
 */
function extractLineFromGeoLine(Module, linePtr) {
  if (!linePtr) return null;

  const partCount = Module._UGCWasm_GeoLine_GetPartCount(linePtr);
  if (partCount === 0) return null;

  const coordinates = [];

  for (let part = 0; part < partCount; part++) {
    const pointCount = Module._UGCWasm_GeoLine_GetPartPointCount(linePtr, part);
    if (pointCount === 0) continue;

    const bufferSize = pointCount * 2 * 8;
    const bufferPtr = Module._malloc(bufferSize);

    try {
      const actualCount = Module._UGCWasm_GeoLine_GetPart2(linePtr, part, bufferPtr, pointCount);
      const line = readPoints(Module, bufferPtr, actualCount);
      coordinates.push(line);
    } finally {
      Module._free(bufferPtr);
    }
  }

  return coordinates.length > 0 ? coordinates : null;
}

/**
 * 从 WASM 几何对象创建 GeoJSON Geometry
 * @param {Object} Module - WASM 模块实例
 * @param {number} geomPtr - 几何对象指针
 * @param {number} geomType - 几何类型
 * @returns {Object} GeoJSON Geometry 对象
 */
function createGeoJSONGeometry(Module, geomPtr, geomType) {
  if (!geomPtr) return null;

  switch (geomType) {
    case GeometryType.POINT: {
      const x = Module._UGCWasm_GeoPoint_GetX(geomPtr);
      const y = Module._UGCWasm_GeoPoint_GetY(geomPtr);
      return {
        type: 'Point',
        coordinates: [x, y]
      };
    }

    case GeometryType.LINESTRING: {
      const coordinates = extractLineFromGeoLine(Module, geomPtr);
      if (!coordinates || coordinates.length === 0) return null;
      
      // 单线
      if (coordinates.length === 1) {
        return {
          type: 'LineString',
          coordinates: coordinates[0]
        };
      }
      // 多线
      return {
        type: 'MultiLineString',
        coordinates: coordinates
      };
    }

    case GeometryType.POLYGON: {
      const coordinates = extractPolygonFromRegion(Module, geomPtr);
      if (!coordinates || coordinates.length === 0) return null;
      
      // 单多边形
      if (coordinates.length === 1) {
        return {
          type: 'Polygon',
          coordinates: coordinates[0]
        };
      }
      // 多多边形或带洞多边形
      // 判断是否为带洞多边形
      const isHole = coordinates.length > 1 && isCounterClockwise(Module, geomPtr, 1);
      if (isHole || coordinates.length === 1) {
        return {
          type: 'Polygon',
          coordinates: coordinates
        };
      }
      return {
        type: 'MultiPolygon',
        coordinates: coordinates.map(c => [c])
      };
    }

    default:
      console.error(`不支持的几何类型: ${geomType}`);
      return null;
  }
}

/**
 * 判断多边形环是否为逆时针方向（洞）
 */
function isCounterClockwise(Module, regionPtr, partIndex) {
  try {
    return Module._UGCWasm_GeoRegion_IsCounterClockwise(regionPtr, partIndex);
  } catch {
    return false;
  }
}

/**
 * 解析输入的 GeoJSON 并提取几何信息
 * @param {Object} input - GeoJSON Feature 或 Geometry
 * @returns {Object} { type, coordinates, properties }
 */
function parseGeoJSON(input) {
  // 处理 Feature
  if (input.type === 'Feature' && input.geometry) {
    return {
      type: input.geometry.type,
      coordinates: input.geometry.coordinates,
      properties: input.properties || {}
    };
  }

  // 处理 Geometry
  if (input.geometry) {
    return {
      type: input.geometry.type,
      coordinates: input.geometry.coordinates,
      properties: {}
    };
  }

  // 直接是 Geometry
  if (input.type && input.coordinates) {
    return {
      type: input.type,
      coordinates: input.coordinates,
      properties: {}
    };
  }

  throw new Error('无效的 GeoJSON 输入');
}

/**
 * 创建 GeoJSON Feature 输出
 * @param {Object} geometry - GeoJSON Geometry
 * @param {Object} properties - 属性
 * @returns {Object} GeoJSON Feature
 */
function createFeature(geometry, properties = {}) {
  return {
    type: 'Feature',
    geometry: geometry,
    properties: properties
  };
}

/**
 * 创建 GeoJSON FeatureCollection 输出
 * @param {Array<Object>} features - Feature 数组
 * @returns {Object} GeoJSON FeatureCollection
 */
function createFeatureCollection(features) {
  return {
    type: 'FeatureCollection',
    features: features
  };
}

/**
 * 将坐标数组转换为 WASM Point2Ds 对象
 * 参照 src1/util.js 的 geojsonCoordsToPoint2Ds
 * @param {Object} Module - WASM 模块实例
 * @param {Array} coords - 坐标数组 [[x,y], ...]
 * @returns {number} Point2Ds 指针
 */
function geojsonCoordsToPoint2Ds(Module, coords) {
  if (!coords || coords.length === 0) return null;
  const length = coords.length;
  const dim = 2;
  const coordArr = new Float64Array(coords.flat());
  const coordPtr = Module._malloc(length * dim * 8);
  Module.HEAPF64.set(coordArr, coordPtr / 8);
  const seqPtr = Module._UGCWasm_Geometry_CreatePoint2DsFromBuffer(coordPtr, length);
  Module._free(coordPtr);
  return seqPtr;
}

/**
 * 将 JS 数组转换为 WASM DoubleArray
 * 参照 src1/util.js 的 geojsonCoords2UGDoubleArray
 * @param {Object} Module - WASM 模块实例
 * @param {Array} coords - 数值数组
 * @returns {number} DoubleArray 指针
 */
function geojsonCoords2UGDoubleArray(Module, coords) {
  if (!coords || coords.length === 0) return null;
  const length = coords.length;
  const coordArr = new Float64Array(coords);
  const coordPtr = Module._malloc(length * 8);
  Module.HEAPF64.set(coordArr, coordPtr / 8);
  const pDoubleArray = Module._UGCWasm_Helper_CreateDoubleArray(coordPtr, length);
  Module._free(coordPtr);
  return pDoubleArray;
}

/**
 * 从 WASM DoubleArray 获取 JS 数组
 * 参照 src1/util.js 的 getJSArrayFromUGDoubleArray
 * @param {Object} Module - WASM 模块实例
 * @param {number} pDoubleArray - DoubleArray 指针
 * @returns {Array} JS 数组
 */
function getJSArrayFromUGDoubleArray(Module, pDoubleArray) {
  const length = Module._UGCWasm_Helper_GetDoubleArrayLength(pDoubleArray);
  const pBuffer = Module._malloc(length * 8);
  Module._UGCWasm_Helper_GetBufferFromDoubleArray(pDoubleArray, pBuffer);
  const view = new Float64Array(Module.HEAPF64.buffer, pBuffer, length);
  const coords = [];
  for (let i = 0; i < length; i++) {
    coords.push(view[i]);
  }
  Module._free(pBuffer);
  return coords;
}

/**
 * 将 GeoJSON 对象转换为 WASM 几何对象指针
 * 参照 src1/util.js 的 geojson2UGGeometry
 * @param {Object} Module - WASM 模块实例
 * @param {Object} geojson - GeoJSON 对象
 * @returns {number} WASM 几何对象指针
 */
function geojson2UGGeometry(Module, geojson) {
  if (!geojson) throw new Error('No GeoJSON object provided');

  switch (geojson.type) {
    case 'Feature':
      return geojson2UGGeometry(Module, geojson.geometry);
    case 'FeatureCollection':
      if (geojson.features.length === 0) return null;
      return geojson2UGGeometry(Module, geojson.features[0].geometry);
    case 'Point':
      if (geojson.coordinates.length === 0) {
        return Module._UGCWasm_GeoPoint_New();
      }
      return Module._UGCWasm_GeoPoint_New2(geojson.coordinates[0], geojson.coordinates[1]);
    case 'LineString': {
      if (geojson.coordinates.length === 0) {
        return Module._UGCWasm_GeoLine_New();
      }
      const pGeoLine = Module._UGCWasm_GeoLine_New();
      const pPoint2Ds = geojsonCoordsToPoint2Ds(Module, geojson.coordinates);
      Module._UGCWasm_GeoLine_AddPart2(pGeoLine, pPoint2Ds, geojson.coordinates.length);
      return pGeoLine;
    }
    case 'Polygon': {
      if (geojson.coordinates.length === 0) {
        return Module._UGCWasm_GeoRegion_New();
      }
      const pGeoRegion = Module._UGCWasm_GeoRegion_New();
      for (let i = 0; i < geojson.coordinates.length; i++) {
        const pPt = geojsonCoordsToPoint2Ds(Module, geojson.coordinates[i]);
        Module._UGCWasm_GeoRegion_AddPart2(pGeoRegion, pPt, geojson.coordinates[i].length);
      }
      return pGeoRegion;
    }
    case 'MultiLineString': {
      if (geojson.coordinates.length === 0) {
        return Module._UGCWasm_GeoLine_New();
      }
      const pGeoLine = Module._UGCWasm_GeoLine_New();
      for (let i = 0; i < geojson.coordinates.length; i++) {
        const pPt = geojsonCoordsToPoint2Ds(Module, geojson.coordinates[i]);
        Module._UGCWasm_GeoLine_AddPart2(pGeoLine, pPt, geojson.coordinates[i].length);
      }
      return pGeoLine;
    }
    case 'MultiPolygon': {
      if (geojson.coordinates.length === 0) {
        return Module._UGCWasm_GeoRegion_New();
      }
      const pGeoRegion = Module._UGCWasm_GeoRegion_New();
      for (let i = 0; i < geojson.coordinates.length; i++) {
        for (let j = 0; j < geojson.coordinates[i].length; j++) {
          const pPt = geojsonCoordsToPoint2Ds(Module, geojson.coordinates[i][j]);
          Module._UGCWasm_GeoRegion_AddPart2(pGeoRegion, pPt, geojson.coordinates[i][j].length);
        }
      }
      return pGeoRegion;
    }
    case 'MultiPoint': {
      const pGeoLine = Module._UGCWasm_GeoLine_New();
      const pPoint2Ds = geojsonCoordsToPoint2Ds(Module, geojson.coordinates);
      Module._UGCWasm_GeoLine_AddPart2(pGeoLine, pPoint2Ds, geojson.coordinates.length);
      return pGeoLine;
    }
    default:
      throw new Error('Unsupported GeoJSON type: ' + geojson.type);
  }
}

/**
 * 将 WASM 几何对象指针转换为 GeoJSON
 * 参照 src1/util.js 的 ugGeometry2Geojson
 * @param {Object} Module - WASM 模块实例
 * @param {number} pUGGeo - WASM 几何对象指针
 * @returns {Object} GeoJSON Geometry 对象
 */
function ugGeometry2Geojson(Module, pUGGeo) {
  if (!pUGGeo) return null;

  const geomType = Module._UGCWasm_Geometry_GetType(pUGGeo);
  switch (geomType) {
    case 1: { // UGGeoPoint
      const x = Module._UGCWasm_GeoPoint_GetX(pUGGeo);
      const y = Module._UGCWasm_GeoPoint_GetY(pUGGeo);
      return { type: 'Point', coordinates: [x, y] };
    }
    case 3: { // UGGeoLine
      const outlines = [];
      const partCount = Module._UGCWasm_GeoLine_GetPartCount(pUGGeo);
      for (let j = 0; j < partCount; j++) {
        const count = Module._UGCWasm_GeoLine_GetPartPointCount(pUGGeo, j);
        const pBuffer = Module._malloc(count * 2 * 8);
        Module._UGCWasm_GeoLine_GetPart2(pUGGeo, pBuffer, j);
        const view = new Float64Array(Module.HEAPF64.buffer, pBuffer, count * 2);
        const coords = [];
        for (let i = 0; i < count * 2; i += 2) {
          coords.push([view[i], view[i + 1]]);
        }
        Module._free(pBuffer);
        outlines.push(coords);
      }
      return { type: 'LineString', coordinates: outlines[0] };
    }
    case 5: { // UGGeoRegion
      const outlines = [];
      const partCount = Module._UGCWasm_GeoRegion_GetPartCount(pUGGeo);
      for (let j = 0; j < partCount; j++) {
        const count = Module._UGCWasm_GeoRegion_GetPartPointCount(pUGGeo, j);
        const pBuffer = Module._malloc(count * 2 * 8);
        Module._UGCWasm_GeoRegion_GetPart2(pUGGeo, pBuffer, j);
        const view = new Float64Array(Module.HEAPF64.buffer, pBuffer, count * 2);
        const coords = [];
        for (let i = 0; i < count * 2; i += 2) {
          coords.push([view[i], view[i + 1]]);
        }
        Module._free(pBuffer);
        outlines.push(coords);
      }
      return { type: 'Polygon', coordinates: outlines };
    }
    default:
      return null;
  }
}

module.exports = {
  GeometryType,
  readPoint,
  readPoints,
  writePoints,
  extractPolygonFromRegion,
  extractLineFromGeoLine,
  createGeoJSONGeometry,
  parseGeoJSON,
  createFeature,
  createFeatureCollection,
  geojsonCoordsToPoint2Ds,
  geojsonCoords2UGDoubleArray,
  getJSArrayFromUGDoubleArray,
  geojson2UGGeometry,
  ugGeometry2Geojson
};


path: scripts/src/geometry-analysis.js
content: /**
 * 几何分析模块
 * 封装 WASM 的所有几何分析功能
 */

const { initWasm } = require('./wasm-adapter');
const {
  parseGeoJSON,
  geojsonCoordsToPoint2Ds,
  geojsonCoords2UGDoubleArray,
  geojson2UGGeometry,
  ugGeometry2Geojson
} = require('./geojson');

// ==================== 几何分析函数 ====================

/**
 * 缓冲区分析
 */
async function buffer(input, radius) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const Module = await initWasm();
  const ugGeom = geojson2UGGeometry(Module, parsedInput);
  const result = Module._UGCWasm_Geometrist_Buffer(ugGeom, radius);
  const geometry = ugGeometry2Geojson(Module, result);
  return { type: 'Feature', geometry, properties: { bufferRadius: radius } };
}

/**
 * 凸多边形计算
 */
async function computeConvexHull(input) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const { type, coordinates } = parseGeoJSON(parsedInput);
  const Module = await initWasm();

  // 提取扁平的点坐标数组
  let points;
  switch (type) {
    case 'Point':
      points = [coordinates];
      break;
    case 'MultiPoint':
    case 'LineString':
      points = coordinates;
      break;
    case 'MultiLineString':
    case 'Polygon':
      points = coordinates.flat();
      break;
    case 'MultiPolygon':
      points = coordinates.flat(2);
      break;
    default:
      points = coordinates;
  }

  const ugPoints = geojsonCoordsToPoint2Ds(Module, points);
  const result = Module._UGCWasm_Geometrist_ComputeConvexHull(ugPoints, points.length);
  const geometry = ugGeometry2Geojson(Module, result);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 重采样分析
 */
async function resample(input, tolerance) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const Module = await initWasm();
  const ugFeature = geojson2UGGeometry(Module, parsedInput);
  const result = Module._UGCWasm_Geometrist_Resample(ugFeature, tolerance);
  const geometry = ugGeometry2Geojson(Module, result);
  return { type: 'Feature', geometry, properties: { tolerance } };
}

/**
 * 线要素光滑分析
 */
async function smooth(input, smoothness) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const Module = await initWasm();
  const ugFeature = geojson2UGGeometry(Module, parsedInput);
  const result = Module._UGCWasm_Geometrist_Smooth(ugFeature, smoothness);
  const geometry = ugGeometry2Geojson(Module, result);
  return { type: 'Feature', geometry, properties: { smoothness } };
}

/**
 * 计算测地线距离
 */
async function computeGeodesicDistance(input, majorAxis = 6378137, flatten = 0.003352810664) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const { type, coordinates } = parseGeoJSON(parsedInput);
  const Module = await initWasm();

  // 提取点坐标并分离 x/y 数组
  let points;
  switch (type) {
    case 'Point':
      points = [coordinates];
      break;
    case 'MultiPoint':
    case 'LineString':
      points = coordinates;
      break;
    default:
      points = Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0])
        ? coordinates.flat()
        : coordinates;
  }

  const xArray = points.map(p => p[0]);
  const yArray = points.map(p => p[1]);
  const pXArray = geojsonCoords2UGDoubleArray(Module, xArray);
  const pYArray = geojsonCoords2UGDoubleArray(Module, yArray);
  const distance = Module._UGCWasm_Geometrist_ComputeGeodesicDistance(pXArray, pYArray, majorAxis, flatten);
  return { type: 'Result', distance, unit: 'meters', properties: {} };
}

/**
 * 计算测地线面积
 */
async function computeGeodesicArea(input, majorAxis = 6378137, flatten = 0.003352810664) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const Module = await initWasm();
  const ugFeature = geojson2UGGeometry(Module, parsedInput);
  const prjCoordSys = Module._UGCWasm_Geometry_NewUGPrjCoordSys(4326);
  const area = Module._UGCWasm_Geometrist_ComputeGeodesicArea(ugFeature, prjCoordSys);
  return { type: 'Result', area, unit: 'square_meters', properties: {} };
}

/**
 * 几何对象是否相交
 */
async function hasIntersection(input1, input2, tolerance = 0) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const result = Module._UGCWasm_Geometrist_HasIntersection(ugFeature1, ugFeature2, tolerance);
  return { type: 'Result', hasIntersection: result === 1 };
}

/**
 * 几何对象边界是否接触
 */
async function hasTouch(input1, input2, tolerance = 0) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const result = Module._UGCWasm_Geometrist_HasTouch(ugFeature1, ugFeature2, tolerance);
  return { type: 'Result', hasTouch: result === 1 };
}

/**
 * 几何对象相等分析
 */
async function isIdentical(input1, input2, tolerance = 0) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const result = Module._UGCWasm_Geometrist_IsIdentical(ugFeature1, ugFeature2, tolerance);
  return { type: 'Result', isIdentical: result === 1 };
}

/**
 * 相交分析
 */
async function intersect(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const resultPtr = Module._UGCWasm_Geometrist_Intersect(ugFeature1, ugFeature2);
  if (!resultPtr) return null;
  const geometry = ugGeometry2Geojson(Module, resultPtr);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 合并分析
 */
async function union(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const resultPtr = Module._UGCWasm_Geometrist_Union(ugFeature1, ugFeature2);
  if (!resultPtr) return null;
  const geometry = ugGeometry2Geojson(Module, resultPtr);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 擦除分析
 */
async function erase(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const resultPtr = Module._UGCWasm_Geometrist_Erase(ugFeature1, ugFeature2);
  if (!resultPtr) return null;
  const geometry = ugGeometry2Geojson(Module, resultPtr);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 异或分析
 */
async function xor(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const resultPtr = Module._UGCWasm_Geometrist_XOR(ugFeature1, ugFeature2);
  if (!resultPtr) return null;
  const geometry = ugGeometry2Geojson(Module, resultPtr);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 裁剪分析
 */
async function clip(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const resultPtr = Module._UGCWasm_Geometrist_Clip(ugFeature1, ugFeature2);
  if (!resultPtr) return null;
  const geometry = ugGeometry2Geojson(Module, resultPtr);
  return { type: 'Feature', geometry, properties: {} };
}

/**
 * 计算距离
 */
async function distance(input1, input2) {
  const parsed1 = typeof input1 === 'string' ? JSON.parse(input1) : input1;
  const parsed2 = typeof input2 === 'string' ? JSON.parse(input2) : input2;
  const Module = await initWasm();
  const ugFeature1 = geojson2UGGeometry(Module, parsed1);
  const ugFeature2 = geojson2UGGeometry(Module, parsed2);
  const dist = Module._UGCWasm_Geometrist_Distance(ugFeature1, ugFeature2);
  return { type: 'Result', distance: dist };
}

/**
 * 点是否在线的左侧
 */
async function isLeft(point, lineStart, lineEnd) {
  const Module = await initWasm();
  const result = Module._UGCWasm_Geometrist_IsLeft(
    point[0], point[1],
    lineStart[0], lineStart[1],
    lineEnd[0], lineEnd[1]
  );
  return { type: 'Result', isLeft: result === 1 };
}

/**
 * 点是否在线的右侧
 */
async function isRight(point, lineStart, lineEnd) {
  const Module = await initWasm();
  const result = Module._UGCWasm_Geometrist_IsRight(
    point[0], point[1],
    lineStart[0], lineStart[1],
    lineEnd[0], lineEnd[1]
  );
  return { type: 'Result', isRight: result === 1 };
}

/**
 * 点是否在线段上
 */
async function isPointOnLine(point, lineStart, lineEnd, extended = false) {
  const Module = await initWasm();
  const result = Module._UGCWasm_Geometrist_IsPointOnLine(
    point[0], point[1],
    lineStart[0], lineStart[1],
    lineEnd[0], lineEnd[1],
    extended ? 1 : 0
  );
  return { type: 'Result', isPointOnLine: result === 1 };
}

/**
 * 线平行分析
 */
async function isParallel(line1Start, line1End, line2Start, line2End) {
  const Module = await initWasm();
  const result = Module._UGCWasm_Geometrist_IsParallel(
    line1Start[0], line1Start[1], line1End[0], line1End[1],
    line2Start[0], line2Start[1], line2End[0], line2End[1]
  );
  return { type: 'Result', isParallel: result === 1 };
}

/**
 * 计算点到线段的距离
 */
async function distanceToLineSegment(point, lineStart, lineEnd) {
  const Module = await initWasm();
  const dist = Module._UGCWasm_Geometrist_DistanceToLineSegment(
    point[0], point[1],
    lineStart[0], lineStart[1],
    lineEnd[0], lineEnd[1]
  );
  return { type: 'Result', distance: dist };
}

/**
 * 计算平行线
 */
async function computeParallel(input, distance) {
  const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;
  const Module = await initWasm();
  const ugFeature = geojson2UGGeometry(Module, parsedInput);
  const result = Module._UGCWasm_Geometrist_ComputeParallel(ugFeature, distance);
  const geometry = ugGeometry2Geojson(Module, result);
  return { type: 'Feature', geometry, properties: { distance } };
}

// 导出所有函数
module.exports = {
  // 单几何操作
  buffer,
  computeConvexHull,
  resample,
  smooth,
  computeGeodesicDistance,
  computeGeodesicArea,
  computeParallel,
  
  // 双几何操作
  hasIntersection,
  hasTouch,
  isIdentical,
  intersect,
  union,
  erase,
  xor,
  clip,
  distance,
  
  // 点线关系
  isLeft,
  isRight,
  isPointOnLine,
  isParallel,
  distanceToLineSegment
};


path: scripts/src/wasm-adapter.js
content: /**
 * Node.js 环境适配器
 * 模拟浏览器环境所需的 API，使 UGCWasmAll.js 能在 Node.js 中运行
 */

const path = require('path');

// 标记环境是否已初始化
let envInitialized = false;

/**
 * 初始化浏览器环境模拟
 */
function initBrowserEnv() {
  if (envInitialized) return;
  envInitialized = true;

  // 模拟完整的 document 对象
  if (typeof global.document === 'undefined') {
    global.document = {
      currentScript: {
        src: __filename,
        ownerDocument: {}
      },
      createElement: (tag) => {
        if (tag === 'script') {
          return {
            src: '',
            onload: null,
            onerror: null,
            readyState: 'complete'
          };
        }
        return {};
      },
      getElementById: () => null,
      getElementsByTagName: (tag) => {
        if (tag === 'script') return [{ src: __filename }];
        if (tag === 'head') return [{}];
        return [];
      },
      head: {},
      body: {},
      documentElement: {},
      createEvent: () => ({ initEvent: () => {} }),
      dispatchEvent: () => true
    };
  }

  // 模拟 self 对象（Worker 环境）
  if (typeof global.self === 'undefined') {
    global.self = global;
  }

  // 模拟 window 对象
  if (typeof global.window === 'undefined') {
    global.window = global;
  }

  // 模拟 XMLHttpRequest
  if (typeof global.XMLHttpRequest === 'undefined') {
    global.XMLHttpRequest = function() {
      this.open = () => {};
      this.send = () => {
        this.readyState = 4;
        this.status = 200;
        if (this.onload) this.onload({ target: { response: new ArrayBuffer(0) } });
      };
      this.setRequestHeader = () => {};
      this.getAllResponseHeaders = () => '';
      this.getResponseHeader = () => null;
      this.abort = () => {};
    };
  }
}

// 加载 WASM 模块
let wasmInstance = null;
let initPromise = null;

/**
 * 初始化 WASM 模块
 * @returns {Promise<Object>} 初始化后的 Module 对象
 */
async function initWasm() {
  // 如果已经初始化，直接返回
  if (wasmInstance && wasmInstance.HEAP8) {
    return wasmInstance;
  }

  // 如果正在初始化，返回同一个 Promise
  if (initPromise) {
    return initPromise;
  }

  initPromise = (async () => {
    try {
      // 初始化浏览器环境
      initBrowserEnv();

      // 加载模块
      const ugcModule = require('../wasm/UGCWasmAll.js');

      // WASM 模块需要异步初始化
      // 等待 HEAP8 可用（表示 WASM 已加载完成）
      await new Promise((resolve, reject) => {
        let attempts = 0;
        const maxAttempts = 50; // 最多等待 5 秒
        
        const check = () => {
          attempts++;
          if (ugcModule.HEAP8) {
            resolve();
          } else if (attempts >= maxAttempts) {
            reject(new Error('WASM 模块初始化超时'));
          } else {
            setTimeout(check, 100);
          }
        };
        check();
      });

      wasmInstance = ugcModule;

      // 验证模块是否正确加载
      if (!wasmInstance.HEAP8 || typeof wasmInstance._malloc !== 'function') {
        throw new Error('WASM 模块加载失败：缺少必要的导出函数');
      }

      return wasmInstance;
    } catch (error) {
      initPromise = null;
      throw error;
    }
  })();

  return initPromise;
}

/**
 * 获取已初始化的 WASM 模块实例
 * @returns {Object|null} Module 对象或 null
 */
function getWasmInstance() {
  return wasmInstance;
}

/**
 * 检查模块是否已初始化
 * @returns {boolean}
 */
function isInitialized() {
  return wasmInstance !== null;
}

module.exports = {
  initWasm,
  getWasmInstance,
  isInitialized
};


path: scripts/bin/supermap-wasi-geo.js
content: #!/usr/bin/env node

/**
 * SuperMap WASI Geometry CLI
 * 基于 SuperMap WASM 的几何分析命令行工具
 * 支持23种几何分析操作
 */

const fs = require('fs');
const path = require('path');
const GeometryAnalysis = require('../src/geometry-analysis');

// 版本信息
const VERSION = require('../../package.json').version;

// 操作定义
const OPERATIONS = {
  // 单几何操作
  'buffer': {
    description: '缓冲区分析',
    params: ['input', 'radius'],
    optionalParams: [],
    example: 'wasi-geo buffer --input \'{"type":"Point","coordinates":[120,30]}\' --radius 2'
  },
  'convex-hull': {
    description: '凸包计算',
    params: ['input'],
    optionalParams: [],
    example: 'wasi-geo convex-hull --input \'{"type":"MultiPoint","coordinates":[[0,0],[1,1],[1,0],[0,1]]}\''
  },
  'resample': {
    description: '重采样分析',
    params: ['input', 'tolerance'],
    optionalParams: [],
    example: 'wasi-geo resample --input \'{"type":"LineString","coordinates":[[0,0],[1,1],[2,0]]}\' --tolerance 0.5'
  },
  'smooth': {
    description: '线要素光滑分析',
    params: ['input', 'smoothness'],
    optionalParams: [],
    example: 'wasi-geo smooth --input \'{"type":"LineString","coordinates":[[0,0],[1,1],[2,0]]}\' --smoothness 3'
  },
  'geodesic-distance': {
    description: '计算测地线距离',
    params: ['input'],
    optionalParams: ['major-axis', 'flatten'],
    example: 'wasi-geo geodesic-distance --input \'{"type":"LineString","coordinates":[[0,0],[1,1]]}\''
  },
  'geodesic-area': {
    description: '计算测地线面积',
    params: ['input'],
    optionalParams: ['major-axis', 'flatten'],
    example: 'wasi-geo geodesic-area --input \'{"type":"Polygon","coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]}\''
  },
  'parallel': {
    description: '计算平行线',
    params: ['input', 'distance'],
    optionalParams: [],
    example: 'wasi-geo parallel --input \'{"type":"LineString","coordinates":[[0,0],[1,1]]}\' --distance 0.5'
  },

  // 双几何操作
  'intersect': {
    description: '相交分析',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo intersect --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'union': {
    description: '合并分析',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo union --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'erase': {
    description: '擦除分析',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo erase --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'xor': {
    description: '异或分析',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo xor --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'clip': {
    description: '裁剪分析',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo clip --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'has-intersection': {
    description: '判断是否相交',
    params: ['input1', 'input2'],
    optionalParams: ['tolerance'],
    example: 'wasi-geo has-intersection --input1 \'{"type":"Point","coordinates":[0,0]}\' --input2 \'{"type":"Point","coordinates":[1,1]}\''
  },
  'has-touch': {
    description: '判断边界是否接触',
    params: ['input1', 'input2'],
    optionalParams: ['tolerance'],
    example: 'wasi-geo has-touch --input1 \'{"type":"Polygon",...}\' --input2 \'{"type":"Polygon",...}\''
  },
  'is-identical': {
    description: '判断是否相等',
    params: ['input1', 'input2'],
    optionalParams: ['tolerance'],
    example: 'wasi-geo is-identical --input1 \'{"type":"Point","coordinates":[0,0]}\' --input2 \'{"type":"Point","coordinates":[0,0]}\''
  },
  'distance': {
    description: '计算两几何对象距离',
    params: ['input1', 'input2'],
    optionalParams: [],
    example: 'wasi-geo distance --input1 \'{"type":"Point","coordinates":[0,0]}\' --input2 \'{"type":"Point","coordinates":[1,1]}\''
  },

  // 点线关系
  'is-left': {
    description: '判断点是否在线的左侧',
    params: ['point', 'line-start', 'line-end'],
    optionalParams: [],
    example: 'wasi-geo is-left --point "[0,1]" --line-start "[0,0]" --line-end "[1,0]"'
  },
  'is-right': {
    description: '判断点是否在线的右侧',
    params: ['point', 'line-start', 'line-end'],
    optionalParams: [],
    example: 'wasi-geo is-right --point "[1,1]" --line-start "[0,0]" --line-end "[1,0]"'
  },
  'is-point-on-line': {
    description: '判断点是否在线段上',
    params: ['point', 'line-start', 'line-end'],
    optionalParams: ['extended'],
    example: 'wasi-geo is-point-on-line --point "[0.5,0]" --line-start "[0,0]" --line-end "[1,0]"'
  },
  'is-parallel': {
    description: '判断两线是否平行',
    params: ['line1-start', 'line1-end', 'line2-start', 'line2-end'],
    optionalParams: [],
    example: 'wasi-geo is-parallel --line1-start "[0,0]" --line1-end "[1,1]" --line2-start "[0,1]" --line2-end "[1,2]"'
  },
  'distance-to-line': {
    description: '计算点到线段的距离',
    params: ['point', 'line-start', 'line-end'],
    optionalParams: [],
    example: 'wasi-geo distance-to-line --point "[0,1]" --line-start "[0,0]" --line-end "[1,0]"'
  }
};

/**
 * 显示帮助信息
 */
function showHelp() {
  console.log(`
wasi-geo v${VERSION} - WASM 几何分析工具

用法:
  wasi-geo <operation> [options]

操作:
  单几何操作:
    buffer              缓冲区分析
    convex-hull         凸包计算
    resample            重采样分析
    smooth              线要素光滑分析
    geodesic-distance   计算测地线距离
    geodesic-area       计算测地线面积
    parallel            计算平行线

  双几何操作:
    intersect           相交分析
    union               合并分析
    erase               擦除分析
    xor                 异或分析
    clip                裁剪分析
    has-intersection    判断是否相交
    has-touch           判断边界是否接触
    is-identical        判断是否相等
    distance            计算两几何对象距离

  点线关系:
    is-left             判断点是否在线的左侧
    is-right            判断点是否在线的右侧
    is-point-on-line    判断点是否在线段上
    is-parallel         判断两线是否平行
    distance-to-line    计算点到线段的距离

通用选项:
  --input <json>        直接传入 GeoJSON 字符串
  --input-file <path>   从文件读取 GeoJSON
  --output <path>       输出到文件（默认 stdout）
  --pretty              美化 JSON 输出
  --help, -h            显示帮助信息
  --version, -v         显示版本信息

查看具体操作帮助:
  wasi-geo <operation> --help

示例:
  # 缓冲区分析
  wasi-geo buffer --input '{"type":"Point","coordinates":[120,30]}' --radius 2

  # 凸包计算
  wasi-geo convex-hull --input '{"type":"MultiPoint","coordinates":[[0,0],[1,1],[1,0],[0,1]]}'

  # Windows CMD 示例（注意转义引号）
  wasi-geo buffer --input "{\\"type\\":\\"Point\\",\\"coordinates\\":[120,30]}" --radius 2
`);
}

/**
 * 显示操作帮助信息
 */
function showOperationHelp(operation) {
  const op = OPERATIONS[operation];
  if (!op) {
    console.error(`错误: 未知操作 "${operation}"`);
    process.exit(1);
  }

  console.log(`
wasi-geo ${operation} - ${op.description}

用法:
  wasi-geo ${operation} ${op.params.map(p => `--${p} <value>`).join(' ')} [options]

参数:
${op.params.map(p => `  --${p} <value>    必需参数`).join('\n')}
${op.optionalParams.map(p => `  --${p} <value>    可选参数`).join('\n')}

示例:
  ${op.example}
`);
}

/**
 * 解析命令行参数
 */
function parseArgs(args) {
  const result = {
    operation: null,
    input: null,
    inputFile: null,
    input1: null,
    input1File: null,
    input2: null,
    input2File: null,
    output: null,
    pretty: false,
    // 操作特定参数
    radius: null,
    tolerance: null,
    smoothness: null,
    distance: null,
    majorAxis: 6378137,
    flatten: 0.003352810664,
    extended: false,
    // 点线关系参数
    point: null,
    lineStart: null,
    lineEnd: null,
    line1Start: null,
    line1End: null,
    line2Start: null,
    line2End: null
  };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];

    switch (arg) {
      case '-h':
      case '--help':
        result.help = true;
        break;

      case '-v':
      case '--version':
        result.version = true;
        break;

      case '--input':
        result.input = args[++i];
        break;

      case '--input-file':
        result.inputFile = args[++i];
        break;

      case '--input1':
        result.input1 = args[++i];
        break;

      case '--input1-file':
        result.input1File = args[++i];
        break;

      case '--input2':
        result.input2 = args[++i];
        break;

      case '--input2-file':
        result.input2File = args[++i];
        break;

      case '--output':
        result.output = args[++i];
        break;

      case '--pretty':
        result.pretty = true;
        break;

      case '--radius':
        result.radius = parseFloat(args[++i]);
        break;

      case '--tolerance':
        result.tolerance = parseFloat(args[++i]);
        break;

      case '--smoothness':
        result.smoothness = parseInt(args[++i], 10);
        break;

      case '--distance':
        result.distance = parseFloat(args[++i]);
        break;

      case '--major-axis':
        result.majorAxis = parseFloat(args[++i]);
        break;

      case '--flatten':
        result.flatten = parseFloat(args[++i]);
        break;

      case '--extended':
        result.extended = true;
        break;

      case '--point':
        result.point = args[++i];
        break;

      case '--line-start':
        result.lineStart = args[++i];
        break;

      case '--line-end':
        result.lineEnd = args[++i];
        break;

      case '--line1-start':
        result.line1Start = args[++i];
        break;

      case '--line1-end':
        result.line1End = args[++i];
        break;

      case '--line2-start':
        result.line2Start = args[++i];
        break;

      case '--line2-end':
        result.line2End = args[++i];
        break;

      default:
        // 第一个非选项参数是操作名
        if (!arg.startsWith('-') && !result.operation) {
          result.operation = arg;
        }
        break;
    }
  }

  return result;
}

/**
 * 从 stdin 读取数据
 */
function readStdin() {
  return new Promise((resolve, reject) => {
    let data = '';

    process.stdin.setEncoding('utf8');

    process.stdin.on('readable', () => {
      let chunk;
      while ((chunk = process.stdin.read()) !== null) {
        data += chunk;
      }
    });

    process.stdin.on('end', () => {
      resolve(data.trim());
    });

    process.stdin.on('error', reject);
  });
}

/**
 * 获取输入数据
 */
async function getInputData(args) {
  if (args.input) {
    return args.input;
  } else if (args.inputFile) {
    if (!fs.existsSync(args.inputFile)) {
      throw new Error(`文件不存在: ${args.inputFile}`);
    }
    return fs.readFileSync(args.inputFile, 'utf-8');
  } else if (!process.stdin.isTTY) {
    return await readStdin();
  }
  return null;
}

/**
 * 解析坐标数组
 */
function parseCoordinates(str) {
  try {
    const arr = JSON.parse(str);
    if (Array.isArray(arr) && arr.length >= 2) {
      return arr;
    }
    throw new Error('无效的坐标格式');
  } catch (e) {
    throw new Error(`坐标解析失败: ${e.message}`);
  }
}

/**
 * 执行操作
 */
async function executeOperation(args) {
  const op = OPERATIONS[args.operation];
  if (!op) {
    throw new Error(`未知操作: ${args.operation}`);
  }

  let result;

  switch (args.operation) {
    // 单几何操作
    case 'buffer': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      if (args.radius === null) throw new Error('缺少 --radius 参数');
      result = await GeometryAnalysis.buffer(input, args.radius);
      break;
    }

    case 'convex-hull': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      result = await GeometryAnalysis.computeConvexHull(input);
      break;
    }

    case 'resample': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      if (args.tolerance === null) throw new Error('缺少 --tolerance 参数');
      result = await GeometryAnalysis.resample(input, args.tolerance);
      break;
    }

    case 'smooth': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      if (args.smoothness === null) throw new Error('缺少 --smoothness 参数');
      result = await GeometryAnalysis.smooth(input, args.smoothness);
      break;
    }

    case 'geodesic-distance': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      result = await GeometryAnalysis.computeGeodesicDistance(input, args.majorAxis, args.flatten);
      break;
    }

    case 'geodesic-area': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      result = await GeometryAnalysis.computeGeodesicArea(input, args.majorAxis, args.flatten);
      break;
    }

    case 'parallel': {
      const input = await getInputData(args);
      if (!input) throw new Error('缺少输入数据');
      if (args.distance === null) throw new Error('缺少 --distance 参数');
      result = await GeometryAnalysis.computeParallel(input, args.distance);
      break;
    }

    // 双几何操作
    case 'intersect': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.intersect(input1, input2);
      break;
    }

    case 'union': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.union(input1, input2);
      break;
    }

    case 'erase': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.erase(input1, input2);
      break;
    }

    case 'xor': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.xor(input1, input2);
      break;
    }

    case 'clip': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.clip(input1, input2);
      break;
    }

    case 'has-intersection': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.hasIntersection(input1, input2, args.tolerance || 0);
      break;
    }

    case 'has-touch': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.hasTouch(input1, input2, args.tolerance || 0);
      break;
    }

    case 'is-identical': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.isIdentical(input1, input2, args.tolerance || 0);
      break;
    }

    case 'distance': {
      if (!args.input1 && !args.input1File) throw new Error('缺少第一个输入数据');
      if (!args.input2 && !args.input2File) throw new Error('缺少第二个输入数据');

      let input1 = args.input1;
      let input2 = args.input2;

      if (args.input1File) {
        if (!fs.existsSync(args.input1File)) throw new Error(`文件不存在: ${args.input1File}`);
        input1 = fs.readFileSync(args.input1File, 'utf-8');
      }
      if (args.input2File) {
        if (!fs.existsSync(args.input2File)) throw new Error(`文件不存在: ${args.input2File}`);
        input2 = fs.readFileSync(args.input2File, 'utf-8');
      }

      result = await GeometryAnalysis.distance(input1, input2);
      break;
    }

    // 点线关系
    case 'is-left': {
      if (!args.point) throw new Error('缺少 --point 参数');
      if (!args.lineStart) throw new Error('缺少 --line-start 参数');
      if (!args.lineEnd) throw new Error('缺少 --line-end 参数');

      const point = parseCoordinates(args.point);
      const lineStart = parseCoordinates(args.lineStart);
      const lineEnd = parseCoordinates(args.lineEnd);

      result = await GeometryAnalysis.isLeft(point, lineStart, lineEnd);
      break;
    }

    case 'is-right': {
      if (!args.point) throw new Error('缺少 --point 参数');
      if (!args.lineStart) throw new Error('缺少 --line-start 参数');
      if (!args.lineEnd) throw new Error('缺少 --line-end 参数');

      const point = parseCoordinates(args.point);
      const lineStart = parseCoordinates(args.lineStart);
      const lineEnd = parseCoordinates(args.lineEnd);

      result = await GeometryAnalysis.isRight(point, lineStart, lineEnd);
      break;
    }

    case 'is-point-on-line': {
      if (!args.point) throw new Error('缺少 --point 参数');
      if (!args.lineStart) throw new Error('缺少 --line-start 参数');
      if (!args.lineEnd) throw new Error('缺少 --line-end 参数');

      const point = parseCoordinates(args.point);
      const lineStart = parseCoordinates(args.lineStart);
      const lineEnd = parseCoordinates(args.lineEnd);

      result = await GeometryAnalysis.isPointOnLine(point, lineStart, lineEnd, args.extended);
      break;
    }

    case 'is-parallel': {
      if (!args.line1Start) throw new Error('缺少 --line1-start 参数');
      if (!args.line1End) throw new Error('缺少 --line1-end 参数');
      if (!args.line2Start) throw new Error('缺少 --line2-start 参数');
      if (!args.line2End) throw new Error('缺少 --line2-end 参数');

      const line1Start = parseCoordinates(args.line1Start);
      const line1End = parseCoordinates(args.line1End);
      const line2Start = parseCoordinates(args.line2Start);
      const line2End = parseCoordinates(args.line2End);

      result = await GeometryAnalysis.isParallel(line1Start, line1End, line2Start, line2End);
      break;
    }

    case 'distance-to-line': {
      if (!args.point) throw new Error('缺少 --point 参数');
      if (!args.lineStart) throw new Error('缺少 --line-start 参数');
      if (!args.lineEnd) throw new Error('缺少 --line-end 参数');

      const point = parseCoordinates(args.point);
      const lineStart = parseCoordinates(args.lineStart);
      const lineEnd = parseCoordinates(args.lineEnd);

      result = await GeometryAnalysis.distanceToLineSegment(point, lineStart, lineEnd);
      break;
    }

    default:
      throw new Error(`未实现的操作: ${args.operation}`);
  }

  return result;
}

/**
 * 主函数
 */
async function main() {
  const args = parseArgs(process.argv.slice(2));

  // 显示帮助
  if (args.help) {
    if (args.operation) {
      showOperationHelp(args.operation);
    } else {
      showHelp();
    }
    process.exit(0);
  }

  // 显示版本
  if (args.version) {
    console.log(`wasi-geo v${VERSION}`);
    process.exit(0);
  }

  // 检查操作
  if (!args.operation) {
    console.error('错误: 请指定操作');
    console.error('使用 --help 查看帮助信息');
    process.exit(1);
  }

  try {
    const result = await executeOperation(args);

    // 格式化输出
    const output = args.pretty
      ? JSON.stringify(result, null, 2)
      : JSON.stringify(result);

    // 输出结果
    if (args.output) {
      fs.writeFileSync(args.output, output + '\n');
      console.error(`结果已写入: ${args.output}`);
    } else {
      console.log(output);
    }

  } catch (error) {
    console.error(`错误: ${error.message}`);
    if (process.env.DEBUG) {
      console.error(error.stack);
    }
    process.exit(1);
  }
}

// 运行主函数
main();


path: references/usage.md
content: # SuperMap WASI Geometry 详细使用说明

## 安装与配置

### 环境要求

- Node.js 14.0 或更高版本
- 支持 SuperMap WebAssembly 的运行环境

### 验证安装

```bash
# 检查 Node.js 版本
node --version

# 测试 CLI
node scripts/bin/supermap-wasi-geo.js --help
```

## 支持的操作

### 单几何操作

| 操作 | 说明 | 参数 |
|------|------|------|
| `buffer` | 缓冲区分析 | `--input`, `--radius` |
| `convex-hull` | 凸包计算 | `--input` |
| `resample` | 重采样分析 | `--input`, `--tolerance` |
| `smooth` | 线要素光滑 | `--input`, `--smoothness` |
| `geodesic-distance` | 测地线距离 | `--input`, `[--major-axis]`, `[--flatten]` |
| `geodesic-area` | 测地线面积 | `--input`, `[--major-axis]`, `[--flatten]` |
| `parallel` | 计算平行线 | `--input`, `--distance` |

### 双几何操作

| 操作 | 说明 | 参数 |
|------|------|------|
| `intersect` | 相交分析 | `--input1`, `--input2` |
| `union` | 合并分析 | `--input1`, `--input2` |
| `erase` | 擦除分析 | `--input1`, `--input2` |
| `xor` | 异或分析 | `--input1`, `--input2` |
| `clip` | 裁剪分析 | `--input1`, `--input2` |
| `has-intersection` | 判断是否相交 | `--input1`, `--input2`, `[--tolerance]` |
| `has-touch` | 判断边界是否接触 | `--input1`, `--input2`, `[--tolerance]` |
| `is-identical` | 判断是否相等 | `--input1`, `--input2`, `[--tolerance]` |
| `distance` | 计算两几何距离 | `--input1`, `--input2` |

### 点线关系分析

| 操作 | 说明 | 参数 |
|------|------|------|
| `is-left` | 点是否在线左侧 | `--point`, `--line-start`, `--line-end` |
| `is-right` | 点是否在线右侧 | `--point`, `--line-start`, `--line-end` |
| `is-point-on-line` | 点是否在线段上 | `--point`, `--line-start`, `--line-end`, `[--extended]` |
| `is-parallel` | 两线是否平行 | `--line1-start`, `--line1-end`, `--line2-start`, `--line2-end` |
| `distance-to-line` | 点到线段距离 | `--point`, `--line-start`, `--line-end` |

## 完整 API 参考

### CLI 命令

```
node scripts/bin/supermap-wasi-geo.js <operation> [options]
```

### 通用选项

| 选项 | 简写 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|------|--------|------|
| `--input` | | string | 二选一 | - | GeoJSON 字符串 |
| `--input-file` | | path | 二选一 | - | 输入文件路径 |
| `--output` | | path | 否 | stdout | 输出文件路径 |
| `--pretty` | | boolean | 否 | false | 美化 JSON 输出 |
| `--help` | `-h` | | 否 | | 显示帮助 |
| `--version` | `-v` | | 否 | | 显示版本 |

### 操作特定参数

| 参数 | 适用操作 | 说明 |
|------|----------|------|
| `--radius` | buffer | 缓冲区半径 |
| `--tolerance` | resample, has-intersection, has-touch, is-identical | 容差值 |
| `--smoothness` | smooth | 光滑度（整数） |
| `--distance` | parallel | 平行线距离 |
| `--major-axis` | geodesic-distance, geodesic-area | 椭球长半轴（默认 6378137） |
| `--flatten` | geodesic-distance, geodesic-area | 椭球扁率（默认 0.003352810664） |
| `--extended` | is-point-on-line | 是否延长线段 |

### 输入格式

支持以下 GeoJSON 格式：

#### 1. Geometry 直接输入

```json
{"type": "Point", "coordinates": [120, 30]}
```

#### 2. Feature 输入

```json
{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [120, 30]
  },
  "properties": {
    "name": "示例点"
  }
}
```

#### 3. 支持的几何类型

- `Point` - 点
- `LineString` - 线
- `Polygon` - 多边形
- `MultiPoint` - 多点
- `MultiLineString` - 多线
- `MultiPolygon` - 多多边形

### 输出格式

#### 几何操作输出

返回 GeoJSON Feature：

```json
{
  "type": "Feature",
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [[x1, y1], [x2, y2], ..., [x1, y1]]
    ]
  },
  "properties": {
    "bufferRadius": <radius>,
    "...": "其他原始属性"
  }
}
```

#### 判断操作输出

返回结果对象：

```json
{
  "type": "Result",
  "hasIntersection": true
}
```

#### 计算操作输出

返回结果对象：

```json
{
  "type": "Result",
  "distance": 5,
  "unit": "meters"
}
```

## 使用场景

### 场景 1: 缓冲区分析

分析城市中心 50km 范围内的区域：

```bash
node scripts/bin/supermap-wasi-geo.js buffer --input '{"type":"Point","coordinates":[116.4,39.9]}' --radius 0.5 --pretty
```

> 注意：经纬度坐标系下，1度约等于 111km

### 场景 2: 距离计算

计算两点之间的欧氏距离：

```bash
node scripts/bin/supermap-wasi-geo.js distance --input1 '{"type":"Point","coordinates":[0,0]}' --input2 '{"type":"Point","coordinates":[3,4]}'
# 输出: {"type":"Result","distance":5}
```

### 场景 3: 相交判断

判断两个几何对象是否相交：

```bash
node scripts/bin/supermap-wasi-geo.js has-intersection --input1 '{"type":"Point","coordinates":[0,0]}' --input2 '{"type":"Point","coordinates":[1,1]}'
# 输出: {"type":"Result","hasIntersection":false}
```

### 场景 4: 点线关系

判断点是否在线段上：

```bash
node scripts/bin/supermap-wasi-geo.js is-point-on-line --point "[0.5,0]" --line-start "[0,0]" --line-end "[1,0]"
# 输出: {"type":"Result","isPointOnLine":true}
```

### 场景 5: 测地线计算

计算线段的测地线距离（单位：米）：

```bash
node scripts/bin/supermap-wasi-geo.js geodesic-distance --input '{"type":"LineString","coordinates":[[0,0],[1,1]]}'
```

### 场景 6: 批量处理

使用脚本批量处理多个文件：

```bash
# Windows PowerShell
Get-ChildItem *.geojson | ForEach-Object {
    node scripts/bin/supermap-wasi-geo.js buffer --input-file $_.Name --radius 1 --output "buffer_$($_.Name)"
}
```

## Node.js 模块 API

### 引入模块

```javascript
const GeometryAnalysis = require('./scripts/src/geometry-analysis.js');
```

### 单几何操作

```javascript
// 缓冲区分析
const buffer = await GeometryAnalysis.buffer(input, radius);

// 凸包计算
const hull = await GeometryAnalysis.computeConvexHull(input);

// 重采样
const resampled = await GeometryAnalysis.resample(input, tolerance);

// 光滑
const smoothed = await GeometryAnalysis.smooth(input, smoothness);

// 测地线距离
const geoDist = await GeometryAnalysis.computeGeodesicDistance(input, majorAxis?, flatten?);

// 测地线面积
const geoArea = await GeometryAnalysis.computeGeodesicArea(input, majorAxis?, flatten?);

// 平行线
const parallel = await GeometryAnalysis.computeParallel(input, distance);
```

### 双几何操作

```javascript
// 相交
const intersect = await GeometryAnalysis.intersect(input1, input2);

// 合并
const union = await GeometryAnalysis.union(input1, input2);

// 擦除
const erased = await GeometryAnalysis.erase(input1, input2);

// 异或
const xored = await GeometryAnalysis.xor(input1, input2);

// 裁剪
const clipped = await GeometryAnalysis.clip(input1, input2);

// 判断相交
const hasInt = await GeometryAnalysis.hasIntersection(input1, input2, tolerance?);

// 判断接触
const hasTch = await GeometryAnalysis.hasTouch(input1, input2, tolerance?);

// 判断相等
const isId = await GeometryAnalysis.isIdentical(input1, input2, tolerance?);

// 计算距离
const dist = await GeometryAnalysis.distance(input1, input2);
```

### 点线关系

```javascript
// 点是否在线左侧
const left = await GeometryAnalysis.isLeft(point, lineStart, lineEnd);

// 点是否在线右侧
const right = await GeometryAnalysis.isRight(point, lineStart, lineEnd);

// 点是否在线段上
const onLine = await GeometryAnalysis.isPointOnLine(point, lineStart, lineEnd, extended?);

// 两线是否平行
const parallel = await GeometryAnalysis.isParallel(line1Start, line1End, line2Start, line2End);

// 点到线段距离
const dist = await GeometryAnalysis.distanceToLineSegment(point, lineStart, lineEnd);
```

## 常见问题

### Q: 缓冲区结果不正确？

A: 检查以下几点：
1. 坐标单位是否正确（经纬度用度，投影坐标用米）
2. 半径是否为正数
3. GeoJSON 格式是否正确

### Q: Windows CMD 中引号问题？

A: Windows CMD 使用双引号，内部双引号需要转义：

```cmd
node scripts\bin\supermap-wasi-geo.js buffer --input "{\"type\":\"Point\",\"coordinates\":[120,30]}" --radius 2
```

### Q: 首次运行很慢？

A: 首次运行需要初始化 WASM 模块（约 1-2 秒），后续调用会更快。

### Q: 如何在其他目录使用？

A: 使用绝对路径或先 cd 到 skill 目录：

```bash
cd /path/to/wasi-buffer-skill
node scripts/bin/supermap-wasi-geo.js buffer --input '...' --radius 1
```

## 性能优化

1. **批量处理**：使用文件输入/输出减少命令行参数解析开销
2. **管道操作**：使用 stdin/stdout 进行链式处理
3. **避免重复初始化**：在脚本中复用模块实例

## 技术细节

### WASM 模块

- 文件：`scripts/wasm/UGCWasmAll.js`
- 大小：约 1.6 MB（包含内嵌 WASM 二进制）
- 来源：SuperMap UGC Wasm

### 内存管理

WASM 模块使用 Emscripten 运行时，自动管理内存。大几何对象可能需要更多内存。

### 坐标系

默认假设输入为 WGS84 经纬度坐标。使用其他坐标系时，注意半径单位匹配。


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)