func (c GeometryCollection) ForceCCW() GeometryCollection {
	return c.forceOrientation(false)
}
ForceCCW returns the equivalent GeometryCollection that has its constituent
Polygons and MultiPolygons reoriented in a counter-clockwise direction (i.e.
exterior rings counter-clockwise and interior rings clockwise). Geometries
other that Polygons and MultiPolygons are unchanged.
package geom
import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"unsafe"
)
type GeometryCollection struct {
	geoms []Geometry
	ctype CoordinatesType
}
func NewGeometryCollection(geoms []Geometry, opts ...ConstructorOption) GeometryCollection {
	if len(geoms) == 0 {
		return GeometryCollection{}
	}
	ctype := DimXYZM
	for _, g := range geoms {
		ctype &= g.CoordinatesType()
	}
	geoms = append([]Geometry(nil), geoms...)
	for i := range geoms {
		geoms[i] = geoms[i].ForceCoordinatesType(ctype)
	}
	return GeometryCollection{geoms, ctype}
}
func (c GeometryCollection) Type() GeometryType {
	return TypeGeometryCollection
}
func (c GeometryCollection) AsGeometry() Geometry {
	return Geometry{TypeGeometryCollection, unsafe.Pointer(&c)}
}
func (c GeometryCollection) NumGeometries() int {
	return len(c.geoms)
}
func (c GeometryCollection) NumTotalGeometries() int {
	var n int
	for _, geom := range c.geoms {
		if geom.IsGeometryCollection() {
			n += geom.AsGeometryCollection().NumTotalGeometries()
		}
	}
	return n + c.NumGeometries()
}
func (c GeometryCollection) GeometryN(n int) Geometry {
	return c.geoms[n]
}
func (c GeometryCollection) AsText() string {
	return string(c.AppendWKT(nil))
}
func (c GeometryCollection) AppendWKT(dst []byte) []byte {
	dst = appendWKTHeader(dst, "GEOMETRYCOLLECTION", c.ctype)
	if len(c.geoms) == 0 {
		return appendWKTEmpty(dst)
	}
	dst = append(dst, '(')
	for i, g := range c.geoms {
		if i > 0 {
			dst = append(dst, ',')
		}
		dst = g.AppendWKT(dst)
	}
	return append(dst, ')')
}
func (c GeometryCollection) IsEmpty() bool {
	for _, g := range c.geoms {
		if !g.IsEmpty() {
			return false
		}
	}
	return true
}
func (c GeometryCollection) Dimension() int {
	dim := 0
	for _, g := range c.geoms {
		dim = max(dim, g.Dimension())
	}
	return dim
}
func (c GeometryCollection) walk(fn func(Geometry)) {
	for _, g := range c.geoms {
		if g.IsGeometryCollection() {
			g.AsGeometryCollection().walk(fn)
		} else {
			fn(g)
		}
	}
}
func (c GeometryCollection) Envelope() Envelope {
	var env Envelope
	for _, g := range c.geoms {
		env = env.ExpandToIncludeEnvelope(g.Envelope())
	}
	return env
}
func (c GeometryCollection) Boundary() GeometryCollection {
	if c.IsEmpty() {
		return c
	}
	var bounds []Geometry
	for _, g := range c.geoms {
		bound := g.Boundary().Force2D()
		if !bound.IsEmpty() {
			bounds = append(bounds, bound)
		}
	}
	return GeometryCollection{bounds, DimXY}
}
func (c GeometryCollection) Value() (driver.Value, error) {
	return c.AsBinary(), nil
}
func (c *GeometryCollection) Scan(src interface{}) error {
	return scanAsType(src, c, TypeGeometryCollection)
}
func (c GeometryCollection) AsBinary() []byte {
	return c.AppendWKB(nil)
}
func (c GeometryCollection) AppendWKB(dst []byte) []byte {
	marsh := newWKBMarshaller(dst)
	marsh.writeByteOrder()
	marsh.writeGeomType(TypeGeometryCollection, c.ctype)
	n := c.NumGeometries()
	marsh.writeCount(n)
	for i := 0; i < n; i++ {
		g := c.GeometryN(i)
		marsh.buf = g.AppendWKB(marsh.buf)
	}
	return marsh.buf
}
func (c GeometryCollection) ConvexHull() Geometry {
	return convexHull(c.AsGeometry())
}
func (c GeometryCollection) MarshalJSON() ([]byte, error) {
	buf := []byte(`{"type":"GeometryCollection","geometries":`)
	var geoms = c.geoms
	if geoms == nil {
		geoms = []Geometry{}
	}
	geomsJSON, err := json.Marshal(geoms)
	if err != nil {
		return nil, err
	}
	buf = append(buf, geomsJSON...)
	buf = append(buf, '}')
	return buf, nil
}
func (c GeometryCollection) TransformXY(fn func(XY) XY, opts ...ConstructorOption) (GeometryCollection, error) {
	transformed := make([]Geometry, len(c.geoms))
	for i := range c.geoms {
		var err error
		transformed[i], err = c.geoms[i].TransformXY(fn, opts...)
		if err != nil {
			return GeometryCollection{}, wrapTransformed(err)
		}
	}
	return GeometryCollection{transformed, c.ctype}, nil
}
func (c GeometryCollection) Reverse() GeometryCollection {
	if c.IsEmpty() {
		return c
	}
	var geoms []Geometry
	for n := 0; n < c.NumGeometries(); n++ {
		rev := c.GeometryN(n).Reverse()
		geoms = append(geoms, rev)
	}
	return GeometryCollection{geoms, c.ctype}
}
func (c GeometryCollection) Length() float64 {
	var sum float64
	n := c.NumGeometries()
	for i := 0; i < n; i++ {
		geom := c.GeometryN(i)
		sum += geom.Length()
	}
	return sum
}
func (c GeometryCollection) Area(opts ...AreaOption) float64 {
	var sum float64
	n := c.NumGeometries()
	for i := 0; i < n; i++ {
		g := c.GeometryN(i)
		sum += g.Area(opts...)
	}
	return sum
}
func highestDimensionIgnoreEmpties(g Geometry) int {
	if g.IsEmpty() {
		return 0
	}
	if !g.IsGeometryCollection() {
		return g.Dimension()
	}
	c := g.AsGeometryCollection()
	highestDim := 0
	for _, g2 := range c.geoms {
		highestDim = max(highestDim, highestDimensionIgnoreEmpties(g2))
	}
	return highestDim
}
func (c GeometryCollection) Centroid() Point {
	if c.IsEmpty() {
		return NewEmptyPoint(DimXY)
	}
	switch highestDimensionIgnoreEmpties(c.AsGeometry()) {
	case 0:
		return c.pointCentroid()
	case 1:
		return c.linearCentroid()
	case 2:
		return c.arealCentroid()
	default:
		panic("Invalid dimensionality in centroid calculation.")
	}
}
func (c GeometryCollection) pointCentroid() Point {
	var (
		numPoints int
		sumPoints XY
	)
	c.walk(func(g Geometry) {
		switch {
		case g.IsPoint():
			xy, ok := g.AsPoint().XY()
			if ok {
				numPoints++
				sumPoints = sumPoints.Add(xy)
			}
		case g.IsMultiPoint():
			mp := g.AsMultiPoint()
			for i := 0; i < mp.NumPoints(); i++ {
				xy, ok := mp.PointN(i).XY()
				if ok {
					numPoints++
					sumPoints = sumPoints.Add(xy)
				}
			}
		}
	})
	return sumPoints.Scale(1 / float64(numPoints)).asUncheckedPoint()
}
func (c GeometryCollection) linearCentroid() Point {
	var (
		lengthSum        float64
		weightedCentroid XY
	)
	c.walk(func(g Geometry) {
		switch {
		case g.IsLineString():
			ls := g.AsLineString()
			centroid, ok := ls.Centroid().XY()
			if ok {
				length := ls.Length()
				lengthSum += length
				weightedCentroid = weightedCentroid.Add(centroid.Scale(length))
			}
		case g.IsMultiLineString():
			mls := g.AsMultiLineString()
			for i := 0; i < mls.NumLineStrings(); i++ {
				ls := mls.LineStringN(i)
				centroid, ok := ls.Centroid().XY()
				if ok {
					length := ls.Length()
					lengthSum += length
					weightedCentroid = weightedCentroid.Add(centroid.Scale(length))
				}
			}
		}
	})
	return weightedCentroid.Scale(1 / lengthSum).asUncheckedPoint()
}
func (c GeometryCollection) arealCentroid() Point {
	var (
		areas   []float64
		areaSum float64
	)
	c.walk(func(g Geometry) {
		area := g.Area()
		areas = append(areas, area)
		areaSum += area
	})
	var weightedCentroid XY
	c.walk(func(g Geometry) {
		area := areas[0]
		areas = areas[1:]
		centroid, ok := g.Centroid().XY()
		if ok {
			weightedCentroid = weightedCentroid.Add(
				centroid.Scale(area / areaSum))
		}
	})
	return weightedCentroid.asUncheckedPoint()
}
func (c GeometryCollection) CoordinatesType() CoordinatesType {
	return c.ctype
}
func (c GeometryCollection) ForceCoordinatesType(newCType CoordinatesType) GeometryCollection {
	gs := make([]Geometry, len(c.geoms))
	for i := range c.geoms {
		gs[i] = c.geoms[i].ForceCoordinatesType(newCType)
	}
	return GeometryCollection{gs, newCType}
}
func (c GeometryCollection) Force2D() GeometryCollection {
	return c.ForceCoordinatesType(DimXY)
}
func (c GeometryCollection) PointOnSurface() Point {
	var maxDim int
	c.walk(func(g Geometry) {
		if !g.IsEmpty() {
			maxDim = max(maxDim, g.Dimension())
		}
	})
	nearest := newNearestPointAccumulator(c.Centroid())
	c.walk(func(g Geometry) {
		if g.Dimension() == maxDim {
			nearest.consider(g.PointOnSurface())
		}
	})
	return nearest.point
}
func (c GeometryCollection) ForceCW() GeometryCollection {
	return c.forceOrientation(true)
}