tile38/vendor/github.com/tidwall/grect/grect.go

338 lines
6.3 KiB
Go
Raw Normal View History

package grect
import (
"strconv"
"strings"
"github.com/tidwall/gjson"
)
type Rect struct {
Min, Max []float64
}
func (r Rect) String() string {
diff := len(r.Min) != len(r.Max)
if !diff {
for i := 0; i < len(r.Min); i++ {
if r.Min[i] != r.Max[i] {
diff = true
break
}
}
}
var buf []byte
buf = append(buf, '[')
for i, v := range r.Min {
if i > 0 {
buf = append(buf, ' ')
}
buf = append(buf, strconv.FormatFloat(v, 'f', -1, 64)...)
}
if diff {
buf = append(buf, ']', ',', '[')
for i, v := range r.Max {
if i > 0 {
buf = append(buf, ' ')
}
buf = append(buf, strconv.FormatFloat(v, 'f', -1, 64)...)
}
}
buf = append(buf, ']')
return string(buf)
}
func normalize(min, max []float64) (nmin, nmax []float64) {
if len(max) == 0 {
return min, min
} else if len(max) != len(min) {
if len(max) < len(min) {
max = append(max, min[len(max):]...)
} else if len(min) < len(max) {
min = append(min, max[len(min):]...)
}
}
match := true
for i := 0; i < len(min); i++ {
if min[i] != max[i] {
if match {
match = false
}
if min[i] > max[i] {
min[i], max[i] = max[i], min[i]
}
}
}
if match {
return min, min
}
return min, max
}
func Get(s string) Rect {
var i int
var ws bool
var min, max []float64
for ; i < len(s); i++ {
switch s[i] {
default:
continue
case ' ', '\t', '\r', '\n':
ws = true
continue
case '[':
min, max, i = getRect(s, i)
case '{':
min, max, i = getGeoJSON(s, i)
case 0x00, 0x01:
if !ws {
// return parseWKB(s, i)
}
case 'p', 'P', 'l', 'L', 'm', 'M', 'g', 'G':
min, max, i = getWKT(s, i)
}
break
}
min, max = normalize(min, max)
return Rect{Min: min, Max: max}
}
func getRect(s string, i int) (min, max []float64, ri int) {
a := s[i:]
parts := strings.Split(a, ",")
for i := 0; i < len(parts) && i < 2; i++ {
part := parts[i]
if len(part) > 0 && (part[0] <= ' ' || part[len(part)-1] <= ' ') {
part = strings.TrimSpace(part)
}
if len(part) >= 2 && part[0] == '[' && part[len(part)-1] == ']' {
pieces := strings.Split(part[1:len(part)-1], " ")
if i == 0 {
min = make([]float64, 0, len(pieces))
} else {
max = make([]float64, 0, len(pieces))
}
for j := 0; j < len(pieces); j++ {
piece := pieces[j]
if piece != "" {
n, _ := strconv.ParseFloat(piece, 64)
if i == 0 {
min = append(min, n)
} else {
max = append(max, n)
}
}
}
}
}
// normalize
if len(parts) == 1 {
max = min
} else {
min, max = normalize(min, max)
}
return min, max, len(s)
}
func union(min1, max1, min2, max2 []float64) (umin, umax []float64) {
for i := 0; i < len(min1) || i < len(min2); i++ {
if i >= len(min1) {
// just copy min2
umin = append(umin, min2[i])
umax = append(umax, max2[i])
} else if i >= len(min2) {
// just copy min1
umin = append(umin, min1[i])
umax = append(umax, max1[i])
} else {
if min1[i] < min2[i] {
umin = append(umin, min1[i])
} else {
umin = append(umin, min2[i])
}
if max1[i] > max2[i] {
umax = append(umax, max1[i])
} else {
umax = append(umax, max2[i])
}
}
}
return umin, umax
}
func getWKT(s string, i int) (min, max []float64, ri int) {
switch s[i] {
default:
for ; i < len(s); i++ {
if s[i] == ',' {
return nil, nil, i
}
if s[i] == '(' {
return getWKTAny(s, i)
}
}
return nil, nil, i
case 'g', 'G':
if len(s)-i < 18 {
return nil, nil, i
}
return getWKTGeometryCollection(s, i+18)
}
}
func getWKTAny(s string, i int) (min, max []float64, ri int) {
min, max = make([]float64, 0, 4), make([]float64, 0, 4)
var depth int
var ni int
var idx int
loop:
for ; i < len(s); i++ {
switch s[i] {
default:
if ni == 0 {
ni = i
}
case '(':
depth++
case ')', ' ', '\t', '\r', '\n', ',':
if ni != 0 {
n, _ := strconv.ParseFloat(s[ni:i], 64)
if idx >= len(min) {
min = append(min, n)
max = append(max, n)
} else {
if n < min[idx] {
min[idx] = n
} else if n > max[idx] {
max[idx] = n
}
}
idx++
ni = 0
}
switch s[i] {
case ')':
idx = 0
depth--
if depth == 0 {
i++
break loop
}
case ',':
idx = 0
}
}
}
return min, max, i
}
func getWKTGeometryCollection(s string, i int) (min, max []float64, ri int) {
var depth int
for ; i < len(s); i++ {
if s[i] == ',' || s[i] == ')' {
// do not increment the index
return nil, nil, i
}
if s[i] == '(' {
depth++
i++
break
}
}
next:
for ; i < len(s); i++ {
switch s[i] {
case 'p', 'P', 'l', 'L', 'm', 'M', 'g', 'G':
var min2, max2 []float64
min2, max2, i = getWKT(s, i)
min, max = union(min, max, min2, max2)
for ; i < len(s); i++ {
if s[i] == ',' {
i++
goto next
}
if s[i] == ')' {
i++
goto done
}
}
case ' ', '\t', '\r', '\n':
continue
default:
goto end_early
}
}
end_early:
// just balance the parens
for ; i < len(s); i++ {
if s[i] == '(' {
depth++
} else if s[i] == ')' {
depth--
if depth == 0 {
i++
break
}
}
}
done:
return min, max, i
}
func getGeoJSON(s string, i int) (min, max []float64, ri int) {
json := s[i:]
switch gjson.Get(json, "type").String() {
default:
min, max = getMinMaxBrackets(gjson.Get(json, "coordinates").Raw)
case "Feature":
min, max, _ = getGeoJSON(gjson.Get(json, "geometry").String(), 0)
case "FeatureCollection":
for _, json := range gjson.Get(json, "features").Array() {
nmin, nmax, _ := getGeoJSON(json.String(), 0)
min, max = union(min, max, nmin, nmax)
}
case "GeometryCollection":
for _, json := range gjson.Get(json, "geometries").Array() {
nmin, nmax, _ := getGeoJSON(json.String(), 0)
min, max = union(min, max, nmin, nmax)
}
}
return min, max, len(json)
}
func getMinMaxBrackets(s string) (min, max []float64) {
var ni int
var idx int
for i := 0; i < len(s); i++ {
switch s[i] {
default:
if ni == 0 {
ni = i
}
case '[', ',', ']', ' ', '\t', '\r', '\n':
if ni > 0 {
n, _ := strconv.ParseFloat(s[ni:i], 64)
if idx >= len(min) {
min = append(min, n)
max = append(max, n)
} else {
if n < min[idx] {
min[idx] = n
} else if n > max[idx] {
max[idx] = n
}
}
ni = 0
idx++
}
if s[i] == ']' {
idx = 0
}
}
}
return
}