/** * Simplified Vietnam mainland polygon for "is this point in VN?" tests. * * The country has a very long, narrow shape (>1500 km north-south) with * neighbours pressing in on the western and northern borders. Bbox-based * filtering (the chunks the Overpass sync uses) inevitably catches some * `landuse=industrial` polygons that sit just across the border in Laos, * Cambodia, Thailand and southern China. * * The polygon below is a hand-traced ~30-vertex outline that follows the * official border closely enough for a point-in-polygon test. It's not * survey-grade — coastal islands are clipped, and the Mekong delta tip * is rounded — but it's sufficient to reject industrial sites that are * clearly not in VN. Where edge cases exist (a row landing in the * 1-2 km buffer near a border crossing), admin can promote / unlock by * hand from the OSM review queue. * * Format: GeoJSON Polygon, coordinates as `[lng, lat]` pairs (per the * spec). The ring is closed (first === last). */ import type { Polygon } from 'geojson'; export const VN_COUNTRY_POLYGON: Polygon = { type: 'Polygon', coordinates: [ [ // Northern border, west → east. The northern edge is the actual // China border line; we trace it loosely. [102.14, 22.47], // Lai Châu / China junction [103.0, 22.78], // Lào Cai [104.0, 22.82], // northern Hà Giang [105.32, 23.39], // Đồng Văn (northernmost point) [106.55, 22.95], // Cao Bằng [107.0, 22.34], // Lạng Sơn [108.05, 21.55], // Móng Cái / Quảng Ninh // Eastern boundary at 110°E — generous on the sea side so that // every coastal industrial zone (Vũng Áng / Formosa, Dung Quất, // Nhơn Hội, Vũng Tàu, Long Sơn etc.) sits inside. This omits the // Hoàng Sa / Trường Sa archipelagos — fine, they have no KCN. [110.0, 21.0], [110.0, 18.0], [110.0, 15.0], [110.0, 12.0], [110.0, 9.5], // Mekong delta — Cà Mau cape, then Hà Tiên (south-west tip). [105.5, 8.4], // south of Cà Mau [104.83, 8.59], // Cà Mau [104.45, 10.39], // Hà Tiên // West / south-west, climbing along the Cambodia + Laos borders. [105.0, 10.78], // Châu Đốc [105.85, 11.38], // Tây Ninh [106.0, 11.7], [106.6, 11.95], // Lộc Ninh [107.55, 12.36], // Bù Đăng [107.55, 14.42], // Kon Tum [107.32, 16.0], // A Lưới [106.5, 16.45], // Hướng Hóa [105.97, 17.69], // Quảng Bình border [105.18, 18.66], // Hà Tĩnh / Laos border [104.34, 19.7], // Nghệ An / Laos [103.95, 20.66], // Mai Châu [103.05, 21.13], // Sơn La / Laos [102.78, 21.91], // Điện Biên [102.14, 22.47], // close ring ], ], }; /** GeoJSON string ready to feed to PostGIS `ST_GeomFromGeoJSON`. */ export const VN_COUNTRY_POLYGON_GEOJSON = JSON.stringify(VN_COUNTRY_POLYGON); /** * Pure-JS point-in-polygon test using the standard ray-casting algorithm. * Avoids pulling in `@turf/boolean-point-in-polygon` for the sync script * (one fewer dep, and we only have one polygon to test against). */ export function isPointInVietnam(lng: number, lat: number): boolean { const ring = VN_COUNTRY_POLYGON.coordinates[0]; let inside = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { const xi = ring[i][0]; const yi = ring[i][1]; const xj = ring[j][0]; const yj = ring[j][1]; const intersect = yi > lat !== yj > lat && lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; }