mcpField
G
geospatial-data-pipeline

geospatial-data-pipeline

github.com/curiositech数据与数据库257345 次安装2 天前更新
代码仓库

关于 geospatial-data-pipeline

geospatial-data-pipeline 面向处理和可视化地理数据的工程师,覆盖无人机影像与 GPS 轨迹。内容侧重空间数据库、坐标系、空间查询及高效地图数据交付的实践选择。

主要功能

  1. 01无人机影像与 GPS 数据处理
  2. 02空间查询与索引指导
  3. 03坐标系转换
  4. 04GeoJSON 优化
  5. 05矢量瓦片生成
  6. 06地图数据可视化

适用场景

  1. 01搜索某点附近的位置
  2. 02分析并可视化 GPS 轨迹
  3. 03整理无人机测绘数据
  4. 04高效交付大型地图数据集

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/curiositech/some_claude_skills --skill geospatial-data-pipeline

安装后在 Claude Code 中输入/geospatial-data-pipeline确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.claude-plugin/
plugin.json
references/
coordinate-systems.md
geojson-optimization.md
postgis-guide.md
scripts/
geospatial_processor.ts
tile_generator.ts

详细介绍

地理空间数据管道

擅长大规模处理、优化和可视化地理空间数据。

适用场景

✅ 适用于:

  • 无人机影像处理和标注
  • GPS 轨迹分析和可视化
  • 基于位置的搜索(查找附近的 X)
  • 为 Web/移动端生成地图瓦片
  • 坐标系转换
  • 地理围栏和空间查询
  • 面向 Web 的 GeoJSON 优化

❌ 不适用于:

  • 简单的地址验证(使用地址 API)
  • 基本的距离计算(使用 Haversine 公式)
  • 静态地图嵌入(使用 Mapbox Static API)
  • 地理编码(使用 Nominatim 或 Google Geocoding API)

技术选型

数据库:PostGIS 与 MongoDB 地理空间功能对比
功能PostGISMongoDB
空间索引GiST、SP-GiST2dsphere
查询语言SQL + 空间函数聚合管道
几何类型20+(完整支持 OGC)基础类型(Point、Line、Polygon)
坐标系通过 EPSG 支持 6000+ 种仅 WGS84
性能(1000 万个点)<100ms<200ms
最适用于复杂空间分析以文档为中心的应用

时间线:

  • 2005 年:PostGIS 1.0 发布
  • 2012 年:MongoDB 添加地理空间索引
  • 2020 年:PostGIS 3.0 提升性能
  • 2024 年:PostGIS 仍是 GIS 工作负载的黄金标准

常见反模式

反模式 1:将坐标存储为字符串

新手想法:“我只要把经纬度存成文本就行,很简单。”

问题:无法使用空间索引,查询速度慢,也没有验证。

错误做法:

// ❌ String storage, no spatial features
interface Location {
  id: string;
  name: string;
  latitude: string;   // "37.7749"
  longitude: string;  // "-122.4194"
}

// Linear scan for "nearby" queries
async function findNearby(lat: string, lon: string): Promise<Location[]> {
  const all = await db.locations.findAll();

  return all.filter(loc => {
    const distance = calculateDistance(
      parseFloat(lat),
      parseFloat(lon),
      parseFloat(loc.latitude),
      parseFloat(loc.longitude)
    );
    return distance < 5000; // 5km
  });
}

错误原因:O(N) 线性扫描、没有空间索引,还需要承担字符串解析开销。

正确做法:

// ✅ PostGIS GEOGRAPHY type with spatial index
CREATE TABLE locations (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255),
  location GEOGRAPHY(POINT, 4326)  -- WGS84 coordinates
);

-- Spatial index (GiST)
CREATE INDEX idx_locations_geography ON locations USING GIST(location);

-- TypeScript query
async function findNearby(lat: number, lon: number, radiusMeters: number): Promise<Location[]> {
  const query = `
    SELECT id, name, ST_AsGeoJSON(location) as geojson
    FROM locations
    WHERE ST_DWithin(
      location,
      ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography,
      $3
    )
    ORDER BY location <-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography
    LIMIT 100
  `;

  return db.query(query, [lon, lat, radiusMeters]);  // &lt;10ms with index
}

时间线背景:

  • 2000 年代:将经纬度存储为 FLOAT 列,并在应用代码中进行计算
  • 2010 年代:PostGIS 普及,开始使用空间索引
  • 2024 年:GEOGRAPHY 类型可自动处理地球曲率

反模式 2:不使用空间索引

问题:邻近查询会扫描整张表。

错误做法:

-- ❌ No index, sequential scan
CREATE TABLE drone_images (
  id SERIAL PRIMARY KEY,
  image_url VARCHAR(255),
  location GEOGRAPHY(POINT, 4326)
);

-- This query scans ALL rows
SELECT * FROM drone_images
WHERE ST_DWithin(
  location,
  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,
  1000  -- 1km
);

EXPLAIN 输出:Seq Scan on drone_images (cost=0.00..1234.56 rows=1 width=123)

正确做法:

-- ✅ GiST index for spatial queries
CREATE INDEX idx_drone_images_location ON drone_images USING GIST(location);

-- Same query, now uses index
SELECT * FROM drone_images
WHERE ST_DWithin(
  location,
  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,
  1000
);

EXPLAIN 输出:Bitmap Index Scan on idx_drone_images_location (cost=4.30..78.30 rows=50 width=123)

性能影响:1000 万个点,查询半径为 5km

  • 无索引:3.2 秒(全表扫描)
  • 使用 GiST 索引:12ms(快 99.6%)

反模式 3:混用坐标系

新手想法:“坐标不过就是数字,我可以混着用。”

问题:距离不准确,地图要素无法对齐。

错误做法:

// ❌ Mixing EPSG:4326 (WGS84) and EPSG:3857 (Web Mercator)
const userLocation = {
  lat: 37.7749,   // WGS84
  lon: -122.4194
};

const droneImage = {
  x: -13634876,  // Web Mercator (EPSG:3857)
  y: 4545684
};

// Comparing apples to oranges!
const distance = Math.sqrt(
  Math.pow(userLocation.lon - droneImage.x, 2) +
  Math.pow(userLocation.lat - droneImage.y, 2)
);

结果:距离严重错误(数百万“单位”)。

正确做法:

-- ✅ Transform to common coordinate system
SELECT ST_Distance(
  ST_Transform(
    ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326),  -- WGS84
    3857  -- Transform to Web Mercator
  ),
  ST_SetSRID(ST_MakePoint(-13634876, 4545684), 3857)  -- Already Web Mercator
) AS distance_meters;

更好的做法:始终使用一种坐标系(WGS84)存储,仅在显示时进行转换。

时间线:

  • 2005 年:Google Maps 引入 Web Mercator(EPSG:3857)
  • 2010 年:应用混用 WGS84 数据和 Web Mercator 瓦片,相关混淆达到顶峰
  • 2024 年:最佳实践:使用 WGS84 存储,仅在渲染瓦片时转换为 3857

反模式 4:加载超大的 GeoJSON 文件

问题:50MB 的 GeoJSON 文件会导致浏览器崩溃。

错误做法:

// ❌ Load entire file into memory
const geoJson = await fetch('/drone-survey-data.geojson').then(r => r.json());

// 50MB of GeoJSON = browser freeze
map.addSource('drone-data', {
  type: 'geojson',
  data: geoJson  // All 10,000 polygons loaded at once
});

正确做法 1:使用矢量瓦片(预先分块)

// ✅ Serve as vector tiles (MBTiles or PMTiles)
map.addSource('drone-data', {
  type: 'vector',
  tiles: ['https://api.example.com/tiles/{z}/{x}/{y}.pbf'],
  minzoom: 10,
  maxzoom: 18
});

// Browser only loads visible tiles

正确做法 2:简化 GeoJSON 并分块

# Simplify geometry (reduce points)
npm install -g @mapbox/geojson-precision
geojson-precision -p 5 input.geojson output.geojson

# Split into tiles
npm install -g geojson-vt
# Generate tiles programmatically (see scripts/tile_generator.ts)

正确做法 3:在服务端进行筛选

// ✅ Only fetch visible bounds
async function fetchVisibleFeatures(bounds: Bounds): Promise<GeoJSON> {
  const response = await fetch(
    `/api/features?bbox=${bounds.west},${bounds.south},${bounds.east},${bounds.north}`
  );
  return response.json();
}

map.on('moveend', async () => {
  const bounds = map.getBounds();
  const geojson = await fetchVisibleFeatures(bounds);
  map.getSource('dynamic-data').setData(geojson);
});

反模式 5:在球形地球上使用欧几里得距离

新手想法:“距离就是勾股定理。”

问题:大范围测量时不准确,在极地附近误差更大。

错误做法:

// ❌ Flat Earth distance (wrong!)
function distanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const dx = lon2 - lon1;
  const dy = lat2 - lat1;

  return Math.sqrt(dx * dx + dy * dy) * 111.32;  // 111.32 km/degree (WRONG)
}

// Example: San Francisco to New York
const distance = distanceKm(37.7749, -122.4194, 40.7128, -74.0060);
// Returns: ~55 km (WRONG! Actual: ~4,130 km)

错误原因:地球是球体,不是平面。

正确做法 1:使用 Haversine 公式(大圆距离)

// ✅ Haversine formula (spherical Earth)
function haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const R = 6371; // Earth radius in km

  const dLat = toRadians(lat2 - lat1);
  const dLon = toRadians(lon2 - lon1);

  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return R * c;
}

// San Francisco to New York
const distance = haversineKm(37.7749, -122.4194, 40.7128, -74.0060);
// Returns: ~4,130 km ✅

正确做法 2:使用 PostGIS(自动处理曲率)

-- ✅ PostGIS ST_Distance with GEOGRAPHY
SELECT ST_Distance(
  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,
  ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography
) / 1000 AS distance_km;
-- Returns: 4130.137 km ✅

精度对比:

方法SF 到 NYC误差
欧几里得距离(平面)55 km错误 98.7%
Haversine(球体)4,130 km✅ 正确
PostGIS(椭球体)4,135 km最准确

生产环境检查清单

□ PostGIS extension installed and spatial indexes created
□ All coordinates stored in consistent SRID (recommend: 4326)
□ GeoJSON files optimized (&lt;1MB) or served as vector tiles
□ Coordinate transformations use ST_Transform, not manual math
□ Distance calculations use ST_Distance with GEOGRAPHY type
□ Bounding box queries use ST_MakeEnvelope + ST_Intersects
□ Large geometries chunked (not &gt;100KB per feature)
□ Map tiles pre-generated for common zoom levels
□ CORS configured for tile servers
□ Rate limiting on geocoding/reverse geocoding endpoints

适用与不适用的场景

场景是否适用?
无人机影像标注与搜索✅ 是 - 处理测绘数据
GPS 轨迹可视化✅ 是 - 优化路径
查找最近的咖啡店✅ 是 - 执行空间查询
行政区边界查询✅ 是 - 执行点在多边形内判断
简单的地址自动补全❌ 否 - 使用 Mapbox/Google
在页面中嵌入静态地图❌ 否 - 使用 Static API
对单个地址进行地理编码❌ 否 - 使用地理编码 API

参考资料

  • /references/coordinate-systems.md - EPSG 代码、坐标转换、Web Mercator 与 WGS84
  • /references/postgis-guide.md - PostGIS 设置、空间索引、常见查询
  • /references/geojson-optimization.md - 简化、分块、矢量瓦片

脚本

  • scripts/geospatial_processor.ts - 处理无人机影像、GPS 轨迹并验证 GeoJSON
  • scripts/tile_generator.ts - 从 GeoJSON 生成矢量瓦片(MBTiles/PMTiles)

此技能适用范围:地理空间数据 | PostGIS | GeoJSON | 地图瓦片 | 坐标系 | 无人机数据处理 | 空间查询

评论

支持 Markdown