forked from mirror/grect
first commit
This commit is contained in:
commit
ba9a043346
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Josh Baker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,25 @@
|
||||||
|
GRECT
|
||||||
|
====
|
||||||
|
|
||||||
|
Quickly get the outer rectangle for GeoJSON, WKT, WKB.
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := grect.Get(`{
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
||||||
|
[100.0, 1.0], [100.0, 0.0] ]
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
fmt.Printf("%v %v\n", r.Min, r.Max)
|
||||||
|
// Output:
|
||||||
|
// [100 0] [101 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GRECT source code is available under the MIT [License](/LICENSE).
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package grect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testGet(t *testing.T, s, expect string) {
|
||||||
|
if Get(s).String() != expect {
|
||||||
|
t.Fatalf("for '%v': expected '%v', got '%v'", s, expect, Get(s).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRect(t *testing.T) {
|
||||||
|
testGet(t, "", "[]")
|
||||||
|
testGet(t, "[]", "[]")
|
||||||
|
testGet(t, "[],[]", "[]")
|
||||||
|
testGet(t, "[],[],[]", "[]")
|
||||||
|
testGet(t, "[10]", "[10]")
|
||||||
|
testGet(t, "[10],[10]", "[10]")
|
||||||
|
testGet(t, "[10 11],[10]", "[10 11]")
|
||||||
|
testGet(t, "[10 11],[11 10]", "[10 10],[11 11]")
|
||||||
|
testGet(t, ",[10]", "[10]")
|
||||||
|
testGet(t, "[10 11]", "[10 11]")
|
||||||
|
testGet(t, "[-3 -2 -1 0 1 2 3],[3 2 1 0 -1 -2 -3]", "[-3 -2 -1 0 -1 -2 -3],[3 2 1 0 1 2 3]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWKT(t *testing.T) {
|
||||||
|
testGet(t, "POINT(1 2)", "[1 2]")
|
||||||
|
testGet(t, "LINESTRING(3 4, -1 -3, (-20 15 18 ))", "[-20 -3 18],[3 15 18]")
|
||||||
|
testGet(t, "POLYGON (((1 2 0), 3 4 1, -1 -3 123))", "[-1 -3 0],[3 4 123]")
|
||||||
|
testGet(t, "MULTIPOINT (1 2, 3 4, -1 0)", "[-1 0],[3 4]")
|
||||||
|
testGet(t, " mUltiLineString ( (1 2, 3 4),(3 4, (5 6)), (-1 -2 -3)) ", "[-1 -2 -3],[5 6 -3]")
|
||||||
|
testGet(t, ` MULTIPOLYGON (
|
||||||
|
((1 2,2 3),(2 3,8 9)),
|
||||||
|
((4 5,6 7))
|
||||||
|
)`, "[1 2],[8 9]")
|
||||||
|
testGet(t, `
|
||||||
|
GEOMETRYCOLLECTION (
|
||||||
|
POLYGON EMPTY,
|
||||||
|
POINT EMPTY,
|
||||||
|
POINT(1000 2),
|
||||||
|
POINT EMPTY,
|
||||||
|
LINESTRING(3 4, -1 -3, (-20 15 18)),
|
||||||
|
GEOMETRYCOLLECTION EMPTY,
|
||||||
|
GEOMETRYCOLLECTION(POINT(-1000),POLYGON((10 20,-50 1500))),
|
||||||
|
)`, "[-1000 -3 18],[1000 1500 18]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeoJSON(t *testing.T) {
|
||||||
|
testGet(t, `{"type":"Point","coordinates":[1,2]}`, "[1 2]")
|
||||||
|
testGet(t, `{"type":"LineString","coordinates":[[3,4], [-1,-3], [-20,15,18]]}`, "[-20 -3 18],[3 15 18]")
|
||||||
|
testGet(t, `{"type":"Polygon", "coordinates": [[[[1,2,0]],[ 3,4,1], [-1,-3,123]]]}`, "[-1 -3 0],[3 4 123]")
|
||||||
|
testGet(t, `{"type":"MultiPoint", "coordinates":[[1,2], [3,4], [-1,0]]}`, "[-1 0],[3 4]")
|
||||||
|
testGet(t, `{"type":"mUltiLineString","coordinates": [ [[1,2], [3,4]],[[3,4], [5,6]], [-1,-2,-3]]} `, "[-1 -2 -3],[5 6 -3]")
|
||||||
|
testGet(t, `{"type":"MULTIPOLYGON","coordinates": [
|
||||||
|
[[[1 2],[2 3]],[[2 3],[8 9]]],
|
||||||
|
[[[4 5],[6 7]]]
|
||||||
|
)`, "[1 2],[8 9]")
|
||||||
|
testGet(t, `{"type":"GeometryCollection", "geometries":[
|
||||||
|
{"type":"Feature","geometry":{"type":"Point","coordinates":[0 -10, 17]}},
|
||||||
|
{"type":"FeatureCollection","features":[
|
||||||
|
{"type":"Feature","geometry":{"type":"Point","coordinates":[0 -10]}},
|
||||||
|
{"type":"Feature","geometry":{"type":"Point","coordinates":[0 -11]}}
|
||||||
|
]},
|
||||||
|
{"type":"POLYGON","coordinates":[]},
|
||||||
|
{"type":"POINT","coordinates":[]},
|
||||||
|
{"type":"POINT","coordinates":[1000,2]},
|
||||||
|
{"type":"POINT","coordinates":[]},
|
||||||
|
{"type":"LINESTRING","coordinates":[[3,4], [-1,-3], [[-20,15,18]]]},
|
||||||
|
{"type":"GeometryCollection","geometries":[]},
|
||||||
|
{"type":"GeometryCollection","geometries":[
|
||||||
|
{"type":"Point","coordinates":[-1000]},
|
||||||
|
{"type":"Polygon","coordinates":[[[10,20],[-50,1500]]]}
|
||||||
|
],
|
||||||
|
]}`, "[-1000 -11 17],[1000 1500 18]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRandom(t *testing.T) {
|
||||||
|
buf := make([]byte, 50)
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
for i := 0; i < 10000000; i++ {
|
||||||
|
rand.Read(buf)
|
||||||
|
Get(string(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGet() {
|
||||||
|
r := Get(`{
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
||||||
|
[100.0, 1.0], [100.0, 0.0] ]
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
fmt.Printf("%v %v\n", r.Min, r.Max)
|
||||||
|
// Output:
|
||||||
|
// [100 0] [101 1]
|
||||||
|
}
|
Loading…
Reference in New Issue