pax_global_header00006660000000000000000000000064140036355570014522gustar00rootroot0000000000000052 comment=e78a33a022b778c6b9d9c372d9a758f9e050ede3 grect-0.1.0/000077500000000000000000000000001400363555700126245ustar00rootroot00000000000000grect-0.1.0/LICENSE.md000066400000000000000000000020651400363555700142330ustar00rootroot00000000000000The 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. grect-0.1.0/README.md000066400000000000000000000007211400363555700141030ustar00rootroot00000000000000GRECT ==== 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). grect-0.1.0/go.mod000066400000000000000000000001221400363555700137250ustar00rootroot00000000000000module github.com/tidwall/grect go 1.15 require github.com/tidwall/gjson v1.6.7 grect-0.1.0/go.sum000066400000000000000000000007671400363555700137710ustar00rootroot00000000000000github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= grect-0.1.0/grect.go000066400000000000000000000144321400363555700142630ustar00rootroot00000000000000package 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 } grect-0.1.0/grect_test.go000066400000000000000000000065241400363555700153250ustar00rootroot00000000000000package 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] }