Skip to content

Commit

Permalink
added area and perimeter
Browse files Browse the repository at this point in the history
  • Loading branch information
akhenakh committed Feb 19, 2024
1 parent a78b649 commit 9d70f43
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 17 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ tgo
---
![Tests](https://github.com/akhenakh/tgo/actions/workflows/build.yml/badge.svg)

[![GoDoc](https://pkg.go.dev/badge/github.com/akhenakh/tgp)](https://pkg.go.dev/github.com/akhenakh/tgo)
[![GoDoc](https://pkg.go.dev/badge/github.com/akhenakh/tgo)](https://pkg.go.dev/github.com/akhenakh/tgo)


Go bindings for [tidwall/tg](https://github.com/tidwall/tg) Geometry library for C - Fast point-in-polygon

Expand Down Expand Up @@ -67,4 +68,4 @@ if g.Types() == tgo.Polygon() {

## Tests

Some tests are borrowed from [simplefeatures](https://github.com/peterstace/simplefeatures).
Some tests are borrowed from [simplefeatures](https://github.com/peterstace/simplefeatures).
16 changes: 16 additions & 0 deletions poly.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,55 @@ type MultiPoly struct {
cg *C.struct_tg_geom
}

// AsGeom returns a Geom of the polygon without cloning.
func (p *Poly) AsGeom() *Geom {
cg := (*C.struct_tg_geom)(unsafe.Pointer(p.cp))
return &Geom{
cg: cg,
}
}

// IsClockWise returns true if the polygon is clock wise.
func (p *Poly) IsClockWise() bool {
return bool(C.tg_poly_clockwise(p.cp))
}

// AsText returns the representation of the poly as WKT.
func (p *Poly) AsText() string {
return p.AsGeom().AsText()
}

// HolesCount returns the holes count.
func (p *Poly) HolesCount() int {
return int(C.tg_poly_num_holes(p.cp))
}

// Exterior returns the exterior Ring of the poly.
func (p *Poly) Exterior() *Ring {
cr := C.tg_poly_exterior(p.cp)
return &Ring{
cr: cr,
}
}

// AsGeom returns a Geom of the multipolygon without cloning.
func (mp *MultiPoly) AsGeom() *Geom {
return &Geom{
cg: mp.cg,
}
}

// AsText returns the representation of the multipoly as WKT.
func (mp *MultiPoly) AsText() string {
return mp.AsGeom().AsText()
}

// PolygonsCount returns the count of polygons in the multipoly.
func (mp *MultiPoly) PolygonsCount() int {
return int(C.tg_geom_num_polys(mp.cg))
}

// PolygonAt returns the Poly at index, true if it's applicable.
func (mp *MultiPoly) PolygonAt(index int) (*Poly, bool) {
if index < 0 || index+1 > mp.PolygonsCount() {
return nil, false
Expand Down
15 changes: 15 additions & 0 deletions ring.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ func (r *Ring) AsGeom() *Geom {
}
}

func (r *Ring) AsPoly() *Poly {
cp := (*C.struct_tg_poly)(unsafe.Pointer(r.cr))
return &Poly{
cp: cp,
}
}

func (r *Ring) AsText() string {
return r.AsGeom().AsText()
}

func (r *Ring) Area() float64 {
return float64(C.tg_ring_area(r.cr))
}

func (r *Ring) Perimeter() float64 {
return float64(C.tg_ring_perimeter(r.cr))
}
57 changes: 57 additions & 0 deletions ring_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package tgo

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestRing_Perimeter(t *testing.T) {
tests := []struct {
name string
data string
want float64
}{
{
"polygon",
`POLYGON((0 0, 40 0, 40 40, 0 40, 0 0))`,
160,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g, err := UnmarshalWKT(tt.data)
require.NoError(t, err)
p, valid := g.AsPoly()
require.True(t, valid)
r := p.Exterior()
require.Equal(t, tt.want, r.Perimeter())
})
}
}

func TestRing_Area(t *testing.T) {
tests := []struct {
name string
data string
want float64
}{
{
"polygon",
`POLYGON((0 0, 40 0, 40 40, 0 40, 0 0))`,
1600,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g, err := UnmarshalWKT(tt.data)
require.NoError(t, err)
p, valid := g.AsPoly()
require.True(t, valid)
r := p.Exterior()
require.Equal(t, tt.want, r.Area())
})
}
}
41 changes: 36 additions & 5 deletions tg.c
Original file line number Diff line number Diff line change
Expand Up @@ -4802,6 +4802,12 @@ static size_t geom_memsize(const struct tg_geom *geom) {
for (int i = 0; i < geom->multi->ngeoms; i++) {
size += tg_geom_memsize(geom->multi->geoms[i]);
}
if (geom->multi->index) {
size += geom->multi->index->memsz;
}
if (geom->multi->ixgeoms) {
size += geom->multi->ngeoms*sizeof(int);
}
}
break;
}
Expand Down Expand Up @@ -13505,13 +13511,15 @@ struct tg_geom *tg_parse_hex(const char *hex) {
return tg_parse_hexn_ix(hex, hex?strlen(hex):0, TG_DEFAULT);
}

static double ring_area(const struct tg_ring *ring) {
/// Calculate the area of a ring.
double tg_ring_area(const struct tg_ring *ring) {
if (tg_ring_empty(ring)) return 0;
// The ring area has already been calculated by process_points.
return ring->area;
}

static double ring_perimeter(const struct tg_ring *ring) {
/// Calculate the perimeter length of a ring.
double tg_ring_perimeter(const struct tg_ring *ring) {
if (tg_ring_empty(ring)) return 0;
int nsegs = tg_ring_num_segments(ring);
double perim = 0;
Expand All @@ -13532,8 +13540,8 @@ double tg_ring_polsby_popper_score(const struct tg_ring *ring) {
// and all other shapes will be smaller. Itty bitty scores mean the
// polygon is really something nuts or has bad data.
double score = 0.0;
double perim = ring_perimeter(ring);
double area = ring_area(ring);
double perim = tg_ring_perimeter(ring);
double area = tg_ring_area(ring);
if (perim > 0) {
score = (area * M_PI * 4) / (perim * perim);
}
Expand Down Expand Up @@ -14079,14 +14087,15 @@ static struct tg_geom *geom_copy(const struct tg_geom *geom) {
goto fail;
}
memset(geom2->multi, 0, sizeof(struct multi));
geom2->multi->rect = geom->multi->rect;
if (geom->multi->geoms) {
size_t gsize = sizeof(struct tg_geom*)*geom->multi->ngeoms;
geom2->multi->geoms = tg_malloc(gsize);
if (!geom2->multi->geoms) {
goto fail;
}
geom2->multi->ngeoms = geom->multi->ngeoms;
memset(geom2->multi->geoms, 0, gsize);
geom2->multi->ngeoms = geom->multi->ngeoms;
for (int i = 0; i < geom->multi->ngeoms; i++) {
const struct tg_geom *child = geom->multi->geoms[i];
geom2->multi->geoms[i] = tg_geom_copy(child);
Expand All @@ -14095,6 +14104,23 @@ static struct tg_geom *geom_copy(const struct tg_geom *geom) {
}
}
}
if (geom->multi->index) {
geom2->multi->index = tg_malloc(geom->multi->index->memsz);
if (!geom2->multi->index) {
goto fail;
}
memcpy(geom2->multi->index, geom->multi->index,
geom->multi->index->memsz);
}
if (geom->multi->ixgeoms) {
geom2->multi->ixgeoms = tg_malloc(
geom->multi->ngeoms*sizeof(int));
if (!geom2->multi->ixgeoms) {
goto fail;
}
memcpy(geom2->multi->ixgeoms, geom->multi->ixgeoms,
geom->multi->ngeoms*sizeof(int));
}
}
break;
}
Expand Down Expand Up @@ -14239,3 +14265,8 @@ void tg_geom_search(const struct tg_geom *geom, struct tg_rect rect,
multi_index_search(multi, rect, 0, 0, iter, udata);
}
}

/// Calculate the length of a line.
double tg_line_length(const struct tg_line *line) {
return tg_ring_perimeter((struct tg_ring*)line);
}
10 changes: 6 additions & 4 deletions tg.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ bool tg_rect_intersects_point(struct tg_rect a, struct tg_point b);
/// must upcast the ring to a tg_geom, like such:
///
/// ```
/// tg_geom_interects((struct tg_geom*)ring, geom);
/// tg_geom_intersects((struct tg_geom*)ring, geom);
/// ```
/// @{
struct tg_ring *tg_ring_new(const struct tg_point *points, int npoints);
Expand Down Expand Up @@ -268,7 +268,8 @@ void tg_ring_ring_search(const struct tg_ring *a, const struct tg_ring *b,
bool (*iter)(struct tg_segment aseg, int aidx, struct tg_segment bseg,
int bidx, void *udata),
void *udata);

double tg_ring_area(const struct tg_ring *ring);
double tg_ring_perimeter(const struct tg_ring *ring);
/// @}

/// @defgroup LineFuncs Line functions
Expand All @@ -279,7 +280,7 @@ void tg_ring_ring_search(const struct tg_ring *a, const struct tg_ring *b,
/// must upcast the line to a tg_geom, like such:
///
/// ```
/// tg_geom_interects((struct tg_geom*)line, geom);
/// tg_geom_intersects((struct tg_geom*)line, geom);
/// ```
/// @{
struct tg_line *tg_line_new(const struct tg_point *points, int npoints);
Expand Down Expand Up @@ -308,6 +309,7 @@ void tg_line_line_search(const struct tg_line *a, const struct tg_line *b,
bool (*iter)(struct tg_segment aseg, int aidx, struct tg_segment bseg,
int bidx, void *udata),
void *udata);
double tg_line_length(const struct tg_line *line);
/// @}

/// @defgroup PolyFuncs Polygon functions
Expand All @@ -318,7 +320,7 @@ void tg_line_line_search(const struct tg_line *a, const struct tg_line *b,
/// must upcast the poly to a tg_geom, like such:
///
/// ```
/// tg_geom_interects((struct tg_geom*)poly, geom);
/// tg_geom_intersects((struct tg_geom*)poly, geom);
/// ```
/// @{
struct tg_poly *tg_poly_new(const struct tg_ring *exterior, const struct tg_ring *const holes[], int nholes);
Expand Down
28 changes: 22 additions & 6 deletions tgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,19 @@ const (
type IndexType uint32

const (
None IndexType = iota + 1
Natural
YStripes
None IndexType = iota + 1 // no indexing available, or disabled
Natural // indexing with natural ring order, for rings/lines
YStripes // indexing using segment striping, rings only
)

// UnmarshalWKT parses geometries from a WKT representation.
// Using the Natural indexation.
func UnmarshalWKT(data string) (*Geom, error) {
return UnmarshalWKTAndIndex(data, None)
return UnmarshalWKTAndIndex(data, Natural)
}

// UnmarshalWKTAndIndex parses geometries from a WKT representation,
// and sets the indexation type.
func UnmarshalWKTAndIndex(data string, idxt IndexType) (*Geom, error) {
cd := C.CString(data)
defer C.free(unsafe.Pointer(cd))
Expand All @@ -97,10 +101,14 @@ func UnmarshalWKTAndIndex(data string, idxt IndexType) (*Geom, error) {
return g, nil
}

// UnmarshalWKB parses geometries from a WKB representation.
// Using the Natural indexation.
func UnmarshalWKB(data []byte) (*Geom, error) {
return UnmarshalWKBAndIndex(data, None)
return UnmarshalWKBAndIndex(data, Natural)
}

// UnmarshalWKBAndIndex parses geometries from a WKB representation,
// and sets the indexation type.
func UnmarshalWKBAndIndex(data []byte, idxt IndexType) (*Geom, error) {
if len(data) == 0 {
return nil, errors.New("empty data")
Expand All @@ -121,10 +129,14 @@ func UnmarshalWKBAndIndex(data []byte, idxt IndexType) (*Geom, error) {
return g, nil
}

// UnmarshalGeoJSON parses geometries from a GeoJSON representation.
// Using the Natural indexation.
func UnmarshalGeoJSON(data []byte) (*Geom, error) {
return UnmarshalGeoJSONAndIndex(data, None)
return UnmarshalGeoJSONAndIndex(data, Natural)
}

// UnmarshalGeoJSONAndIndex parses geometries from a GeoJSON representation,
// and sets the indexation type.
func UnmarshalGeoJSONAndIndex(data []byte, idxt IndexType) (*Geom, error) {
if len(data) == 0 {
return nil, errors.New("empty data")
Expand All @@ -145,6 +157,7 @@ func UnmarshalGeoJSONAndIndex(data []byte, idxt IndexType) (*Geom, error) {
return g, nil
}

// Equals returns true if the two geometries are equal
func Equals(g1, g2 *Geom) bool {
return bool(C.tg_geom_equals(g1.cg, g2.cg))
}
Expand Down Expand Up @@ -215,6 +228,7 @@ func (g *Geom) StabOne(x, y float64) *Geom {
// C.tg_ring_free(r)
// }

// AsPoly returns a Poly of the geometry, returns false if not applicable.
func (g *Geom) AsPoly() (*Poly, bool) {
cp := C.tg_geom_poly(g.cg)

Expand All @@ -226,6 +240,7 @@ func (g *Geom) AsPoly() (*Poly, bool) {
return p, true
}

// AsMultiPoly returns a MultiPoly of the geometry, returns false if not applicable.
func (g *Geom) AsMultiPoly() (*MultiPoly, bool) {
if g.Type() != MultiPolygon {
return nil, false
Expand Down Expand Up @@ -253,6 +268,7 @@ func (g *Geom) AsText() string {
return C.GoString((*C.char)(cwkt))
}

// Type returns the geometry type.
func (g *Geom) Type() GeomType {
switch C.tg_geom_typeof(g.cg) {
case C.TG_POINT:
Expand Down

0 comments on commit 9d70f43

Please sign in to comment.