package numerics import "math" // PointInPolygon reports whether (lat, lng) lies inside the closed polygon // whose vertices are given as parallel latitude/longitude slices (degrees). // // The test is ray casting in plate-carrée space. Every longitude is // normalised to within 180° of the first vertex before testing, so a polygon // spanning the antimeridian is handled correctly as long as it spans no more // than 180° in longitude. polyLat and polyLng must have equal length >= 3. func PointInPolygon(lat, lng float64, polyLat, polyLng []float64) bool { n := len(polyLat) if n < 3 || len(polyLng) != n { return false } ref := polyLng[0] qx := NormalizeLng(lng, ref) inside := false for i, j := 0, n-1; i < n; j, i = i, i+1 { yi, yj := polyLat[i], polyLat[j] xi := NormalizeLng(polyLng[i], ref) xj := NormalizeLng(polyLng[j], ref) if (yi > lat) != (yj > lat) { xIntersect := (xj-xi)*(lat-yi)/(yj-yi) + xi if qx < xIntersect { inside = !inside } } } return inside } // NormalizeLng rewrites v so that it lies within 180° of ref. For example, // NormalizeLng(350, 10) returns -10. Used to make longitude comparisons // continuous across the antimeridian. func NormalizeLng(v, ref float64) float64 { return ref + math.Mod(v-ref+540, 360) - 180 }