123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- 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
- }
|