func (m MultiLineString) Envelope() Envelope {
	var env Envelope
	for _, ls := range m.lines {
		env = env.ExpandToIncludeEnvelope(ls.Envelope())
	}
	return env
}
Envelope returns the Envelope that most tightly surrounds the geometry.
package geom
import (
	"database/sql/driver"
	"fmt"
	"unsafe"
	"github.com/peterstace/simplefeatures/rtree"
)
type MultiLineString struct {
	lines []LineString
	ctype CoordinatesType
}
func NewMultiLineString(lines []LineString, opts ...ConstructorOption) MultiLineString {
	if len(lines) == 0 {
		return MultiLineString{}
	}
	ctype := DimXYZM
	for _, ls := range lines {
		ctype &= ls.CoordinatesType()
	}
	lines = append([]LineString(nil), lines...)
	for i := range lines {
		lines[i] = lines[i].ForceCoordinatesType(ctype)
	}
	return MultiLineString{lines, ctype}
}
func (m MultiLineString) Type() GeometryType {
	return TypeMultiLineString
}
func (m MultiLineString) AsGeometry() Geometry {
	return Geometry{TypeMultiLineString, unsafe.Pointer(&m)}
}
func (m MultiLineString) NumLineStrings() int {
	return len(m.lines)
}
func (m MultiLineString) LineStringN(n int) LineString {
	return m.lines[n]
}
func (m MultiLineString) AsText() string {
	return string(m.AppendWKT(nil))
}
func (m MultiLineString) AppendWKT(dst []byte) []byte {
	dst = appendWKTHeader(dst, "MULTILINESTRING", m.ctype)
	if len(m.lines) == 0 {
		return appendWKTEmpty(dst)
	}
	dst = append(dst, '(')
	for i, line := range m.lines {
		if i > 0 {
			dst = append(dst, ',')
		}
		dst = line.appendWKTBody(dst)
	}
	return append(dst, ')')
}
func (m MultiLineString) IsSimple() bool {
	for _, ls := range m.lines {
		if !ls.IsSimple() {
			return false
		}
	}
	toRecordID := func(lineStringIdx, seqIdx int) int {
		return int(uint64(lineStringIdx)<<32 | uint64(seqIdx))
	}
	fromRecordID := func(recordID int) (lineStringIdx, seqIdx int) {
		lineStringIdx = int(uint64(recordID) >> 32)
		seqIdx = int((uint64(recordID) << 32) >> 32)
		return
	}
	var numItems int
	for _, ls := range m.lines {
		numItems += max(0, ls.Coordinates().Length()-1)
	}
	items := make([]rtree.BulkItem, 0, numItems)
	for i, ls := range m.lines {
		seq := ls.Coordinates()
		seqLen := seq.Length()
		for j := 0; j < seqLen; j++ {
			ln, ok := getLine(seq, j)
			if !ok {
				continue
			}
			items = append(items, rtree.BulkItem{
				Box:      ln.box(),
				RecordID: toRecordID(i, j),
			})
		}
	}
	tree := rtree.BulkLoad(items)
	for i, ls := range m.lines {
		seq := ls.Coordinates()
		seqLen := seq.Length()
		for j := 0; j < seqLen; j++ {
			ln, ok := getLine(seq, j)
			if !ok {
				continue
			}
			isSimple := true  
			tree.RangeSearch(ln.box(), func(recordID int) error {
				lineStringIdx, seqIdx := fromRecordID(recordID)
				if lineStringIdx == i {
					return nil
				}
				otherLS := m.lines[lineStringIdx]
				other, ok := getLine(otherLS.Coordinates(), seqIdx)
				if !ok {
					panic("could not getLine")
				}
				inter := ln.intersectLine(other)
				if inter.empty {
					return nil
				}
				if inter.ptA != inter.ptB {
					isSimple = false
					return rtree.Stop
				}
				boundary := intersectionOfMultiPointAndMultiPoint(ls.Boundary(), otherLS.Boundary())
				if !hasIntersectionPointWithMultiPoint(inter.ptA.asUncheckedPoint(), boundary) {
					isSimple = false
					return rtree.Stop
				}
				return nil
			})
			if !isSimple {
				return false
			}
		}
	}
	return true
}
func (m MultiLineString) IsEmpty() bool {
	for _, ls := range m.lines {
		if !ls.IsEmpty() {
			return false
		}
	}
	return true
}