geospatial-data-pipeline について
geospatial-data-pipeline は、ドローン画像や GPS トラックなどの地理データを処理・可視化するエンジニア向けのスキルです。空間データベース、座標系、空間クエリ、効率的な地図データ配信の実践的な選択を扱います。
主な機能
- 01ドローン画像と GPS データの処理
- 02空間クエリとインデックスの指針
- 03座標系の変換
- 04GeoJSON の最適化
- 05ベクタータイルの生成
- 06地図データの可視化
活用シーン
- 01地点周辺の場所を検索
- 02GPS トラックの分析と可視化
- 03ドローン測量データの準備
- 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.jsonreferences/coordinate-systems.mdgeojson-optimization.mdpostgis-guide.mdscripts/geospatial_processor.tstile_generator.ts詳細
地理空間データパイプライン
大規模な地理空間データの処理、最適化、可視化に関するエキスパートです。
使用する場面
✅ 次の用途に使用します:
- ドローン画像の処理とアノテーション
- GPS トラックの分析と可視化
- 位置情報に基づく検索(近くの X を検索)
- Web/モバイル向けのマップタイル生成
- 座標系の変換
- ジオフェンシングと空間クエリ
- Web 向けの GeoJSON 最適化
❌ 次の用途には使用しません:
- 単純な住所の検証(住所 API を使用します)
- 基本的な距離計算(ハバーサイン公式を使用します)
- 静的な地図の埋め込み(Mapbox Static API を使用します)
- ジオコーディング(Nominatim または Google Geocoding API を使用します)
技術の選択
データベース:PostGIS と MongoDB Geospatial の比較
| 機能 | PostGIS | MongoDB |
|---|---|---|
| 空間インデックス | GiST、SP-GiST | 2dsphere |
| クエリ言語 | 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]); // <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 km | 98.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 (<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 >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 | 地図タイル | 座標系 | ドローンデータ処理 | 空間クエリ