func shapeIndexDebugString(index *ShapeIndex) string {
	var buf bytes.Buffer
	for dim := 0; dim <= 2; dim++ {
		if dim > 0 {
			buf.WriteByte('#')
		}
		var count int
		for i := int32(0); i < index.nextID; i++ {
			shape := index.Shape(i)
			if shape == nil || shape.Dimension() != dim {
				continue
			}
			if count > 0 {
				buf.WriteString(" | ")
			} else {
				if dim > 0 {
					buf.WriteByte(' ')
				}
			}
			for c := 0; c < shape.NumChains(); c++ {
				if c > 0 {
					if dim == 2 {
						buf.WriteString("; ")
					} else {
						buf.WriteString(" | ")
					}
				}
				chain := shape.Chain(c)
				pts := []Point{shape.Edge(chain.Start).V0}
				limit := chain.Start + chain.Length
				if dim != 1 {
					limit--
				}
				for e := chain.Start; e < limit; e++ {
					pts = append(pts, shape.Edge(e).V1)
				}
				writePoints(&buf, pts)
				count++
			}
		}
		if dim == 1 || (dim == 0 && count > 0) {
			buf.WriteByte(' ')
		}
	}
	return buf.String()
}
shapeIndexDebugString outputs the contents of this ShapeIndex in debug
format. The index may contain Shapes of any type. Shapes are reordered
if necessary so that all point geometry (shapes of dimension 0) are first,
followed by all polyline geometry, followed by all polygon geometry.
package s2
import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
	"github.com/golang/geo/r3"
)
func writePoint(w io.Writer, p Point) {
	ll := LatLngFromPoint(p)
	fmt.Fprintf(w, "%.15g:%.15g", ll.Lat.Degrees(), ll.Lng.Degrees())
}
func writePoints(w io.Writer, pts []Point) {
	for i, pt := range pts {
		if i > 0 {
			fmt.Fprint(w, ", ")
		}
		writePoint(w, pt)
	}
}
func parsePoint(s string) Point {
	p := parsePoints(s)
	if len(p) > 0 {
		return p[0]
	}
	return Point{r3.Vector{0, 0, 0}}
}
func pointToString(point Point) string {
	var buf bytes.Buffer
	writePoint(&buf, point)
	return buf.String()
}
func parsePoints(s string) []Point {
	lls := parseLatLngs(s)
	if len(lls) == 0 {
		return nil
	}
	points := make([]Point, len(lls))
	for i, ll := range lls {
		points[i] = PointFromLatLng(ll)
	}
	return points
}
func pointsToString(points []Point) string {
	var buf bytes.Buffer
	writePoints(&buf, points)
	return buf.String()
}
func parseLatLngs(s string) []LatLng {
	var lls []LatLng
	if s == "" {
		return lls
	}
	for _, piece := range strings.Split(s, ",") {
		piece = strings.TrimSpace(piece)
		if piece == "" {
			continue
		}
		p := strings.Split(piece, ":")
		if len(p) != 2 {
			panic(fmt.Sprintf("invalid input string for parseLatLngs: %q", piece))
		}
		lat, err := strconv.ParseFloat(p[0], 64)
		if err != nil {
			panic(fmt.Sprintf("invalid float in parseLatLngs: %q, err: %v", p[0], err))
		}
		lng, err := strconv.ParseFloat(p[1], 64)
		if err != nil {
			panic(fmt.Sprintf("invalid float in parseLatLngs: %q, err: %v", p[1], err))
		}
		lls = append(lls, LatLngFromDegrees(lat, lng))
	}
	return lls
}
func makeRect(s string) Rect {
	var rect Rect
	lls := parseLatLngs(s)
	if len(lls) > 0 {
		rect = RectFromLatLng(lls[0])
	}
	for _, ll := range lls[1:] {
		rect = rect.AddPoint(ll)
	}
	return rect
}
func makeCellUnion(tokens ...string) CellUnion {
	var cu CellUnion
	for _, t := range tokens {
		cu = append(cu, cellIDFromString(t))
	}
	return cu
}
func makeLoop(s string) *Loop {
	if s == "full" {
		return FullLoop()
	}
	if s == "empty" {
		return EmptyLoop()
	}
	return LoopFromPoints(parsePoints(s))
}
func makePolygon(s string, normalize bool) *Polygon {
	var loops []*Loop
	if s == "empty" || s == "" {
		return PolygonFromLoops(loops)
	}
	for _, str := range strings.Split(s, ";") {
		if str == "" {
			continue
		}
		loop := makeLoop(strings.TrimSpace(str))
		if normalize && !loop.IsFull() {
			loop.Normalize()
		}
		loops = append(loops, loop)
	}
	return PolygonFromLoops(loops)
}
func makePolyline(s string) *Polyline {
	p := Polyline(parsePoints(s))
	return &p
}
func makeLaxPolyline(s string) *laxPolyline {
	return laxPolylineFromPoints(parsePoints(s))
}
func laxPolylineToString(l *laxPolyline) string {
	var buf bytes.Buffer
	writePoints(&buf, l.vertices)
	return buf.String()
}
func makeLaxPolygon(s string) *laxPolygon {
	var points [][]Point
	if s == "" {
		return laxPolygonFromPoints(points)
	}
	for _, l := range strings.Split(s, ";") {
		if l == "full" {
			points = append(points, []Point{})
		} else if l != "empty" {
			points = append(points, parsePoints(l))
		}
	}
	return laxPolygonFromPoints(points)
}
func makeShapeIndex(s string) *ShapeIndex {
	fields := strings.Split(s, "#")
	if len(fields) != 3 {
		panic("shapeIndex debug string must contain 2 '#' characters")
	}
	index := NewShapeIndex()
	var points []Point
	for _, p := range strings.Split(fields[0], "|") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		points = append(points, parsePoint(p))
	}
	if len(points) > 0 {
		p := PointVector(points)
		index.Add(&p)
	}
	for _, p := range strings.Split(fields[1], "|") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		if polyline := makeLaxPolyline(p); polyline != nil {
			index.Add(polyline)
		}
	}
	for _, p := range strings.Split(fields[2], "|") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		if polygon := makeLaxPolygon(p); polygon != nil {
			index.Add(polygon)
		}
	}
	return index
}