Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
94c3983
[quadedge] Removed hard coded inifinite loop panic
gdey Nov 20, 2019
e23bd4e
[quadedge] moved to using winding package
gdey Nov 20, 2019
85ed906
[quadeedge-tests] Got validate tests to pass
gdey Nov 20, 2019
ed72cb9
[quadedge-testcase] fixed the winding order of some tests
gdey Nov 20, 2019
04a8a0a
[resolve edge] Winding order and resolve edge was not correct
gdey Nov 20, 2019
071f052
[cmp] Added Line cmpare routines
gdey Nov 20, 2019
19d3dd9
[subdivision] Log and comment cleanup, and test for New
gdey Nov 20, 2019
9c40695
[subdivision] Added tests and test cases
gdey Nov 20, 2019
1e184f8
[quadedge-testcases] updated the quadedge to not use ydown and update…
gdey Nov 20, 2019
255c5ab
[resolved_edge] plumbed through order
gdey Nov 21, 2019
8dee10c
[resolve_edge] get test for resolved_edge to 100
gdey Nov 21, 2019
9be3a1a
[winding] test xproduct
gdey Nov 21, 2019
63349cc
[quadedge] refactor
gdey Nov 21, 2019
fa4ecbd
Made sure all carses are tested for in reslove_edge
gdey Nov 21, 2019
5383a19
fixed spelling
gdey Nov 21, 2019
f35719b
Made sure splice works correctly
gdey Nov 22, 2019
39304f0
added documentation to subdivision code base
gdey Nov 22, 2019
ebb8cec
[quadedge] fixed winding of ONext and OPrev detection
gdey Nov 23, 2019
899694a
[subdivision] fixed RightOf to take a y-flip parameter
gdey Nov 23, 2019
b668ae1
[subdivision] Fixed RightOf and no longer update starting edge
gdey Nov 25, 2019
cdfb643
[quadedge] increased precision for checking on line
gdey Nov 25, 2019
9d3ae49
[fixed up tests] Fixed up tests so that they pass
gdey Nov 29, 2019
c88b0a9
Finished moving things to internal/must
gdey Dec 6, 2019
1986e93
TestSubdivision failing because of winding order correction.
gdey Dec 9, 2019
819fb98
Fixed issue with Duplicated points on insert.
gdey Dec 11, 2019
f898533
[mvt] fix encoder tests and added decoder
ear7h Sep 28, 2019
d8916fb
removed unused context import
gdey Dec 11, 2019
e3bd53b
got the final bits of tests passing.
gdey Dec 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions circle.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,42 @@ func CircleFromPoints(a, b, c [2]float64) (Circle, error) {
r := math.Sqrt((vA * vA) + (vB * vB))
return Circle{
Center: [2]float64{x, y},
Radius: RoundToPrec(r, 4),
Radius: r,
//Radius: RoundToPrec(r, 4),
}, nil
}

func cmpFloat(f1, f2 float64) bool {
bitTolerance := int64(math.Float64bits(1.0+tolerance) - math.Float64bits(1.0))
// handle infinity
if math.IsInf(f1, 0) || math.IsInf(f2, 0) {
return math.IsInf(f1, -1) == math.IsInf(f2, -1) &&
math.IsInf(f1, 1) == math.IsInf(f2, 1)
}

// -0.0 exist but -0.0 == 0.0 is true
if f1 == 0 || f2 == 0 {
return math.Abs(f2-f1) < tolerance
}

i1 := int64(math.Float64bits(f1))
i2 := int64(math.Float64bits(f2))
d := i2 - i1

if d < 0 {
return d > -bitTolerance
}
return d < bitTolerance
}

// ContainsPoint will check to see if the point is in the circle.
func (c Circle) ContainsPoint(pt [2]float64) bool {
// get the distance between the center and the point, and if it's greater then the radius it's outside
// of the circle.
v1, v2 := c.Center[0]-pt[0], c.Center[1]-pt[1]
d := math.Sqrt((v1 * v1) + (v2 * v2))
d = RoundToPrec(d, 3)
return c.Radius >= d
//d = RoundToPrec(d, 3)
return cmpFloat(c.Radius, d) || c.Radius > d
}

func (c Circle) AsPoints(k uint) []Point {
Expand Down
10 changes: 5 additions & 5 deletions circle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,23 @@ func TestCircleFromPoints(t *testing.T) {
},
"center outside of triangle": {
p: [3][2]float64{{1, 0}, {10, 20}, {5, 5}},
circle: geom.Circle{Center: [2]float64{-21.642857, 22.214286}, Radius: 31.7202},
circle: geom.Circle{Center: [2]float64{-21.642857142857142, 22.214285714285715}, Radius: 31.72023753674861},
},
"center outside of triangle 1": {
p: [3][2]float64{{1, 0}, {5, 5}, {10, 20}},
circle: geom.Circle{Center: [2]float64{-21.642857, 22.214286}, Radius: 31.7202},
circle: geom.Circle{Center: [2]float64{-21.642857142857142, 22.214285714285715}, Radius: 31.72023753674861},
},
"center outside of triangle 2": {
p: [3][2]float64{{5, 5}, {1, 0}, {10, 20}},
circle: geom.Circle{Center: [2]float64{-21.642857, 22.214286}, Radius: 31.7202},
circle: geom.Circle{Center: [2]float64{-21.642857142857142, 22.214285714285715}, Radius: 31.72023753674861},
},
"center right triangle": {
p: [3][2]float64{{1, 0}, {10, 0}, {10, 7}},
circle: geom.Circle{Center: [2]float64{5.5, 3.5}, Radius: 5.7009},
circle: geom.Circle{Center: [2]float64{5.5, 3.5}, Radius: 5.70087712549569},
},
"center right triangle 1": {
p: [3][2]float64{{10, 0}, {1, 0}, {10, 7}},
circle: geom.Circle{Center: [2]float64{5.5, 3.5}, Radius: 5.7009},
circle: geom.Circle{Center: [2]float64{5.5, 3.5}, Radius: 5.70087712549569},
},
}
for name, tc := range tests {
Expand Down
12 changes: 10 additions & 2 deletions cmp/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,16 @@ func (cmp Compare) MultiPointerEqual(geo1, geo2 geom.MultiPointer) bool {
return cmp.MultiPointEqual(geo1.Points(), geo2.Points())
}

// LineStringerEqual will check to see if the two linestrings passed to it are equal, if
// there lengths are both the same, and the sequence of points are in the same order.
// LineEqual will check to see if the two lines passed to it are equal:
// 1. start points are equal
// 2. end points are equal
func (cmp Compare) LineEqual(ln1, ln2 geom.Line) bool {
return cmp.PointEqual(ln1[0], ln2[0]) && cmp.PointEqual(ln1[1], ln2[1])
}

// LineStringerEqual will check to see if the two linestrings passed to it are equal:
// 1. lengths of both the same
// 2. the sequence of points are in the same order.
// The points don't have to be in the same index point in both line strings.
func (cmp Compare) LineStringerEqual(geo1, geo2 geom.LineStringer) bool {
if geo1Nil, geo2Nil := geo1 == NilLineString, geo2 == NilLineString; geo1Nil || geo2Nil {
Expand Down
304 changes: 304 additions & 0 deletions encoding/mvt/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package mvt

import (
"errors"
"fmt"
"io"
"io/ioutil"

"github.com/arolek/p"
"github.com/go-spatial/geom"
vectorTile "github.com/go-spatial/geom/encoding/mvt/vector_tile"
"github.com/go-spatial/geom/winding"
"github.com/golang/protobuf/proto"
)

// TileGeomCollection returns all geometries in a tile
// as a collection
func TileGeomCollection(tile *Tile) geom.Collection {
ret := geom.Collection{}
for _, v := range tile.layers {
for _, vv := range v.features {
ret = append(ret, vv.Geometry)
}
}

return ret
}

// Decode reads all the data from r and decodes the MVT tile into a Tile
// TODO(ear7h): handle tile tags
func Decode(r io.Reader) (*Tile, error) {
byt, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}

return DecodeByte(byt)
}

// DecodeByte decodes the MVT encoded bytes into a Tile.
// TODO(ear7h): handle tile tags
func DecodeByte(b []byte) (*Tile, error) {
vtile := new(vectorTile.Tile)

err := proto.Unmarshal(b, vtile)
if err != nil {
return nil, err
}

ret := new(Tile)
ret.layers = make([]Layer, len(vtile.Layers))

for i, v := range vtile.Layers {
err = decodeLayer(v, &ret.layers[i])
if err != nil {
return nil, err
}
}

return ret, nil
}

func decodeLayer(pb *vectorTile.Tile_Layer, dst *Layer) error {
dst.Name = *pb.Name
dst.extent = p.Int(int(*pb.Extent))

dst.features = make([]Feature, len(pb.Features))

for i, v := range pb.Features {
err := decodeFeature(v, &dst.features[i])
if err != nil {
return err
}
}

return nil
}

func decodeFeature(pb *vectorTile.Tile_Feature, dst *Feature) error {
dst.ID = pb.Id
// TODO tag support
var err error
dst.Geometry, err = DecodeGeometry(*pb.Type, pb.Geometry)
return err
}

func DecodeGeometry(gtype vectorTile.Tile_GeomType, b []uint32) (geom.Geometry, error) {

switch gtype {
case vectorTile.Tile_LINESTRING:
return decodeLineString(b)
case vectorTile.Tile_POINT:
return decodePoint(b)
case vectorTile.Tile_POLYGON:
return decodePoly(b)
default:
panic("unreachable")
}
}

func decodePoint(buf []uint32) (geom.Geometry, error) {
ret := [][2]float64{}
curs := decodeCursor{}

if len(buf) > 0 {
cmd := Command(buf[0])
buf = buf[1:]

if len(buf) < cmd.Count()*2 {
return nil, fmt.Errorf("not enough integers (%v) for %s", len(buf), cmd)
}

switch cmd.ID() {
case cmdMoveTo:
ret = curs.decodeNPoints(cmd.Count(), buf, false)
buf = buf[cmd.Count()*2:]

default:
return nil, fmt.Errorf("invalid command for POINT, %s", cmd)
}
}

if len(buf) != 0 {
fmt.Println(buf)
return ret, ErrExtraData
}

switch len(ret) {
case 0:
return nil, nil
case 1:
return geom.Point(ret[0]), nil
default:
return geom.MultiPoint(ret), nil
}
}

var ErrExtraData = errors.New("mvt: invalid extra data")

func decodeLineString(buf []uint32) (geom.Geometry, error) {
ret := [][][2]float64{}
curs := decodeCursor{}
var lastCmd Command
var cmd Command

for ; len(buf) > 0; lastCmd = cmd {
cmd = Command(buf[0])
buf = buf[1:]

if len(buf) < cmd.Count()*2 {
return nil, fmt.Errorf("not enough integers (%v) for %s", len(buf), cmd)
}

switch cmd.ID() {
case cmdMoveTo:
if lastCmd != 0 && lastCmd.ID() != cmdLineTo {
return nil, fmt.Errorf("%v cannot follow %v for LINESTRING", cmd, lastCmd)
}

if cmd.Count() != 1 {
// return error
}

curs.decodePoint(buf[0], buf[1])
buf = buf[2:]

case cmdLineTo:
if lastCmd.ID() != cmdMoveTo {
return nil, fmt.Errorf("%v cannot follow %v for LINESTRING", cmd, lastCmd)
}

if cmd.Count() <= 0 {
return nil, fmt.Errorf("%v must have count > 0 for LINESTRING", cmd)
}

ln := curs.decodeNPoints(cmd.Count(), buf, true)
buf = buf[cmd.Count()*2:]
ret = append(ret, ln)

default:
return nil, fmt.Errorf("invalid command for LINESTRING, %s", cmd)
}
}

if len(buf) != 0 {
return ret, ErrExtraData
}

switch len(ret) {
case 0:
panic("unreachable")
case 1:
return geom.LineString(ret[0]), nil
default:
return geom.MultiLineString(ret), nil
}
}

func decodePoly(buf []uint32) (geom.Geometry, error) {
ret := [][][][2]float64{}
curs := decodeCursor{}
var lastCmd Command
var cmd Command

for ; len(buf) > 0; lastCmd = cmd {
cmd = Command(buf[0])
buf = buf[1:]

if cmd.ID() != cmdClosePath && len(buf) < cmd.Count()*2 {
return nil, fmt.Errorf("not enough integers (%v) for %s", len(buf), cmd)
}

switch cmd.ID() {
case cmdMoveTo:
if lastCmd != 0 && lastCmd.ID() != cmdClosePath {
return nil, fmt.Errorf("%v cannot follow %v for POLYGON", cmd, lastCmd)
}

if cmd.Count() != 1 {
// cannot be 1
}

curs.decodePoint(buf[0], buf[1])
buf = buf[2:]

case cmdLineTo:
if lastCmd.ID() != cmdMoveTo {
return nil, fmt.Errorf("%v cannot follow %v for POLYGON", cmd, lastCmd)
}

if cmd.Count() <= 1 {
return nil, fmt.Errorf("%v must have count > 1 for POLYGON", cmd)
}

ln := curs.decodeNPoints(cmd.Count(), buf, true)
buf = buf[cmd.Count()*2:]

if (winding.Order{YPositiveDown: true}).OfPoints(ln...).IsClockwise() {
ret = append(ret, nil)
} else if len(ret) == 0 {
return nil, fmt.Errorf("first ring of POLYGON must be an exterior ring")
}

polyIdx := len(ret) - 1
ret[polyIdx] = append(ret[polyIdx], ln)

case cmdClosePath:
if lastCmd.ID() != cmdLineTo {
return nil, fmt.Errorf("%v cannot follow %v for POLYGON", cmd, lastCmd)
}
}
}

if len(buf) != 0 {
return ret, ErrExtraData
}

switch len(ret) {
case 0:
panic("unreachable")
case 1:
return geom.Polygon(ret[0]), nil
default:
return geom.MultiPolygon(ret), nil
}
}

type decodeCursor struct {
x, y float64
}

// n and len(pts) should be error checked before this function
// this is for more informative context on errors
func (c *decodeCursor) decodeNPoints(n int, pts []uint32, encHere bool) [][2]float64 {
nd := 0

if encHere {
nd = 1
}

ret := make([][2]float64, n+nd)

if encHere {
ret[0] = [2]float64{c.x, c.y}
}

for i := 0; i < n; i++ {
ret[i+nd] = c.decodePoint(pts[i*2], pts[i*2+1])
}

return ret
}

// decodes the zig zag encoded uint32's, moves the cursor
func (c *decodeCursor) decodePoint(x, y uint32) [2]float64 {
c.x += float64(decodeZigZag(x))
c.y += float64(decodeZigZag(y))
return [2]float64{c.x, c.y}
}

// decodes the zig zag encoded int32
func decodeZigZag(i uint32) int32 {
return int32((i >> 1) ^ (-(i & 1)))
}
Loading