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
}
makeShapeIndex builds a ShapeIndex from the given debug string containing
the points, polylines, and loops (in the form of a single polygon)
described by the following format:

point1|point2|... # line1|line2|... # polygon1|polygon2|...

Examples:
1:2 | 2:3 # #                     // Two points
# 0:0, 1:1, 2:2 | 3:3, 4:4 #      // Two polylines
# # 0:0, 0:3, 3:0; 1:1, 2:1, 1:2  // Two nested loops (one polygon)
5:5 # 6:6, 7:7 # 0:0, 0:1, 1:0    // One of each
# # empty                         // One empty polygon
# # empty | full                  // One empty polygon, one full polygon

Loops should be directed so that the region's interior is on the left.
Loops can be degenerate (they do not need to meet Loop requirements).

Note: Because whitespace is ignored, empty polygons must be specified
as the string "empty" rather than as the empty string ("").
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)
}