func (p *Polyline) Validate() error {
	for i, pt := range *p {
		if !pt.IsUnit() {
			return fmt.Errorf("vertex %d is not unit length", i)
		}
	}
	for i := 1; i < len(*p); i++ {
		prev, cur := (*p)[i-1], (*p)[i]
		if prev == cur {
			return fmt.Errorf("vertices %d and %d are identical", i-1, i)
		}
		if prev == (Point{cur.Mul(-1)}) {
			return fmt.Errorf("vertices %d and %d are antipodal", i-1, i)
		}
	}
	return nil
}
Validate checks whether this is a valid polyline or not.
package s2
import (
	"fmt"
	"io"
	"math"
	"github.com/golang/geo/s1"
)
type Polyline []Point
func PolylineFromLatLngs(points []LatLng) *Polyline {
	p := make(Polyline, len(points))
	for k, v := range points {
		p[k] = PointFromLatLng(v)
	}
	return &p
}
func (p *Polyline) Reverse() {
	for i := 0; i < len(*p)/2; i++ {
		(*p)[i], (*p)[len(*p)-i-1] = (*p)[len(*p)-i-1], (*p)[i]
	}
}
func (p *Polyline) Length() s1.Angle {
	var length s1.Angle
	for i := 1; i < len(*p); i++ {
		length += (*p)[i-1].Distance((*p)[i])
	}
	return length
}
func (p *Polyline) Centroid() Point {
	var centroid Point
	for i := 1; i < len(*p); i++ {
		vSum := (*p)[i-1].Add((*p)[i].Vector)   
		vDiff := (*p)[i-1].Sub((*p)[i].Vector)  
		centroid = Point{centroid.Add(vSum.Mul(math.Sqrt(vDiff.Norm2() / vSum.Norm2())))}
	}
	return centroid
}
func (p *Polyline) Equal(b *Polyline) bool {
	if len(*p) != len(*b) {
		return false
	}
	for i, v := range *p {
		if v != (*b)[i] {
			return false
		}
	}
	return true
}
func (p *Polyline) ApproxEqual(o *Polyline) bool {
	return p.approxEqual(o, s1.Angle(epsilon))
}
func (p *Polyline) approxEqual(o *Polyline, maxError s1.Angle) bool {
	if len(*p) != len(*o) {
		return false
	}
	for offset, val := range *p {
		if !val.approxEqual((*o)[offset], maxError) {
			return false
		}
	}
	return true
}
func (p *Polyline) CapBound() Cap {
	return p.RectBound().CapBound()
}
func (p *Polyline) RectBound() Rect {
	rb := NewRectBounder()
	for _, v := range *p {
		rb.AddPoint(v)
	}
	return rb.RectBound()
}
func (p *Polyline) ContainsCell(cell Cell) bool {
	return false
}
func (p *Polyline) IntersectsCell(cell Cell) bool {
	if len(*p) == 0 {
		return false
	}
	for _, v := range *p {
		if cell.ContainsPoint(v) {
			return true
		}
	}
	cellVertices := []Point{
		cell.Vertex(0),
		cell.Vertex(1),
		cell.Vertex(2),
		cell.Vertex(3),
	}
	for j := 0; j < 4; j++ {
		crosser := NewChainEdgeCrosser(cellVertices[j], cellVertices[(j+1)&3], (*p)[0])
		for i := 1; i < len(*p); i++ {
			if crosser.ChainCrossingSign((*p)[i]) != DoNotCross {
				return true
			}
		}
	}
	return false
}
func (p *Polyline) ContainsPoint(point Point) bool {
	return false
}
func (p *Polyline) CellUnionBound() []CellID {
	return p.CapBound().CellUnionBound()
}
func (p *Polyline) NumEdges() int {
	if len(*p) == 0 {
		return 0
	}
	return len(*p) - 1
}
func (p *Polyline) Edge(i int) Edge {
	return Edge{(*p)[i], (*p)[i+1]}
}
func (p *Polyline) ReferencePoint() ReferencePoint {
	return OriginReferencePoint(false)
}
func (p *Polyline) NumChains() int {
	return minInt(1, p.NumEdges())
}
func (p *Polyline) Chain(chainID int) Chain {
	return Chain{0, p.NumEdges()}
}
func (p *Polyline) ChainEdge(chainID, offset int) Edge {
	return Edge{(*p)[offset], (*p)[offset+1]}
}
func (p *Polyline) ChainPosition(edgeID int) ChainPosition {
	return ChainPosition{0, edgeID}
}
func (p *Polyline) Dimension() int { return 1 }
func (p *Polyline) IsEmpty() bool { return defaultShapeIsEmpty(p) }
func (p *Polyline) IsFull() bool { return defaultShapeIsFull(p) }
func (p *Polyline) typeTag() typeTag { return typeTagPolyline }
func (p *Polyline) privateInterface() {}
func findEndVertex(p Polyline, tolerance s1.Angle, index int) int {
	origin := p[index]
	frame := getFrame(origin)
	currentWedge := s1.FullInterval()
	var lastDistance s1.Angle
	for index++; index < len(p); index++ {
		candidate := p[index]
		distance := origin.Distance(candidate)
		if distance > math.Pi/2 && lastDistance > 0 {
			break
		}
		if distance < lastDistance && lastDistance > tolerance {
			break
		}
		lastDistance = distance
		if distance <= tolerance {
			continue
		}
		direction := toFrame(frame, candidate)
		center := math.Atan2(direction.Y, direction.X)
		if !currentWedge.Contains(center) {
			break
		}
		halfAngle := math.Asin(math.Sin(tolerance.Radians()) / math.Sin(distance.Radians()))
		target := s1.IntervalFromPointPair(center, center).Expanded(halfAngle)
		currentWedge = currentWedge.Intersection(target)
	}
	return index - 1
}
func (p *Polyline) SubsampleVertices(tolerance s1.Angle) []int {
	var result []int
	if len(*p) < 1 {
		return result
	}
	result = append(result, 0)
	clampedTolerance := s1.Angle(math.Max(tolerance.Radians(), 0))
	for index := 0; index+1 < len(*p); {
		nextIndex := findEndVertex(*p, clampedTolerance, index)
		if (*p)[nextIndex] != (*p)[index] {
			result = append(result, nextIndex)
		}
		index = nextIndex
	}
	return result
}
func (p Polyline) Encode(w io.Writer) error {
	e := &encoder{w: w}
	p.encode(e)
	return e.err
}
func (p Polyline) encode(e *encoder) {
	e.writeInt8(encodingVersion)
	e.writeUint32(uint32(len(p)))
	for _, v := range p {
		e.writeFloat64(v.X)
		e.writeFloat64(v.Y)
		e.writeFloat64(v.Z)
	}
}
func (p *Polyline) Decode(r io.Reader) error {
	d := decoder{r: asByteReader(r)}
	p.decode(d)
	return d.err
}
func (p *Polyline) decode(d decoder) {
	version := d.readInt8()
	if d.err != nil {
		return
	}
	if int(version) != int(encodingVersion) {
		d.err = fmt.Errorf("can't decode version %d; my version: %d", version, encodingVersion)
		return
	}
	nvertices := d.readUint32()
	if d.err != nil {
		return
	}
	if nvertices > maxEncodedVertices {
		d.err = fmt.Errorf("too many vertices (%d; max is %d)", nvertices, maxEncodedVertices)
		return
	}
	*p = make([]Point, nvertices)
	for i := range *p {
		(*p)[i].X = d.readFloat64()
		(*p)[i].Y = d.readFloat64()
		(*p)[i].Z = d.readFloat64()
	}
}
func (p *Polyline) Project(point Point) (Point, int) {
	if len(*p) == 1 {
		return (*p)[0], 1
	}
	minDist := 10 * s1.Radian
	minIndex := -1
	for i := 1; i < len(*p); i++ {
		if dist := DistanceFromSegment(point, (*p)[i-1], (*p)[i]); dist < minDist {
			minDist = dist
			minIndex = i
		}
	}
	closest := Project(point, (*p)[minIndex-1], (*p)[minIndex])
	if closest == (*p)[minIndex] {
		minIndex++
	}
	return closest, minIndex
}
func (p *Polyline) IsOnRight(point Point) bool {
	closest, next := p.Project(point)
	if closest == (*p)[next-1] && next > 1 && next < len(*p) {
		if point == (*p)[next-1] {
			return false
		}
		return OrderedCCW((*p)[next-2], point, (*p)[next], (*p)[next-1])
	}
	if next == len(*p) {
		next--
	}
	return Sign(point, (*p)[next], (*p)[next-1])
}