func (p Polygon) Centroid() Point {
	if p.IsEmpty() {
		return NewEmptyPoint(DimXY)
	}
	areas := make([]float64, 1+p.NumInteriorRings())
	areas[0] = math.Abs(signedAreaOfLinearRing(p.ExteriorRing(), nil))
	sumAreas := areas[0]
	for i := 0; i < p.NumInteriorRings(); i++ {
		areas[i+1] = -math.Abs(signedAreaOfLinearRing(p.InteriorRingN(i), nil))
		sumAreas += areas[i+1]
	}
	centroid := weightedCentroid(p.ExteriorRing(), areas[0], sumAreas)
	for i := 0; i < p.NumInteriorRings(); i++ {
		centroid = centroid.Add(
			weightedCentroid(p.InteriorRingN(i), areas[i+1], sumAreas))
	}
	return centroid.asUncheckedPoint()
}
Centroid returns the polygon's centroid point. If returns an empty Point if
the Polygon is empty.
package geom
import (
	"database/sql/driver"
	"fmt"
	"math"
	"unsafe"
	"github.com/peterstace/simplefeatures/rtree"
)
type Polygon struct {
	rings []LineString
	ctype CoordinatesType
}
func NewPolygon(rings []LineString, opts ...ConstructorOption) (Polygon, error) {
	if len(rings) == 0 {
		return Polygon{}, nil
	}
	ctype := DimXYZM
	for _, r := range rings {
		ctype &= r.CoordinatesType()
	}
	rings = append([]LineString(nil), rings...)
	for i := range rings {
		rings[i] = rings[i].ForceCoordinatesType(ctype)
	}
	ctorOpts := newOptionSet(opts)
	if err := validatePolygon(rings, ctorOpts); err != nil {
		if ctorOpts.omitInvalid {
			return Polygon{}, nil
		}
		return Polygon{}, err
	}
	return Polygon{rings, ctype}, nil
}
func validatePolygon(rings []LineString, opts ctorOptionSet) error {
	if len(rings) == 0 || opts.skipValidations {
		return nil
	}
	for _, r := range rings {
		if !r.IsClosed() {
			return validationError{"polygon ring not closed"}
		}
		if !r.IsSimple() {
			return validationError{"polygon ring not simple"}
		}
	}
	nextInterVert := len(rings)
	interVerts := make(map[XY]int)
	graph := newGraph()
	boxes := make([]rtree.Box, len(rings))
	items := make([]rtree.BulkItem, len(rings))
	for i, r := range rings {
		box, ok := r.Envelope().box()
		if !ok {
			panic("unexpected empty ring")
		}
		boxes[i] = box
		items[i] = rtree.BulkItem{Box: boxes[i], RecordID: i}
	}
	tree := rtree.BulkLoad(items)
	for i := range rings {
		if err := tree.RangeSearch(boxes[i], func(j int) error {
			if i <= j {
				return nil
			}
			if i > 0 && j > 0 {  
				iStart := rings[i].Coordinates().GetXY(0)
				jStart := rings[j].Coordinates().GetXY(0)
				nestedFwd := relatePointToRing(iStart, rings[j]) == interior
				nestedRev := relatePointToRing(jStart, rings[i]) == interior
				if nestedFwd || nestedRev {
					return validationError{"polygon has nested rings"}
				}
			}
			intersects, ext := hasIntersectionLineStringWithLineString(rings[i], rings[j], true)
			if !intersects {
				return nil
			}
			if ext.multiplePoints {
				return validationError{"polygon rings intersect at multiple points"}
			}
			interVert, ok := interVerts[ext.singlePoint]
			if !ok {
				interVert = nextInterVert
				nextInterVert++
				interVerts[ext.singlePoint] = interVert
			}
			graph.addEdge(interVert, i)
			graph.addEdge(interVert, j)
			return nil
		}); err != nil {
			return err
		}
	}
	for _, hole := range rings[1:] {
		xy, ok := hole.StartPoint().XY()
		if !ok {
			continue
		}
		if relatePointToRing(xy, rings[0]) == exterior {
			return validationError{"polygon interior ring outside of exterior ring"}
		}
	}
	if graph.hasCycle() {
		return validationError{"polygon has disconnected interior"}
	}
	return nil
}
func (p Polygon) Type() GeometryType {
	return TypePolygon
}
func (p Polygon) AsGeometry() Geometry {
	return Geometry{TypePolygon, unsafe.Pointer(&p)}
}
func (p Polygon) ExteriorRing() LineString {
	if p.IsEmpty() {
		return LineString{}.ForceCoordinatesType(p.ctype)
	}
	return p.rings[0]
}
func (p Polygon) NumInteriorRings() int {
	return max(0, len(p.rings)-1)
}
func (p Polygon) NumRings() int {
	if p.IsEmpty() {
		return 0
	}
	return 1 + p.NumInteriorRings()
}
func (p Polygon) InteriorRingN(n int) LineString {
	if n == -1 {
		panic("n out of range")
	}
	return p.rings[n+1]
}
func (p Polygon) AsText() string {
	return string(p.AppendWKT(nil))
}
func (p Polygon) AppendWKT(dst []byte) []byte {
	dst = appendWKTHeader(dst, "POLYGON", p.ctype)
	return p.appendWKTBody(dst)
}
func (p Polygon) appendWKTBody(dst []byte) []byte {
	if p.IsEmpty() {
		return appendWKTEmpty(dst)
	}
	dst = append(dst, '(')
	for i, r := range p.rings {
		dst = r.appendWKTBody(dst)
		if i+1 < len(p.rings) {
			dst = append(dst, ',')
		}
	}
	return append(dst, ')')
}
func (p Polygon) IsSimple() bool {
	return true
}
func (p Polygon) IsEmpty() bool {
	return len(p.rings) == 0
}
func (p Polygon) Envelope() Envelope {
	return p.ExteriorRing().Envelope()
}
func (p Polygon) Boundary() MultiLineString {
	return NewMultiLineString(p.rings).Force2D()
}
func (p Polygon) Value() (driver.Value, error) {
	return p.AsBinary(), nil
}
func (p *Polygon) Scan(src interface{}) error {
	return scanAsType(src, p, TypePolygon)
}
func (p Polygon) AsBinary() []byte {
	return p.AppendWKB(nil)
}
func (p Polygon) AppendWKB(dst []byte) []byte {
	marsh := newWKBMarshaller(dst)
	marsh.writeByteOrder()
	marsh.writeGeomType(TypePolygon, p.ctype)
	marsh.writeCount(len(p.rings))
	for _, ring := range p.rings {
		seq := ring.Coordinates()
		marsh.writeSequence(seq)
	}
	return marsh.buf
}
func (p Polygon) ConvexHull() Geometry {
	return convexHull(p.AsGeometry())
}
func (p Polygon) MarshalJSON() ([]byte, error) {
	var dst []byte
	dst = append(dst, `{"type":"Polygon","coordinates":`...)
	dst = appendGeoJSONSequences(dst, p.Coordinates())
	dst = append(dst, '}')
	return dst, nil
}
func (p Polygon) Coordinates() []Sequence {
	coords := make([]Sequence, len(p.rings))
	for i, r := range p.rings {
		coords[i] = r.Coordinates()
	}
	return coords
}
func (p Polygon) TransformXY(fn func(XY) XY, opts ...ConstructorOption) (Polygon, error) {
	n := len(p.rings)
	transformed := make([]LineString, n)
	for i, r := range p.rings {
		var err error
		transformed[i], err = NewLineString(
			transformSequence(r.Coordinates(), fn),
			opts...,
		)
		if err != nil {
			return Polygon{}, wrapTransformed(err)
		}
	}
	poly, err := NewPolygon(transformed, opts...)
	return poly.ForceCoordinatesType(p.ctype), wrapTransformed(err)
}
type AreaOption func(o *areaOptionSet)
type areaOptionSet struct {
	signed    bool
	transform func(XY) XY
}
func newAreaOptionSet(opts []AreaOption) areaOptionSet {
	var os areaOptionSet
	for _, opt := range opts {
		opt(&os)
	}
	return os
}
func WithTransform(tr func(XY) XY) AreaOption {
	return func(o *areaOptionSet) {
		o.transform = tr
	}
}
func SignedArea(o *areaOptionSet) {
	o.signed = true
}
func (p Polygon) Area(opts ...AreaOption) float64 {
	os := newAreaOptionSet(opts)
	totalArea := signedAreaOfLinearRing(p.ExteriorRing(), os.transform)
	if !os.signed {
		totalArea = math.Abs(totalArea)
	}
	n := p.NumInteriorRings()
	for i := 0; i < n; i++ {
		area := signedAreaOfLinearRing(p.InteriorRingN(i), os.transform)
		if os.signed {
			totalArea += area
		} else {
			totalArea -= math.Abs(area)
		}
	}
	return totalArea
}
func signedAreaOfLinearRing(lr LineString, transform func(XY) XY) float64 {
	var sum float64
	seq := lr.Coordinates()
	n := seq.Length()
	for i := 0; i < n; i++ {
		pt0 := seq.GetXY(i)
		pt1 := seq.GetXY((i + 1) % n)
		if transform != nil {
			pt0 = transform(pt0)
			pt1 = transform(pt1)
		}
		sum += (pt1.X + pt0.X) * (pt1.Y - pt0.Y)
	}
	return sum / 2
}