func (m MultiPolygon) Area(opts ...AreaOption) float64 {
	var area float64
	n := m.NumPolygons()
	for i := 0; i < n; i++ {
		area += m.PolygonN(i).Area(opts...)
	}
	return area
}
Area in the case of a MultiPolygon is the sum of the areas of its polygons.
package geom
import (
	"database/sql/driver"
	"fmt"
	"unsafe"
	"github.com/peterstace/simplefeatures/rtree"
)
type MultiPolygon struct {
	polys []Polygon
	ctype CoordinatesType
}
func NewMultiPolygon(polys []Polygon, opts ...ConstructorOption) (MultiPolygon, error) {
	if len(polys) == 0 {
		return MultiPolygon{}, nil
	}
	ctype := DimXYZM
	for _, p := range polys {
		ctype &= p.CoordinatesType()
	}
	polys = append([]Polygon(nil), polys...)
	for i := range polys {
		polys[i] = polys[i].ForceCoordinatesType(ctype)
	}
	ctorOpts := newOptionSet(opts)
	if err := validateMultiPolygon(polys, ctorOpts); err != nil {
		if ctorOpts.omitInvalid {
			return MultiPolygon{}, nil
		}
		return MultiPolygon{}, err
	}
	return MultiPolygon{polys, ctype}, nil
}
func validateMultiPolygon(polys []Polygon, opts ctorOptionSet) error {
	if opts.skipValidations {
		return nil
	}
	polyBoundaries := make([]indexedLines, len(polys))
	polyBoundaryPopulated := make([]bool, len(polys))
	boxes := make([]rtree.Box, len(polys))
	items := make([]rtree.BulkItem, 0, len(polys))
	for i, p := range polys {
		if box, ok := p.Envelope().box(); ok {
			boxes[i] = box
			item := rtree.BulkItem{Box: boxes[i], RecordID: i}
			items = append(items, item)
		}
	}
	tree := rtree.BulkLoad(items)
	for i := range polys {
		if polys[i].IsEmpty() {
			continue
		}
		if err := tree.RangeSearch(boxes[i], func(j int) error {
			if i <= j {
				return nil
			}
			for _, k := range [...]int{i, j} {
				if !polyBoundaryPopulated[k] {
					polyBoundaries[k] = newIndexedLines(polys[k].Boundary().asLines())
					polyBoundaryPopulated[k] = true
				}
			}
			interMP, interMLS := intersectionOfIndexedLines(
				polyBoundaries[i],
				polyBoundaries[j],
			)
			if !interMLS.IsEmpty() {
				return validationError{"multipolygon child polygon " +
					"boundaries intersect at multiple points"}
			}
			if interMP.IsEmpty() {
				ptI := polys[i].ExteriorRing().Coordinates().GetXY(0)
				ptJ := polys[j].ExteriorRing().Coordinates().GetXY(0)
				if relatePointToPolygon(ptI, polyBoundaries[j]) != exterior ||
					relatePointToPolygon(ptJ, polyBoundaries[i]) != exterior {
					return validationError{"multipolygon has nested child polygons"}
				}
				return nil
			}
			for _, pair := range [...]struct{ pb1, pb2 indexedLines }{
				{polyBoundaries[i], polyBoundaries[j]},
				{polyBoundaries[j], polyBoundaries[i]},
			} {
				if err := validatePolyNotInsidePoly(pair.pb1, pair.pb2); err != nil {
					return err
				}
			}
			return nil
		}); err != nil {
			return err
		}
	}
	return nil
}
func validatePolyNotInsidePoly(p1, p2 indexedLines) error {
	for j := range p2.lines {
		var pts []XY
		p1.tree.RangeSearch(p2.lines[j].box(), func(i int) error {
			inter := p1.lines[i].intersectLine(p2.lines[j])
			if inter.empty {
				return nil
			}
			if inter.ptA != inter.ptB {
				panic(fmt.Sprintf("already established that boundaries only "+
					"intersect at points, but got: %v", inter))
			}
			pts = append(pts, inter.ptA)
			return nil
		})
		if len(pts) == 0 {
			continue
		}
		pts = append(pts, p2.lines[j].a, p2.lines[j].b)
		pts = sortAndUniquifyXYs(pts)
		for k := 0; k+1 < len(pts); k++ {
			midpoint := pts[k].Add(pts[k+1]).Scale(0.5)
			if relatePointToPolygon(midpoint, p1) == interior {
				return validationError{fmt.Sprintf("multipolygon child polygon "+
					"interiors intersect at %v", midpoint)}
			}
		}
	}
	return nil
}
func (m MultiPolygon) Type() GeometryType {
	return TypeMultiPolygon
}
func (m MultiPolygon) AsGeometry() Geometry {
	return Geometry{TypeMultiPolygon, unsafe.Pointer(&m)}
}
func (m MultiPolygon) NumPolygons() int {
	return len(m.polys)
}
func (m MultiPolygon) PolygonN(n int) Polygon {
	return m.polys[n]
}
func (m MultiPolygon) AsText() string {
	return string(m.AppendWKT(nil))
}
func (m MultiPolygon) AppendWKT(dst []byte) []byte {
	dst = appendWKTHeader(dst, "MULTIPOLYGON", m.ctype)
	if len(m.polys) == 0 {
		return appendWKTEmpty(dst)
	}
	dst = append(dst, '(')
	for i, poly := range m.polys {
		if i > 0 {
			dst = append(dst, ',')
		}
		dst = poly.appendWKTBody(dst)
	}
	return append(dst, ')')
}
func (m MultiPolygon) IsSimple() bool {
	return true
}
func (m MultiPolygon) IsEmpty() bool {
	for _, p := range m.polys {
		if !p.IsEmpty() {
			return false
		}
	}
	return true
}
func (m MultiPolygon) Envelope() Envelope {
	var env Envelope
	for _, poly := range m.polys {
		env = env.ExpandToIncludeEnvelope(poly.Envelope())
	}
	return env
}
func (m MultiPolygon) Boundary() MultiLineString {
	var n int
	for _, p := range m.polys {
		n += len(p.rings)
	}
	bounds := make([]LineString, 0, n)
	for _, p := range m.polys {
		for _, r := range p.rings {
			bounds = append(bounds, r.Force2D())
		}
	}
	return NewMultiLineString(bounds)
}
func (m MultiPolygon) Value() (driver.Value, error) {
	return m.AsBinary(), nil
}
func (m *MultiPolygon) Scan(src interface{}) error {
	return scanAsType(src, m, TypeMultiPolygon)
}
func (m MultiPolygon) AsBinary() []byte {
	return m.AppendWKB(nil)
}
func (m MultiPolygon) AppendWKB(dst []byte) []byte {
	marsh := newWKBMarshaller(dst)
	marsh.writeByteOrder()
	marsh.writeGeomType(TypeMultiPolygon, m.ctype)
	n := m.NumPolygons()
	marsh.writeCount(n)
	for i := 0; i < n; i++ {
		poly := m.PolygonN(i)
		marsh.buf = poly.AppendWKB(marsh.buf)
	}
	return marsh.buf
}
func (m MultiPolygon) ConvexHull() Geometry {
	return convexHull(m.AsGeometry())
}
func (m MultiPolygon) MarshalJSON() ([]byte, error) {
	var dst []byte
	dst = append(dst, `{"type":"MultiPolygon","coordinates":`...)
	dst = appendGeoJSONSequenceMatrix(dst, m.Coordinates())
	dst = append(dst, '}')
	return dst, nil
}
func (m MultiPolygon) Coordinates() [][]Sequence {
	numPolys := m.NumPolygons()
	coords := make([][]Sequence, numPolys)
	for i := 0; i < numPolys; i++ {
		coords[i] = m.PolygonN(i).Coordinates()
	}
	return coords
}
func (m MultiPolygon) TransformXY(fn func(XY) XY, opts ...ConstructorOption) (MultiPolygon, error) {
	polys := make([]Polygon, m.NumPolygons())
	for i := range polys {
		transformed, err := m.PolygonN(i).TransformXY(fn, opts...)
		if err != nil {
			return MultiPolygon{}, wrapTransformed(err)
		}
		polys[i] = transformed
	}
	mp, err := NewMultiPolygon(polys, opts...)
	return mp.ForceCoordinatesType(m.ctype), wrapTransformed(err)
}