[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71HX6WRYD7MVR67PZVDK0GC":3,"skill-same:01M71HX6WRYD7MVR67PZVDK0GC":117},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":72,"commitSha":73,"skillDirectory":74,"forksCount":75,"repositoryHtmlUrl":76,"repositoryHomepageUrl":6,"ownerHtmlUrl":77,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":78,"install":79,"resources":92},"01M71HX6WRYD7MVR67PZVDK0GC","geospatial-data-pipeline",null,"Process, analyze, and visualize geospatial data at scale. Handles drone imagery, GPS tracks, GeoJSON optimization, coordinate transformations, and tile generation. Use for mapping apps, drone data processing, location-based services. Activate on \"geospatial\", \"GIS\", \"PostGIS\", \"GeoJSON\", \"map tiles\", \"coordinate systems\". NOT for simple address validation, basic distance calculations, or static map embeds.","JSGforever\u002Fskillguard","JSGforever","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F85541948?v=4","01M71FQD3E132VMR5F4TAYX3YS","01M71FQD4DYW0XJ4XQ4YQH75Y9",2,"GPL-3.0","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"pipeline","data pipeline","data","geospatial","geospatial data pipeline","activate","analysis","drone","Bash","apps","inspection","map","engineer","render","flows","static","postgres","processing","visualization","coordinate","2026-11-10 10:12:30",false,"---\nname: geospatial-data-pipeline\ndescription: Process, analyze, and visualize geospatial data at scale. Handles drone imagery, GPS tracks, GeoJSON optimization, coordinate transformations, and tile generation. Use for mapping apps, drone\n  data processing, location-based services. Activate on \"geospatial\", \"GIS\", \"PostGIS\", \"GeoJSON\", \"map tiles\", \"coordinate systems\". NOT for simple address validation, basic distance calculations, or static\n  map embeds.\nallowed-tools: Read,Write,Edit,Bash(npm:*,gdal*,postgres*)\nmetadata:\n  category: Data & Analytics\n  tags:\n  - geospatial\n  - data\n  - pipeline\n  - gis\n  - postgis\n  pairs-with:\n  - skill: large-scale-map-visualization\n    reason: Geospatial pipelines produce the optimized data that map visualizations render\n  - skill: computer-vision-pipeline\n    reason: Drone imagery processing combines CV object detection with geospatial coordinates\n  - skill: drone-inspection-specialist\n    reason: Infrastructure inspection data flows through geospatial pipelines for analysis\n  - skill: data-pipeline-engineer\n    reason: Geospatial ETL shares batching, streaming, and transformation patterns with general data pipelines\n---\n\n# Geospatial Data Pipeline\n\nExpert in processing, optimizing, and visualizing geospatial data at scale.\n\n## When to Use\n\n✅ **Use for**:\n- Drone imagery processing and annotation\n- GPS track analysis and visualization\n- Location-based search (find nearby X)\n- Map tile generation for web\u002Fmobile\n- Coordinate system transformations\n- Geofencing and spatial queries\n- GeoJSON optimization for web\n\n❌ **NOT for**:\n- Simple address validation (use address APIs)\n- Basic distance calculations (use Haversine formula)\n- Static map embeds (use Mapbox Static API)\n- Geocoding (use Nominatim or Google Geocoding API)\n\n---\n\n## Technology Selection\n\n### Database: PostGIS vs MongoDB Geospatial\n\n| Feature | PostGIS | MongoDB |\n|---------|---------|---------|\n| Spatial indexes | GiST, SP-GiST | 2dsphere |\n| Query language | SQL + spatial functions | Aggregation pipeline |\n| Geometry types | 20+ (full OGC support) | Basic (Point, Line, Polygon) |\n| Coordinate systems | 6000+ via EPSG | WGS84 only |\n| Performance (10M points) | &lt;100ms | &lt;200ms |\n| Best for | Complex spatial analysis | Document-centric apps |\n\n**Timeline**:\n- 2005: PostGIS 1.0 released\n- 2012: MongoDB adds geospatial indexes\n- 2020: PostGIS 3.0 with improved performance\n- 2024: PostGIS remains gold standard for GIS workloads\n\n---\n\n## Common Anti-Patterns\n\n### Anti-Pattern 1: Storing Coordinates as Strings\n\n**Novice thinking**: \"I'll just store lat\u002Flon as text, it's simple\"\n\n**Problem**: Can't use spatial indexes, queries are slow, no validation.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ String storage, no spatial features\ninterface Location {\n  id: string;\n  name: string;\n  latitude: string;   \u002F\u002F \"37.7749\"\n  longitude: string;  \u002F\u002F \"-122.4194\"\n}\n\n\u002F\u002F Linear scan for \"nearby\" queries\nasync function findNearby(lat: string, lon: string): Promise\u003CLocation[]> {\n  const all = await db.locations.findAll();\n\n  return all.filter(loc => {\n    const distance = calculateDistance(\n      parseFloat(lat),\n      parseFloat(lon),\n      parseFloat(loc.latitude),\n      parseFloat(loc.longitude)\n    );\n    return distance \u003C 5000; \u002F\u002F 5km\n  });\n}\n```\n\n**Why wrong**: O(N) linear scan, no spatial index, string parsing overhead.\n\n**Correct approach**:\n```typescript\n\u002F\u002F ✅ PostGIS GEOGRAPHY type with spatial index\nCREATE TABLE locations (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255),\n  location GEOGRAPHY(POINT, 4326)  -- WGS84 coordinates\n);\n\n-- Spatial index (GiST)\nCREATE INDEX idx_locations_geography ON locations USING GIST(location);\n\n-- TypeScript query\nasync function findNearby(lat: number, lon: number, radiusMeters: number): Promise\u003CLocation[]> {\n  const query = `\n    SELECT id, name, ST_AsGeoJSON(location) as geojson\n    FROM locations\n    WHERE ST_DWithin(\n      location,\n      ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography,\n      $3\n    )\n    ORDER BY location \u003C-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography\n    LIMIT 100\n  `;\n\n  return db.query(query, [lon, lat, radiusMeters]);  \u002F\u002F &lt;10ms with index\n}\n```\n\n**Timeline context**:\n- 2000s: Stored lat\u002Flon as FLOAT columns, did math in app code\n- 2010s: PostGIS adoption, spatial indexes\n- 2024: `GEOGRAPHY` type handles Earth curvature automatically\n\n---\n\n### Anti-Pattern 2: Not Using Spatial Indexes\n\n**Problem**: Proximity queries do full table scans.\n\n**Wrong approach**:\n```sql\n-- ❌ No index, sequential scan\nCREATE TABLE drone_images (\n  id SERIAL PRIMARY KEY,\n  image_url VARCHAR(255),\n  location GEOGRAPHY(POINT, 4326)\n);\n\n-- This query scans ALL rows\nSELECT * FROM drone_images\nWHERE ST_DWithin(\n  location,\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  1000  -- 1km\n);\n```\n\n**EXPLAIN output**: `Seq Scan on drone_images (cost=0.00..1234.56 rows=1 width=123)`\n\n**Correct approach**:\n```sql\n-- ✅ GiST index for spatial queries\nCREATE INDEX idx_drone_images_location ON drone_images USING GIST(location);\n\n-- Same query, now uses index\nSELECT * FROM drone_images\nWHERE ST_DWithin(\n  location,\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  1000\n);\n```\n\n**EXPLAIN output**: `Bitmap Index Scan on idx_drone_images_location (cost=4.30..78.30 rows=50 width=123)`\n\n**Performance impact**: 10M points, 5km radius query\n- Without index: 3.2 seconds (full scan)\n- With GiST index: 12ms (99.6% faster)\n\n---\n\n### Anti-Pattern 3: Mixing Coordinate Systems\n\n**Novice thinking**: \"Coordinates are just numbers, I can mix them\"\n\n**Problem**: Incorrect distances, misaligned map features.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Mixing EPSG:4326 (WGS84) and EPSG:3857 (Web Mercator)\nconst userLocation = {\n  lat: 37.7749,   \u002F\u002F WGS84\n  lon: -122.4194\n};\n\nconst droneImage = {\n  x: -13634876,  \u002F\u002F Web Mercator (EPSG:3857)\n  y: 4545684\n};\n\n\u002F\u002F Comparing apples to oranges!\nconst distance = Math.sqrt(\n  Math.pow(userLocation.lon - droneImage.x, 2) +\n  Math.pow(userLocation.lat - droneImage.y, 2)\n);\n```\n\n**Result**: Wildly incorrect distance (millions of \"units\").\n\n**Correct approach**:\n```sql\n-- ✅ Transform to common coordinate system\nSELECT ST_Distance(\n  ST_Transform(\n    ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326),  -- WGS84\n    3857  -- Transform to Web Mercator\n  ),\n  ST_SetSRID(ST_MakePoint(-13634876, 4545684), 3857)  -- Already Web Mercator\n) AS distance_meters;\n```\n\n**Or better**: Always store in one system (WGS84), transform on display only.\n\n**Timeline**:\n- 2005: Web Mercator (EPSG:3857) introduced by Google Maps\n- 2010: Confusion peaks as apps mix WGS84 data with Web Mercator tiles\n- 2024: Best practice: Store WGS84, transform to 3857 only for tile rendering\n\n---\n\n### Anti-Pattern 4: Loading Huge GeoJSON Files\n\n**Problem**: 50MB GeoJSON file crashes browser.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Load entire file into memory\nconst geoJson = await fetch('\u002Fdrone-survey-data.geojson').then(r => r.json());\n\n\u002F\u002F 50MB of GeoJSON = browser freeze\nmap.addSource('drone-data', {\n  type: 'geojson',\n  data: geoJson  \u002F\u002F All 10,000 polygons loaded at once\n});\n```\n\n**Correct approach 1**: Vector tiles (pre-chunked)\n```typescript\n\u002F\u002F ✅ Serve as vector tiles (MBTiles or PMTiles)\nmap.addSource('drone-data', {\n  type: 'vector',\n  tiles: ['https:\u002F\u002Fapi.example.com\u002Ftiles\u002F{z}\u002F{x}\u002F{y}.pbf'],\n  minzoom: 10,\n  maxzoom: 18\n});\n\n\u002F\u002F Browser only loads visible tiles\n```\n\n**Correct approach 2**: GeoJSON simplification + chunking\n```bash\n# Simplify geometry (reduce points)\nnpm install -g @mapbox\u002Fgeojson-precision\ngeojson-precision -p 5 input.geojson output.geojson\n\n# Split into tiles\nnpm install -g geojson-vt\n# Generate tiles programmatically (see scripts\u002Ftile_generator.ts)\n```\n\n**Correct approach 3**: Server-side filtering\n```typescript\n\u002F\u002F ✅ Only fetch visible bounds\nasync function fetchVisibleFeatures(bounds: Bounds): Promise\u003CGeoJSON> {\n  const response = await fetch(\n    `\u002Fapi\u002Ffeatures?bbox=${bounds.west},${bounds.south},${bounds.east},${bounds.north}`\n  );\n  return response.json();\n}\n\nmap.on('moveend', async () => {\n  const bounds = map.getBounds();\n  const geojson = await fetchVisibleFeatures(bounds);\n  map.getSource('dynamic-data').setData(geojson);\n});\n```\n\n---\n\n### Anti-Pattern 5: Euclidean Distance on Spherical Earth\n\n**Novice thinking**: \"Distance is just Pythagorean theorem\"\n\n**Problem**: Incorrect at scale, worse near poles.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Flat Earth distance (wrong!)\nfunction distanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number {\n  const dx = lon2 - lon1;\n  const dy = lat2 - lat1;\n\n  return Math.sqrt(dx * dx + dy * dy) * 111.32;  \u002F\u002F 111.32 km\u002Fdegree (WRONG)\n}\n\n\u002F\u002F Example: San Francisco to New York\nconst distance = distanceKm(37.7749, -122.4194, 40.7128, -74.0060);\n\u002F\u002F Returns: ~55 km (WRONG! Actual: ~4,130 km)\n```\n\n**Why wrong**: Earth is a sphere, not a flat plane.\n\n**Correct approach 1**: Haversine formula (great circle distance)\n```typescript\n\u002F\u002F ✅ Haversine formula (spherical Earth)\nfunction haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number {\n  const R = 6371; \u002F\u002F Earth radius in km\n\n  const dLat = toRadians(lat2 - lat1);\n  const dLon = toRadians(lon2 - lon1);\n\n  const a =\n    Math.sin(dLat \u002F 2) * Math.sin(dLat \u002F 2) +\n    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *\n    Math.sin(dLon \u002F 2) * Math.sin(dLon \u002F 2);\n\n  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n  return R * c;\n}\n\n\u002F\u002F San Francisco to New York\nconst distance = haversineKm(37.7749, -122.4194, 40.7128, -74.0060);\n\u002F\u002F Returns: ~4,130 km ✅\n```\n\n**Correct approach 2**: PostGIS (handles curvature automatically)\n```sql\n-- ✅ PostGIS ST_Distance with GEOGRAPHY\nSELECT ST_Distance(\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography\n) \u002F 1000 AS distance_km;\n-- Returns: 4130.137 km ✅\n```\n\n**Accuracy comparison**:\n| Method | SF to NYC | Error |\n|--------|-----------|-------|\n| Euclidean (flat) | 55 km | 98.7% wrong |\n| Haversine (sphere) | 4,130 km | ✅ Correct |\n| PostGIS (ellipsoid) | 4,135 km | Most accurate |\n\n---\n\n## Production Checklist\n\n```\n□ PostGIS extension installed and spatial indexes created\n□ All coordinates stored in consistent SRID (recommend: 4326)\n□ GeoJSON files optimized (&lt;1MB) or served as vector tiles\n□ Coordinate transformations use ST_Transform, not manual math\n□ Distance calculations use ST_Distance with GEOGRAPHY type\n□ Bounding box queries use ST_MakeEnvelope + ST_Intersects\n□ Large geometries chunked (not &gt;100KB per feature)\n□ Map tiles pre-generated for common zoom levels\n□ CORS configured for tile servers\n□ Rate limiting on geocoding\u002Freverse geocoding endpoints\n```\n\n---\n\n## When to Use vs Avoid\n\n| Scenario | Appropriate? |\n|----------|--------------|\n| Drone imagery annotation and search | ✅ Yes - process survey data |\n| GPS track visualization | ✅ Yes - optimize paths |\n| Find nearest coffee shops | ✅ Yes - spatial queries |\n| Jurisdiction boundary lookups | ✅ Yes - point-in-polygon |\n| Simple address autocomplete | ❌ No - use Mapbox\u002FGoogle |\n| Embed static map on page | ❌ No - use Static API |\n| Geocode single address | ❌ No - use geocoding API |\n\n---\n\n## References\n\n- `\u002Freferences\u002Fcoordinate-systems.md` - EPSG codes, transformations, Web Mercator vs WGS84\n- `\u002Freferences\u002Fpostgis-guide.md` - PostGIS setup, spatial indexes, common queries\n- `\u002Freferences\u002Fgeojson-optimization.md` - Simplification, chunking, vector tiles\n\n## Scripts\n\n- `scripts\u002Fgeospatial_processor.ts` - Process drone imagery, GPS tracks, GeoJSON validation\n- `scripts\u002Ftile_generator.ts` - Generate vector tiles (MBTiles\u002FPMTiles) from GeoJSON\n\n---\n\n**This skill guides**: Geospatial data | PostGIS | GeoJSON | Map tiles | Coordinate systems | Drone data processing | Spatial queries\n","# Geospatial Data Pipeline\n\nExpert in processing, optimizing, and visualizing geospatial data at scale.\n\n## When to Use\n\n✅ **Use for**:\n- Drone imagery processing and annotation\n- GPS track analysis and visualization\n- Location-based search (find nearby X)\n- Map tile generation for web\u002Fmobile\n- Coordinate system transformations\n- Geofencing and spatial queries\n- GeoJSON optimization for web\n\n❌ **NOT for**:\n- Simple address validation (use address APIs)\n- Basic distance calculations (use Haversine formula)\n- Static map embeds (use Mapbox Static API)\n- Geocoding (use Nominatim or Google Geocoding API)\n\n---\n\n## Technology Selection\n\n### Database: PostGIS vs MongoDB Geospatial\n\n| Feature | PostGIS | MongoDB |\n|---------|---------|---------|\n| Spatial indexes | GiST, SP-GiST | 2dsphere |\n| Query language | SQL + spatial functions | Aggregation pipeline |\n| Geometry types | 20+ (full OGC support) | Basic (Point, Line, Polygon) |\n| Coordinate systems | 6000+ via EPSG | WGS84 only |\n| Performance (10M points) | &lt;100ms | &lt;200ms |\n| Best for | Complex spatial analysis | Document-centric apps |\n\n**Timeline**:\n- 2005: PostGIS 1.0 released\n- 2012: MongoDB adds geospatial indexes\n- 2020: PostGIS 3.0 with improved performance\n- 2024: PostGIS remains gold standard for GIS workloads\n\n---\n\n## Common Anti-Patterns\n\n### Anti-Pattern 1: Storing Coordinates as Strings\n\n**Novice thinking**: \"I'll just store lat\u002Flon as text, it's simple\"\n\n**Problem**: Can't use spatial indexes, queries are slow, no validation.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ String storage, no spatial features\ninterface Location {\n  id: string;\n  name: string;\n  latitude: string;   \u002F\u002F \"37.7749\"\n  longitude: string;  \u002F\u002F \"-122.4194\"\n}\n\n\u002F\u002F Linear scan for \"nearby\" queries\nasync function findNearby(lat: string, lon: string): Promise\u003CLocation[]> {\n  const all = await db.locations.findAll();\n\n  return all.filter(loc => {\n    const distance = calculateDistance(\n      parseFloat(lat),\n      parseFloat(lon),\n      parseFloat(loc.latitude),\n      parseFloat(loc.longitude)\n    );\n    return distance \u003C 5000; \u002F\u002F 5km\n  });\n}\n```\n\n**Why wrong**: O(N) linear scan, no spatial index, string parsing overhead.\n\n**Correct approach**:\n```typescript\n\u002F\u002F ✅ PostGIS GEOGRAPHY type with spatial index\nCREATE TABLE locations (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255),\n  location GEOGRAPHY(POINT, 4326)  -- WGS84 coordinates\n);\n\n-- Spatial index (GiST)\nCREATE INDEX idx_locations_geography ON locations USING GIST(location);\n\n-- TypeScript query\nasync function findNearby(lat: number, lon: number, radiusMeters: number): Promise\u003CLocation[]> {\n  const query = `\n    SELECT id, name, ST_AsGeoJSON(location) as geojson\n    FROM locations\n    WHERE ST_DWithin(\n      location,\n      ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography,\n      $3\n    )\n    ORDER BY location \u003C-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography\n    LIMIT 100\n  `;\n\n  return db.query(query, [lon, lat, radiusMeters]);  \u002F\u002F &lt;10ms with index\n}\n```\n\n**Timeline context**:\n- 2000s: Stored lat\u002Flon as FLOAT columns, did math in app code\n- 2010s: PostGIS adoption, spatial indexes\n- 2024: `GEOGRAPHY` type handles Earth curvature automatically\n\n---\n\n### Anti-Pattern 2: Not Using Spatial Indexes\n\n**Problem**: Proximity queries do full table scans.\n\n**Wrong approach**:\n```sql\n-- ❌ No index, sequential scan\nCREATE TABLE drone_images (\n  id SERIAL PRIMARY KEY,\n  image_url VARCHAR(255),\n  location GEOGRAPHY(POINT, 4326)\n);\n\n-- This query scans ALL rows\nSELECT * FROM drone_images\nWHERE ST_DWithin(\n  location,\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  1000  -- 1km\n);\n```\n\n**EXPLAIN output**: `Seq Scan on drone_images (cost=0.00..1234.56 rows=1 width=123)`\n\n**Correct approach**:\n```sql\n-- ✅ GiST index for spatial queries\nCREATE INDEX idx_drone_images_location ON drone_images USING GIST(location);\n\n-- Same query, now uses index\nSELECT * FROM drone_images\nWHERE ST_DWithin(\n  location,\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  1000\n);\n```\n\n**EXPLAIN output**: `Bitmap Index Scan on idx_drone_images_location (cost=4.30..78.30 rows=50 width=123)`\n\n**Performance impact**: 10M points, 5km radius query\n- Without index: 3.2 seconds (full scan)\n- With GiST index: 12ms (99.6% faster)\n\n---\n\n### Anti-Pattern 3: Mixing Coordinate Systems\n\n**Novice thinking**: \"Coordinates are just numbers, I can mix them\"\n\n**Problem**: Incorrect distances, misaligned map features.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Mixing EPSG:4326 (WGS84) and EPSG:3857 (Web Mercator)\nconst userLocation = {\n  lat: 37.7749,   \u002F\u002F WGS84\n  lon: -122.4194\n};\n\nconst droneImage = {\n  x: -13634876,  \u002F\u002F Web Mercator (EPSG:3857)\n  y: 4545684\n};\n\n\u002F\u002F Comparing apples to oranges!\nconst distance = Math.sqrt(\n  Math.pow(userLocation.lon - droneImage.x, 2) +\n  Math.pow(userLocation.lat - droneImage.y, 2)\n);\n```\n\n**Result**: Wildly incorrect distance (millions of \"units\").\n\n**Correct approach**:\n```sql\n-- ✅ Transform to common coordinate system\nSELECT ST_Distance(\n  ST_Transform(\n    ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326),  -- WGS84\n    3857  -- Transform to Web Mercator\n  ),\n  ST_SetSRID(ST_MakePoint(-13634876, 4545684), 3857)  -- Already Web Mercator\n) AS distance_meters;\n```\n\n**Or better**: Always store in one system (WGS84), transform on display only.\n\n**Timeline**:\n- 2005: Web Mercator (EPSG:3857) introduced by Google Maps\n- 2010: Confusion peaks as apps mix WGS84 data with Web Mercator tiles\n- 2024: Best practice: Store WGS84, transform to 3857 only for tile rendering\n\n---\n\n### Anti-Pattern 4: Loading Huge GeoJSON Files\n\n**Problem**: 50MB GeoJSON file crashes browser.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Load entire file into memory\nconst geoJson = await fetch('\u002Fdrone-survey-data.geojson').then(r => r.json());\n\n\u002F\u002F 50MB of GeoJSON = browser freeze\nmap.addSource('drone-data', {\n  type: 'geojson',\n  data: geoJson  \u002F\u002F All 10,000 polygons loaded at once\n});\n```\n\n**Correct approach 1**: Vector tiles (pre-chunked)\n```typescript\n\u002F\u002F ✅ Serve as vector tiles (MBTiles or PMTiles)\nmap.addSource('drone-data', {\n  type: 'vector',\n  tiles: ['https:\u002F\u002Fapi.example.com\u002Ftiles\u002F{z}\u002F{x}\u002F{y}.pbf'],\n  minzoom: 10,\n  maxzoom: 18\n});\n\n\u002F\u002F Browser only loads visible tiles\n```\n\n**Correct approach 2**: GeoJSON simplification + chunking\n```bash\n# Simplify geometry (reduce points)\nnpm install -g @mapbox\u002Fgeojson-precision\ngeojson-precision -p 5 input.geojson output.geojson\n\n# Split into tiles\nnpm install -g geojson-vt\n# Generate tiles programmatically (see scripts\u002Ftile_generator.ts)\n```\n\n**Correct approach 3**: Server-side filtering\n```typescript\n\u002F\u002F ✅ Only fetch visible bounds\nasync function fetchVisibleFeatures(bounds: Bounds): Promise\u003CGeoJSON> {\n  const response = await fetch(\n    `\u002Fapi\u002Ffeatures?bbox=${bounds.west},${bounds.south},${bounds.east},${bounds.north}`\n  );\n  return response.json();\n}\n\nmap.on('moveend', async () => {\n  const bounds = map.getBounds();\n  const geojson = await fetchVisibleFeatures(bounds);\n  map.getSource('dynamic-data').setData(geojson);\n});\n```\n\n---\n\n### Anti-Pattern 5: Euclidean Distance on Spherical Earth\n\n**Novice thinking**: \"Distance is just Pythagorean theorem\"\n\n**Problem**: Incorrect at scale, worse near poles.\n\n**Wrong approach**:\n```typescript\n\u002F\u002F ❌ Flat Earth distance (wrong!)\nfunction distanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number {\n  const dx = lon2 - lon1;\n  const dy = lat2 - lat1;\n\n  return Math.sqrt(dx * dx + dy * dy) * 111.32;  \u002F\u002F 111.32 km\u002Fdegree (WRONG)\n}\n\n\u002F\u002F Example: San Francisco to New York\nconst distance = distanceKm(37.7749, -122.4194, 40.7128, -74.0060);\n\u002F\u002F Returns: ~55 km (WRONG! Actual: ~4,130 km)\n```\n\n**Why wrong**: Earth is a sphere, not a flat plane.\n\n**Correct approach 1**: Haversine formula (great circle distance)\n```typescript\n\u002F\u002F ✅ Haversine formula (spherical Earth)\nfunction haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number {\n  const R = 6371; \u002F\u002F Earth radius in km\n\n  const dLat = toRadians(lat2 - lat1);\n  const dLon = toRadians(lon2 - lon1);\n\n  const a =\n    Math.sin(dLat \u002F 2) * Math.sin(dLat \u002F 2) +\n    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *\n    Math.sin(dLon \u002F 2) * Math.sin(dLon \u002F 2);\n\n  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n  return R * c;\n}\n\n\u002F\u002F San Francisco to New York\nconst distance = haversineKm(37.7749, -122.4194, 40.7128, -74.0060);\n\u002F\u002F Returns: ~4,130 km ✅\n```\n\n**Correct approach 2**: PostGIS (handles curvature automatically)\n```sql\n-- ✅ PostGIS ST_Distance with GEOGRAPHY\nSELECT ST_Distance(\n  ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography,\n  ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography\n) \u002F 1000 AS distance_km;\n-- Returns: 4130.137 km ✅\n```\n\n**Accuracy comparison**:\n| Method | SF to NYC | Error |\n|--------|-----------|-------|\n| Euclidean (flat) | 55 km | 98.7% wrong |\n| Haversine (sphere) | 4,130 km | ✅ Correct |\n| PostGIS (ellipsoid) | 4,135 km | Most accurate |\n\n---\n\n## Production Checklist\n\n```\n□ PostGIS extension installed and spatial indexes created\n□ All coordinates stored in consistent SRID (recommend: 4326)\n□ GeoJSON files optimized (&lt;1MB) or served as vector tiles\n□ Coordinate transformations use ST_Transform, not manual math\n□ Distance calculations use ST_Distance with GEOGRAPHY type\n□ Bounding box queries use ST_MakeEnvelope + ST_Intersects\n□ Large geometries chunked (not &gt;100KB per feature)\n□ Map tiles pre-generated for common zoom levels\n□ CORS configured for tile servers\n□ Rate limiting on geocoding\u002Freverse geocoding endpoints\n```\n\n---\n\n## When to Use vs Avoid\n\n| Scenario | Appropriate? |\n|----------|--------------|\n| Drone imagery annotation and search | ✅ Yes - process survey data |\n| GPS track visualization | ✅ Yes - optimize paths |\n| Find nearest coffee shops | ✅ Yes - spatial queries |\n| Jurisdiction boundary lookups | ✅ Yes - point-in-polygon |\n| Simple address autocomplete | ❌ No - use Mapbox\u002FGoogle |\n| Embed static map on page | ❌ No - use Static API |\n| Geocode single address | ❌ No - use geocoding API |\n\n---\n\n## References\n\n- `\u002Freferences\u002Fcoordinate-systems.md` - EPSG codes, transformations, Web Mercator vs WGS84\n- `\u002Freferences\u002Fpostgis-guide.md` - PostGIS setup, spatial indexes, common queries\n- `\u002Freferences\u002Fgeojson-optimization.md` - Simplification, chunking, vector tiles\n\n## Scripts\n\n- `scripts\u002Fgeospatial_processor.ts` - Process drone imagery, GPS tracks, GeoJSON validation\n- `scripts\u002Ftile_generator.ts` - Generate vector tiles (MBTiles\u002FPMTiles) from GeoJSON\n\n---\n\n**This skill guides**: Geospatial data | PostGIS | GeoJSON | Map tiles | Coordinate systems | Drone data processing | Spatial queries\n",{"name":5,"metadata":53,"description":7,"allowed-tools":71},{"tags":54,"category":57,"pairs-with":58},[31,30,28,55,56],"gis","postgis","Data & Analytics",[59,62,65,68],{"skill":60,"reason":61},"large-scale-map-visualization","Geospatial pipelines produce the optimized data that map visualizations render",{"skill":63,"reason":64},"computer-vision-pipeline","Drone imagery processing combines CV object detection with geospatial coordinates",{"skill":66,"reason":67},"drone-inspection-specialist","Infrastructure inspection data flows through geospatial pipelines for analysis",{"skill":69,"reason":70},"data-pipeline-engineer","Geospatial ETL shares batching, streaming, and transformation patterns with general data pipelines","Read,Write,Edit,Bash(npm:*,gdal*,postgres*)","main","f2ec4b3b9133d34eeaf9b38e4b0a97e8e5705a7f","benchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline",0,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard","https:\u002F\u002Fgithub.com\u002FJSGforever","2026-08-29 18:27:56",{"source":8,"skillName":5,"commands":80,"prompt":90,"zipAvailable":91},[81,84,87],{"tool":82,"command":83},"npx","npx skills add JSGforever\u002Fskillguard --skill geospatial-data-pipeline",{"tool":85,"command":86},"bunx","bunx skills add JSGforever\u002Fskillguard --skill geospatial-data-pipeline",{"tool":88,"command":89},"pnpm","pnpm dlx skills add JSGforever\u002Fskillguard --skill geospatial-data-pipeline","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002FSKILL.md",true,[93,99,103,107,113],{"path":94,"resourceType":95,"resourceTypeDesc":96,"sizeBytes":97,"sourceHtmlUrl":98},"references\u002Fcoordinate-systems.md","REFERENCE","参考文档",9968,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002Freferences\u002Fcoordinate-systems.md",{"path":100,"resourceType":95,"resourceTypeDesc":96,"sizeBytes":101,"sourceHtmlUrl":102},"references\u002Fgeojson-optimization.md",9544,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002Freferences\u002Fgeojson-optimization.md",{"path":104,"resourceType":95,"resourceTypeDesc":96,"sizeBytes":105,"sourceHtmlUrl":106},"references\u002Fpostgis-guide.md",11519,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002Freferences\u002Fpostgis-guide.md",{"path":108,"resourceType":109,"resourceTypeDesc":110,"sizeBytes":111,"sourceHtmlUrl":112},"scripts\u002Fgeospatial_processor.ts","SCRIPT","脚本",17329,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002Fscripts\u002Fgeospatial_processor.ts",{"path":114,"resourceType":109,"resourceTypeDesc":110,"sizeBytes":115,"sourceHtmlUrl":116},"scripts\u002Ftile_generator.ts",12764,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--geospatial-data-pipeline\u002Fscripts\u002Ftile_generator.ts",[118,148,159,183,204,224],{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":127,"publishedAt":48},"01M71HY2SNXBA9Q00WZDTZQ31Z","skillguard-codex-scanner","Hybrid deterministic+LLM agent-skill security scanner. Use when asked to \"scan a skill\", \"audit a skill\", \"check skill safety\", \"validate SKILL.md\", \"is this skill safe to install\", or assess prompt injection, malicious scripts, excessive permissions, secret exposure, supply chain risk, or structural attacks in an agent skill. Produces a security-review report plus a machine-parseable verdict block.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fsrc\u002Fskillguard\u002Fstatic_scanner\u002Fskillguard-codex-scanner\u002FSKILL.md",[124],{"categoryNo":125,"categoryName":126,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147],"scanner","skillguard codex scanner","skillguard","codex","chain","skill security","security-review","review report","structural","secret","prompt injection","security scanner","when","review","malicious","permissions","exposure","audit","scripts","safe",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":152,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":156,"publishedAt":48},"01M71HY0SPKTT3S27BC2HPAC5M","data-exfiltrator","Analyzes data files","MIT","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Fsmoke-one-skill\u002Fasset1__cisco-ai-defense--data-exfiltrator\u002FSKILL.md",[155],{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},[30,157,158],"data exfiltrator","files",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":165,"publishedAt":48},"01M71HXVS9EMN44BGDW215FZ72","real-time-collaboration-engine","Build real-time collaborative editing with WebSockets, OT\u002FCRDT conflict resolution, and presence awareness. Implements cursor tracking, optimistic updates, and offline sync. Use for collaborative editors, whiteboards, video editing. Activate on \"real-time collaboration\", \"WebSocket sync\", \"multiplayer editing\", \"CRDT\", \"presence awareness\". NOT for simple chat, request-response APIs, or single-user apps.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--real-time-collaboration-engine\u002FSKILL.md",[],[166,167,168,169,170,171,172,173,174,175,176,177,178,37,179,36,33,180,181,182],"time","real time collaboration engine","real time collaboration","collaboration","engine","real time","Video","reliability","video editing","performance","conflict resolution","presence","access","updates","optimizer","react","streaming",{"skillNo":184,"name":185,"nameZh":6,"description":186,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":187,"categories":188,"keywords":192,"publishedAt":48},"01M71HXP6CTY4KFN0QVMMN0V7J","react-performance-optimizer","Optimize React apps for 60fps performance. Implements memoization, virtualization, code splitting, bundle optimization. Use for slow renders, large lists, bundle bloat. Activate on \"React performance\", \"slow render\", \"useMemo\", \"bundle size\", \"virtualization\". NOT for backend optimization, non-React frameworks, or premature optimization.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--react-performance-optimizer\u002FSKILL.md",[189],{"categoryNo":190,"categoryName":191,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发",[175,193,180,181,194,195,196,197,198,199,166,41,200,37,36,201,33,182,202,203],"react performance optimizer","react performance","performance optimizer","app","nextjs","expert","re","2025","code","router","strategies",{"skillNo":205,"name":206,"nameZh":6,"description":207,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":208,"categories":209,"keywords":211,"publishedAt":48},"01M71HXH7QXSQB4QTDZ7SP7YXQ","llm-streaming-response-handler","Build production LLM streaming UIs with Server-Sent Events, real-time token display, cancellation, error recovery. Handles OpenAI\u002FAnthropic\u002FClaude streaming APIs. Use for chatbots, AI assistants, real-time text generation. Activate on \"LLM streaming\", \"SSE\", \"token stream\", \"chat UI\", \"real-time AI\". NOT for batch processing, non-streaming APIs, or WebSocket bidirectional chat.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--llm-streaming-response-handler\u002FSKILL.md",[210],{"categoryNo":190,"categoryName":191,"taxonomyType":20,"taxonomyTypeDesc":21},[212,182,213,214,45,215,175,166,216,217,218,36,33,180,219,181,220,221,222,223],"llm streaming response handler","handler","error","retry","chat ui","error recovery","critical","batch","display","text","server","read",{"skillNo":225,"name":226,"nameZh":6,"description":227,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":228,"categories":229,"keywords":237,"publishedAt":48},"01M71HXCB0E1YRKYW1Y8T0Y106","github-actions-pipeline-builder","Build production CI\u002FCD pipelines with GitHub Actions. Implements matrix builds, caching, deployments, testing, security scanning. Use for automated testing, deployments, release workflows. Activate on \"GitHub Actions\", \"CI\u002FCD\", \"workflow\", \"deployment pipeline\", \"automated testing\". NOT for Jenkins\u002FCircleCI, manual deployments, or non-GitHub repositories.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--github-actions-pipeline-builder\u002FSKILL.md",[230,233,236],{"categoryNo":231,"categoryName":232,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":234,"categoryName":235,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":125,"categoryName":126,"taxonomyType":20,"taxonomyTypeDesc":21},[238,239,240,241,242,243,28,173,198,244,245,246,33,203,247,223,248,249,250,251],"github actions pipeline builder","actions","github actions","pipeline builder","github","builder","testing","ci","workflows","run","release","write","edit","git"]