func (c Cell) vEdgeIsClosest(p Point, uHi bool) bool {
	v0 := c.uv.Y.Lo
	v1 := c.uv.Y.Hi
	u := c.uv.X.Lo
	if uHi {
		u = c.uv.X.Hi
	}
	dir0 := r3.Vector{-u * v0, u*u + 1, -v0}
	dir1 := r3.Vector{-u * v1, u*u + 1, -v1}
	return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}
vEdgeIsClosest reports whether a point P is closer to the interior of the specified
Cell edge (either the right or left edge of the Cell) or to the endpoints.
package s2
import (
	"io"
	"math"
	"github.com/golang/geo/r1"
	"github.com/golang/geo/r2"
	"github.com/golang/geo/r3"
	"github.com/golang/geo/s1"
)
type Cell struct {
	face        int8
	level       int8
	orientation int8
	id          CellID
	uv          r2.Rect
}
func CellFromCellID(id CellID) Cell {
	c := Cell{}
	c.id = id
	f, i, j, o := c.id.faceIJOrientation()
	c.face = int8(f)
	c.level = int8(c.id.Level())
	c.orientation = int8(o)
	c.uv = ijLevelToBoundUV(i, j, int(c.level))
	return c
}
func CellFromPoint(p Point) Cell {
	return CellFromCellID(cellIDFromPoint(p))
}
func CellFromLatLng(ll LatLng) Cell {
	return CellFromCellID(CellIDFromLatLng(ll))
}
func (c Cell) Face() int {
	return int(c.face)
}
func oppositeFace(face int) int {
	return (face + 3) % 6
}
func (c Cell) Level() int {
	return int(c.level)
}
func (c Cell) ID() CellID {
	return c.id
}
func (c Cell) IsLeaf() bool {
	return c.level == maxLevel
}
func (c Cell) SizeIJ() int {
	return sizeIJ(int(c.level))
}
func (c Cell) SizeST() float64 {
	return c.id.sizeST(int(c.level))
}
func (c Cell) Vertex(k int) Point {
	return Point{faceUVToXYZ(int(c.face), c.uv.Vertices()[k].X, c.uv.Vertices()[k].Y).Normalize()}
}
func (c Cell) Edge(k int) Point {
	switch k {
	case 0:
		return Point{vNorm(int(c.face), c.uv.Y.Lo).Normalize()}  
	case 1:
		return Point{uNorm(int(c.face), c.uv.X.Hi).Normalize()}  
	case 2:
		return Point{vNorm(int(c.face), c.uv.Y.Hi).Mul(-1.0).Normalize()}  
	default:
		return Point{uNorm(int(c.face), c.uv.X.Lo).Mul(-1.0).Normalize()}  
	}
}
func (c Cell) BoundUV() r2.Rect {
	return c.uv
}
func (c Cell) Center() Point {
	return Point{c.id.rawPoint().Normalize()}
}
func (c Cell) Children() ([4]Cell, bool) {
	var children [4]Cell
	if c.id.IsLeaf() {
		return children, false
	}
	uvMid := c.id.centerUV()
	cid := c.id.ChildBegin()
	for pos := 0; pos < 4; pos++ {
		children[pos] = Cell{
			face:        c.face,
			level:       c.level + 1,
			orientation: c.orientation ^ int8(posToOrientation[pos]),
			id:          cid,
		}
		ij := posToIJ[c.orientation][pos]
		i := ij >> 1
		j := ij & 1
		if i == 1 {
			children[pos].uv.X.Hi = c.uv.X.Hi
			children[pos].uv.X.Lo = uvMid.X
		} else {
			children[pos].uv.X.Lo = c.uv.X.Lo
			children[pos].uv.X.Hi = uvMid.X
		}
		if j == 1 {
			children[pos].uv.Y.Hi = c.uv.Y.Hi
			children[pos].uv.Y.Lo = uvMid.Y
		} else {
			children[pos].uv.Y.Lo = c.uv.Y.Lo
			children[pos].uv.Y.Hi = uvMid.Y
		}
		cid = cid.Next()
	}
	return children, true
}
func (c Cell) ExactArea() float64 {
	v0, v1, v2, v3 := c.Vertex(0), c.Vertex(1), c.Vertex(2), c.Vertex(3)
	return PointArea(v0, v1, v2) + PointArea(v0, v2, v3)
}
func (c Cell) ApproxArea() float64 {
	if c.level < 2 {
		return c.AverageArea()
	}
	flatArea := 0.5 * (c.Vertex(2).Sub(c.Vertex(0).Vector).
		Cross(c.Vertex(3).Sub(c.Vertex(1).Vector)).Norm())
	return flatArea * 2 / (1 + math.Sqrt(1-math.Min(1/math.Pi*flatArea, 1)))
}
func (c Cell) AverageArea() float64 {
	return AvgAreaMetric.Value(int(c.level))
}
func (c Cell) IntersectsCell(oc Cell) bool {
	return c.id.Intersects(oc.id)
}
func (c Cell) ContainsCell(oc Cell) bool {
	return c.id.Contains(oc.id)
}
func (c Cell) CellUnionBound() []CellID {
	return c.CapBound().CellUnionBound()
}
func (c Cell) latitude(i, j int) float64 {
	var u, v float64
	switch {
	case i == 0 && j == 0:
		u = c.uv.X.Lo
		v = c.uv.Y.Lo
	case i == 0 && j == 1:
		u = c.uv.X.Lo
		v = c.uv.Y.Hi
	case i == 1 && j == 0:
		u = c.uv.X.Hi
		v = c.uv.Y.Lo
	case i == 1 && j == 1:
		u = c.uv.X.Hi
		v = c.uv.Y.Hi
	default:
		panic("i and/or j is out of bounds")
	}
	return latitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}
func (c Cell) longitude(i, j int) float64 {
	var u, v float64
	switch {
	case i == 0 && j == 0:
		u = c.uv.X.Lo
		v = c.uv.Y.Lo
	case i == 0 && j == 1:
		u = c.uv.X.Lo
		v = c.uv.Y.Hi
	case i == 1 && j == 0:
		u = c.uv.X.Hi
		v = c.uv.Y.Lo
	case i == 1 && j == 1:
		u = c.uv.X.Hi
		v = c.uv.Y.Hi
	default:
		panic("i and/or j is out of bounds")
	}
	return longitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}
var (
	poleMinLat = math.Asin(math.Sqrt(1.0/3)) - 0.5*dblEpsilon
)
func (c Cell) RectBound() Rect {
	if c.level > 0 {
		u := c.uv.X.Lo + c.uv.X.Hi
		v := c.uv.Y.Lo + c.uv.Y.Hi
		var i, j int
		if uAxis(int(c.face)).Z == 0 {
			if u < 0 {
				i = 1
			}
		} else if u > 0 {
			i = 1
		}
		if vAxis(int(c.face)).Z == 0 {
			if v < 0 {
				j = 1
			}
		} else if v > 0 {
			j = 1
		}
		lat := r1.IntervalFromPoint(c.latitude(i, j)).AddPoint(c.latitude(1-i, 1-j))
		lng := s1.EmptyInterval().AddPoint(c.longitude(i, 1-j)).AddPoint(c.longitude(1-i, j))
		return Rect{lat, lng}.expanded(LatLng{s1.Angle(2 * dblEpsilon), s1.Angle(2 * dblEpsilon)}).PolarClosure()
	}
	var bound Rect
	switch c.face {
	case 0:
		bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-math.Pi / 4, math.Pi / 4}}
	case 1:
		bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{math.Pi / 4, 3 * math.Pi / 4}}
	case 2:
		bound = Rect{r1.Interval{poleMinLat, math.Pi / 2}, s1.FullInterval()}
	case 3:
		bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{3 * math.Pi / 4, -3 * math.Pi / 4}}
	case 4:
		bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-3 * math.Pi / 4, -math.Pi / 4}}
	default:
		bound = Rect{r1.Interval{-math.Pi / 2, -poleMinLat}, s1.FullInterval()}
	}
	return bound.expanded(LatLng{s1.Angle(dblEpsilon), s1.Angle(0)})
}
func (c Cell) CapBound() Cap {
	cap := CapFromPoint(Point{faceUVToXYZ(int(c.face), c.uv.Center().X, c.uv.Center().Y).Normalize()})
	for k := 0; k < 4; k++ {
		cap = cap.AddPoint(c.Vertex(k))
	}
	return cap
}
func (c Cell) ContainsPoint(p Point) bool {
	var uv r2.Point
	var ok bool
	if uv.X, uv.Y, ok = faceXYZToUV(int(c.face), p); !ok {
		return false
	}
	return c.uv.ExpandedByMargin(dblEpsilon).ContainsPoint(uv)
}
func (c Cell) Encode(w io.Writer) error {
	e := &encoder{w: w}
	c.encode(e)
	return e.err
}
func (c Cell) encode(e *encoder) {
	c.id.encode(e)
}
func (c *Cell) Decode(r io.Reader) error {
	d := &decoder{r: asByteReader(r)}
	c.decode(d)
	return d.err
}
func (c *Cell) decode(d *decoder) {
	c.id.decode(d)
	*c = CellFromCellID(c.id)
}
func (c Cell) vertexChordDist2(p Point, xHi, yHi bool) s1.ChordAngle {
	x := c.uv.X.Lo
	y := c.uv.Y.Lo
	if xHi {
		x = c.uv.X.Hi
	}
	if yHi {
		y = c.uv.Y.Hi
	}
	return ChordAngleBetweenPoints(p, PointFromCoords(x, y, 1))
}
func (c Cell) uEdgeIsClosest(p Point, vHi bool) bool {
	u0 := c.uv.X.Lo
	u1 := c.uv.X.Hi
	v := c.uv.Y.Lo
	if vHi {
		v = c.uv.Y.Hi
	}
	dir0 := r3.Vector{v*v + 1, -u0 * v, -u0}
	dir1 := r3.Vector{v*v + 1, -u1 * v, -u1}
	return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}