tile38/geojson/bbox.go
umpc 05de7283b4 Non-essential changes
I changed style slightly to better fit in with the rest of the project's code, added some missing comments where needed, and added in the commented code that the unchanged bounds must be copied to both of the new bboxes if it is later refactored to work that way.
2016-10-13 06:17:45 -04:00

264 lines
6.0 KiB
Go

package geojson
import (
"bytes"
"strconv"
"math"
"github.com/tidwall/tile38/geojson/poly"
)
// BBox is a bounding box
type BBox struct {
Min Position
Max Position
}
// New2DBBox creates a new bounding box
func New2DBBox(minX, minY, maxX, maxY float64) BBox {
return BBox{Min: Position{X: minX, Y: minY, Z: 0}, Max: Position{X: maxX, Y: maxY, Z: 0}}
}
func fillBBox(m map[string]interface{}) (*BBox, error) {
var bbox *BBox
var ok bool
switch v := m["bbox"].(type) {
default:
return nil, errBBoxInvalidType
case nil:
case []interface{}:
if !(len(v) == 4 || len(v) == 6) {
return nil, errBBoxInvalidNumberOfValues
}
bbox = &BBox{}
if bbox.Min.X, ok = v[0].(float64); !ok {
return nil, errBBoxInvalidValue
}
if bbox.Min.Y, ok = v[1].(float64); !ok {
return nil, errBBoxInvalidValue
}
i := 2
if len(v) == 6 {
if bbox.Min.Z, ok = v[2].(float64); !ok {
return nil, errBBoxInvalidValue
}
i = 3
} else {
bbox.Min.Z = nilz
}
if bbox.Max.X, ok = v[i+0].(float64); !ok {
return nil, errBBoxInvalidValue
}
if bbox.Max.Y, ok = v[i+1].(float64); !ok {
return nil, errBBoxInvalidValue
}
if len(v) == 6 {
if bbox.Max.Z, ok = v[i+2].(float64); !ok {
return nil, errBBoxInvalidValue
}
} else {
bbox.Max.Z = nilz
}
}
return bbox, nil
}
func (b *BBox) isCordZDefined() bool {
return b != nil && (b.Min.Z != nilz || b.Max.Z != nilz)
}
func (b *BBox) write(buf *bytes.Buffer) {
if b == nil {
return
}
hasZ := b.Min.Z != nilz && b.Max.Z != nilz
buf.WriteString(`,"bbox":[`)
buf.WriteString(strconv.FormatFloat(b.Min.X, 'f', -1, 64))
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Min.Y, 'f', -1, 64))
if hasZ {
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Min.Z, 'f', -1, 64))
}
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.X, 'f', -1, 64))
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.Y, 'f', -1, 64))
if hasZ {
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.Z, 'f', -1, 64))
}
buf.WriteByte(']')
}
func (b BBox) center() Position {
return Position{
(b.Max.X-b.Min.X)/2 + b.Min.X,
(b.Max.Y-b.Min.Y)/2 + b.Min.Y,
0,
}
}
func (b BBox) union(bbox BBox) BBox {
if bbox.Min.X < b.Min.X {
b.Min.X = bbox.Min.X
}
if bbox.Min.Y < b.Min.Y {
b.Min.Y = bbox.Min.Y
}
if bbox.Max.X > b.Max.X {
b.Max.X = bbox.Max.X
}
if bbox.Max.Y > b.Max.Y {
b.Max.Y = bbox.Max.Y
}
return b
}
func (b BBox) exterior() []Position {
return []Position{
{b.Min.X, b.Min.Y, 0},
{b.Min.X, b.Max.Y, 0},
{b.Max.X, b.Max.Y, 0},
{b.Max.X, b.Min.Y, 0},
{b.Min.X, b.Min.Y, 0},
}
}
func rectBBox(bbox BBox) poly.Rect {
return poly.Rect{
Min: poly.Point{X: bbox.Min.X, Y: bbox.Min.Y, Z: 0},
Max: poly.Point{X: bbox.Max.X, Y: bbox.Max.Y, Z: 0},
}
}
// ExternalJSON is the simple json representation of the bounding box used for external applications.
func (b BBox) ExternalJSON() string {
sw, ne := b.Min, b.Max
sw.Z, ne.Z = 0, 0
return `{"sw":` + sw.ExternalJSON() + `,"ne":` + ne.ExternalJSON() + `}`
}
// Sparse returns back an evenly distributed number of sub bboxs.
func (b BBox) Sparse(amount byte) []BBox {
if amount == 0 {
return []BBox{b}
}
var bboxes []BBox
split := 1 << amount
var xsize, ysize float64
if b.Max.X < b.Min.X {
// crosses the prime meridian
xsize = (b.Min.X - b.Max.X) / float64(split)
} else {
xsize = (b.Max.X - b.Min.X) / float64(split)
}
if b.Max.Y < b.Min.Y {
// crosses the equator
ysize = (b.Min.Y - b.Max.Y) / float64(split)
} else {
ysize = (b.Max.Y - b.Min.Y) / float64(split)
}
for y := b.Min.Y; y < b.Max.Y; y += ysize {
for x := b.Min.X; x < b.Max.X; x += xsize {
bboxes = append(bboxes, BBox{
Min: Position{X: x, Y: y, Z: b.Min.Z},
Max: Position{X: x + xsize, Y: y + ysize, Z: b.Max.Z},
})
}
}
return bboxes
}
// BBoxesFromCenter calculates the bounding box surrounding a circle.
func BBoxesFromCenter(lat, lon, meters float64) (outer BBox) {
outer.Min.Y, outer.Min.X, outer.Max.Y, outer.Max.X = BBoxBounds(lat, lon, meters)
if outer.Min.X == outer.Max.X {
switch outer.Min.X {
case -180:
outer.Max.X = 180
case 180:
outer.Min.X = -180
}
}
return outer
}
func BBoxBounds(lat, lon, meters float64) (latMin, lonMin, latMax, lonMax float64) {
// see http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#Latitude
lat = toRadians(lat)
lon = toRadians(lon)
r := meters / earthRadius // angular radius
latMin = lat - r
latMax = lat + r
latT := math.Asin(math.Sin(lat) / math.Cos(r))
lonΔ := math.Acos(( math.Cos(r) - math.Sin(latT) * math.Sin(lat)) / (math.Cos(latT) * math.Cos(lat) ))
lonMin = lon - lonΔ
lonMax = lon + lonΔ
// Adjust for north poll
if latMax > math.Pi/2 {
lonMin = -math.Pi
latMax = math.Pi/2
lonMax = math.Pi
}
// Adjust for south poll
if latMin < -math.Pi/2 {
latMin = -math.Pi/2
lonMin = -math.Pi
lonMax = math.Pi
}
// Adjust for wraparound. Remove this if the commented-out condition below this block is added.
if lonMin < -math.Pi || lonMax > math.Pi {
lonMin = -math.Pi
lonMax = math.Pi
}
/*
// Consider splitting area into two bboxes, using the below checks, and erasing above block for performance. See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#PolesAnd180thMeridian
// Adjust for wraparound if minimum longitude is less than -180 degrees.
if lonMin < -math.Pi {
// box 1:
latMin = latMin
latMax = latMax
lonMin += 2*math.Pi
lonMax = math.Pi
// box 2:
latMin = latMin
latMax = latMax
lonMin = -math.Pi
lonMax = lonMax
}
// Adjust for wraparound if minimum longitude is greater than -180 degrees.
if lonMax > math.Pi {
// box 1:
latMin = latMin
latMax = latMax
lonMin = lonMin
lonMax = -math.Pi
// box 2:
latMin = latMin
latMax = latMax
lonMin = -math.Pi
lonMax -= 2*math.Pi
}
*/
lonMin = math.Mod(lonMin+3*math.Pi, 2*math.Pi) - math.Pi // normalise to -180..+180°
lonMax = math.Mod(lonMax+3*math.Pi, 2*math.Pi) - math.Pi
return toDegrees(latMin), toDegrees(lonMin), toDegrees(latMax), toDegrees(lonMax)
}