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. 02GPS トラックの分析と可視化
  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 を使用します)
  • 基本的な距離計算(ハバーサイン公式を使用します)
  • 静的な地図の埋め込み(Mapbox Static API を使用します)
  • ジオコーディング(Nominatim または Google Geocoding API を使用します)

技術の選択

データベース:PostGIS と MongoDB Geospatial の比較
機能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 万ポイント、半径 5 km のクエリ

  • インデックスなし:3.2 秒(全件スキャン)
  • GiST インデックスあり:12 ms(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;

さらによい方法:常に 1 つの座標系(WGS84)で保存し、表示時にのみ変換します。

タイムライン:

  • 2005 年:Google Maps が Web Mercator(EPSG:3857)を導入
  • 2010 年:アプリで WGS84 データと Web Mercator タイルが混在し、混乱がピークに
  • 2024 年:ベストプラクティスは、WGS84 で保存し、タイルの描画時のみ 3857 に変換することです

アンチパターン 4:巨大な GeoJSON ファイルを読み込む

問題:50 MB の 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 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 km98.7%の誤り
ハバーサイン(球体)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 | 地図タイル | 座標系 | ドローンデータ処理 | 空間クエリ

コメント