package geo import ( "encoding/json" "fmt" ) type geometry struct { Type string `json:"type"` Coordinates json.RawMessage `json:"coordinates"` } // ContainsPoint reports whether a GeoJSON Polygon or MultiPolygon contains p. // GeoJSON coordinate order is [longitude, latitude]. func ContainsPoint(raw []byte, p Point) (bool, error) { if len(raw) == 0 { return false, fmt.Errorf("geojson geometry is empty") } var geom geometry if err := json.Unmarshal(raw, &geom); err != nil { return false, fmt.Errorf("decode geojson geometry: %w", err) } switch geom.Type { case "Polygon": polygon, err := decodePolygon(geom.Coordinates) if err != nil { return false, fmt.Errorf("decode polygon: %w", err) } return polygonContainsPoint(polygon, p), nil case "MultiPolygon": multiPolygon, err := decodeMultiPolygon(geom.Coordinates) if err != nil { return false, fmt.Errorf("decode multipolygon: %w", err) } for _, polygon := range multiPolygon { if polygonContainsPoint(polygon, p) { return true, nil } } return false, nil case "": return false, fmt.Errorf("geojson geometry type is required") default: return false, fmt.Errorf("unsupported geojson geometry type %q", geom.Type) } } func decodePolygon(raw json.RawMessage) (Polygon, error) { var coords [][][]float64 if err := json.Unmarshal(raw, &coords); err != nil { return nil, err } return polygonFromCoordinates(coords) } func decodeMultiPolygon(raw json.RawMessage) ([]Polygon, error) { var coords [][][][]float64 if err := json.Unmarshal(raw, &coords); err != nil { return nil, err } if len(coords) == 0 { return nil, fmt.Errorf("multipolygon has no polygons") } out := make([]Polygon, 0, len(coords)) for i, polygonCoords := range coords { polygon, err := polygonFromCoordinates(polygonCoords) if err != nil { return nil, fmt.Errorf("polygons[%d]: %w", i, err) } out = append(out, polygon) } return out, nil } func polygonFromCoordinates(coords [][][]float64) (Polygon, error) { if len(coords) == 0 { return nil, fmt.Errorf("polygon has no rings") } polygon := make(Polygon, 0, len(coords)) for i, ringCoords := range coords { ring, err := ringFromCoordinates(ringCoords) if err != nil { return nil, fmt.Errorf("rings[%d]: %w", i, err) } polygon = append(polygon, ring) } return polygon, nil } func ringFromCoordinates(coords [][]float64) (Ring, error) { if len(coords) == 0 { return nil, fmt.Errorf("ring has no points") } ring := make(Ring, 0, len(coords)) for i, pair := range coords { if len(pair) < 2 { return nil, fmt.Errorf("points[%d] has %d values, need longitude and latitude", i, len(pair)) } ring = append(ring, Point{Longitude: pair[0], Latitude: pair[1]}) } return ring, nil }