photoprism-client-go/vendor/github.com/paulmach/go.geojson/geometry.go

348 lines
9.2 KiB
Go
Raw Normal View History

package geojson
import (
"encoding/json"
"errors"
"fmt"
)
// A GeometryType serves to enumerate the different GeoJSON geometry types.
type GeometryType string
// The geometry types supported by GeoJSON 1.0
const (
GeometryPoint GeometryType = "Point"
GeometryMultiPoint GeometryType = "MultiPoint"
GeometryLineString GeometryType = "LineString"
GeometryMultiLineString GeometryType = "MultiLineString"
GeometryPolygon GeometryType = "Polygon"
GeometryMultiPolygon GeometryType = "MultiPolygon"
GeometryCollection GeometryType = "GeometryCollection"
)
// A Geometry correlates to a GeoJSON geometry object.
type Geometry struct {
Type GeometryType `json:"type"`
BoundingBox []float64 `json:"bbox,omitempty"`
Point []float64
MultiPoint [][]float64
LineString [][]float64
MultiLineString [][][]float64
Polygon [][][]float64
MultiPolygon [][][][]float64
Geometries []*Geometry
CRS map[string]interface{} `json:"crs,omitempty"` // Coordinate Reference System Objects are not currently supported
}
// NewPointGeometry creates and initializes a point geometry with the give coordinate.
func NewPointGeometry(coordinate []float64) *Geometry {
return &Geometry{
Type: GeometryPoint,
Point: coordinate,
}
}
// NewMultiPointGeometry creates and initializes a multi-point geometry with the given coordinates.
func NewMultiPointGeometry(coordinates ...[]float64) *Geometry {
return &Geometry{
Type: GeometryMultiPoint,
MultiPoint: coordinates,
}
}
// NewLineStringGeometry creates and initializes a line string geometry with the given coordinates.
func NewLineStringGeometry(coordinates [][]float64) *Geometry {
return &Geometry{
Type: GeometryLineString,
LineString: coordinates,
}
}
// NewMultiLineStringGeometry creates and initializes a multi-line string geometry with the given lines.
func NewMultiLineStringGeometry(lines ...[][]float64) *Geometry {
return &Geometry{
Type: GeometryMultiLineString,
MultiLineString: lines,
}
}
// NewPolygonGeometry creates and initializes a polygon geometry with the given polygon.
func NewPolygonGeometry(polygon [][][]float64) *Geometry {
return &Geometry{
Type: GeometryPolygon,
Polygon: polygon,
}
}
// NewMultiPolygonGeometry creates and initializes a multi-polygon geometry with the given polygons.
func NewMultiPolygonGeometry(polygons ...[][][]float64) *Geometry {
return &Geometry{
Type: GeometryMultiPolygon,
MultiPolygon: polygons,
}
}
// NewCollectionGeometry creates and initializes a geometry collection geometry with the given geometries.
func NewCollectionGeometry(geometries ...*Geometry) *Geometry {
return &Geometry{
Type: GeometryCollection,
Geometries: geometries,
}
}
// MarshalJSON converts the geometry object into the correct JSON.
// This fulfills the json.Marshaler interface.
func (g Geometry) MarshalJSON() ([]byte, error) {
// defining a struct here lets us define the order of the JSON elements.
type geometry struct {
Type GeometryType `json:"type"`
BoundingBox []float64 `json:"bbox,omitempty"`
Coordinates interface{} `json:"coordinates,omitempty"`
Geometries interface{} `json:"geometries,omitempty"`
CRS map[string]interface{} `json:"crs,omitempty"`
}
geo := &geometry{
Type: g.Type,
}
if g.BoundingBox != nil && len(g.BoundingBox) != 0 {
geo.BoundingBox = g.BoundingBox
}
switch g.Type {
case GeometryPoint:
geo.Coordinates = g.Point
case GeometryMultiPoint:
geo.Coordinates = g.MultiPoint
case GeometryLineString:
geo.Coordinates = g.LineString
case GeometryMultiLineString:
geo.Coordinates = g.MultiLineString
case GeometryPolygon:
geo.Coordinates = g.Polygon
case GeometryMultiPolygon:
geo.Coordinates = g.MultiPolygon
case GeometryCollection:
geo.Geometries = g.Geometries
}
return json.Marshal(geo)
}
// UnmarshalGeometry decodes the data into a GeoJSON geometry.
// Alternately one can call json.Unmarshal(g) directly for the same result.
func UnmarshalGeometry(data []byte) (*Geometry, error) {
g := &Geometry{}
err := json.Unmarshal(data, g)
if err != nil {
return nil, err
}
return g, nil
}
// UnmarshalJSON decodes the data into a GeoJSON geometry.
// This fulfills the json.Unmarshaler interface.
func (g *Geometry) UnmarshalJSON(data []byte) error {
var object map[string]interface{}
err := json.Unmarshal(data, &object)
if err != nil {
return err
}
return decodeGeometry(g, object)
}
// Scan implements the sql.Scanner interface allowing
// geometry structs to be passed into rows.Scan(...interface{})
// The columns must be received as GeoJSON Geometry.
// When using PostGIS a spatial column would need to be wrapped in ST_AsGeoJSON.
func (g *Geometry) Scan(value interface{}) error {
var data []byte
switch value.(type) {
case string:
data = []byte(value.(string))
case []byte:
data = value.([]byte)
default:
return errors.New("unable to parse this type into geojson")
}
return g.UnmarshalJSON(data)
}
func decodeGeometry(g *Geometry, object map[string]interface{}) error {
t, ok := object["type"]
if !ok {
return errors.New("type property not defined")
}
if s, ok := t.(string); ok {
g.Type = GeometryType(s)
} else {
return errors.New("type property not string")
}
bb, err := decodeBoundingBox(object["bbox"])
if err != nil {
return err
}
g.BoundingBox = bb
switch g.Type {
case GeometryPoint:
g.Point, err = decodePosition(object["coordinates"])
case GeometryMultiPoint:
g.MultiPoint, err = decodePositionSet(object["coordinates"])
case GeometryLineString:
g.LineString, err = decodePositionSet(object["coordinates"])
case GeometryMultiLineString:
g.MultiLineString, err = decodePathSet(object["coordinates"])
case GeometryPolygon:
g.Polygon, err = decodePathSet(object["coordinates"])
case GeometryMultiPolygon:
g.MultiPolygon, err = decodePolygonSet(object["coordinates"])
case GeometryCollection:
g.Geometries, err = decodeGeometries(object["geometries"])
}
return err
}
func decodePosition(data interface{}) ([]float64, error) {
coords, ok := data.([]interface{})
if !ok {
return nil, fmt.Errorf("not a valid position, got %v", data)
}
result := make([]float64, 0, len(coords))
for _, coord := range coords {
if f, ok := coord.(float64); ok {
result = append(result, f)
} else {
return nil, fmt.Errorf("not a valid coordinate, got %v", coord)
}
}
return result, nil
}
func decodePositionSet(data interface{}) ([][]float64, error) {
points, ok := data.([]interface{})
if !ok {
return nil, fmt.Errorf("not a valid set of positions, got %v", data)
}
result := make([][]float64, 0, len(points))
for _, point := range points {
if p, err := decodePosition(point); err == nil {
result = append(result, p)
} else {
return nil, err
}
}
return result, nil
}
func decodePathSet(data interface{}) ([][][]float64, error) {
sets, ok := data.([]interface{})
if !ok {
return nil, fmt.Errorf("not a valid path, got %v", data)
}
result := make([][][]float64, 0, len(sets))
for _, set := range sets {
if s, err := decodePositionSet(set); err == nil {
result = append(result, s)
} else {
return nil, err
}
}
return result, nil
}
func decodePolygonSet(data interface{}) ([][][][]float64, error) {
polygons, ok := data.([]interface{})
if !ok {
return nil, fmt.Errorf("not a valid polygon, got %v", data)
}
result := make([][][][]float64, 0, len(polygons))
for _, polygon := range polygons {
if p, err := decodePathSet(polygon); err == nil {
result = append(result, p)
} else {
return nil, err
}
}
return result, nil
}
func decodeGeometries(data interface{}) ([]*Geometry, error) {
if vs, ok := data.([]interface{}); ok {
geometries := make([]*Geometry, 0, len(vs))
for _, v := range vs {
g := &Geometry{}
vmap, ok := v.(map[string]interface{})
if !ok {
break
}
err := decodeGeometry(g, vmap)
if err != nil {
return nil, err
}
geometries = append(geometries, g)
}
if len(geometries) == len(vs) {
return geometries, nil
}
}
return nil, fmt.Errorf("not a valid set of geometries, got %v", data)
}
// IsPoint returns true with the geometry object is a Point type.
func (g *Geometry) IsPoint() bool {
return g.Type == GeometryPoint
}
// IsMultiPoint returns true with the geometry object is a MultiPoint type.
func (g *Geometry) IsMultiPoint() bool {
return g.Type == GeometryMultiPoint
}
// IsLineString returns true with the geometry object is a LineString type.
func (g *Geometry) IsLineString() bool {
return g.Type == GeometryLineString
}
// IsMultiLineString returns true with the geometry object is a LineString type.
func (g *Geometry) IsMultiLineString() bool {
return g.Type == GeometryMultiLineString
}
// IsPolygon returns true with the geometry object is a Polygon type.
func (g *Geometry) IsPolygon() bool {
return g.Type == GeometryPolygon
}
// IsMultiPolygon returns true with the geometry object is a MultiPolygon type.
func (g *Geometry) IsMultiPolygon() bool {
return g.Type == GeometryMultiPolygon
}
// IsCollection returns true with the geometry object is a GeometryCollection type.
func (g *Geometry) IsCollection() bool {
return g.Type == GeometryCollection
}