Skip to content

Commit 0bbcbae

Browse files
committed
Add check for polygons/multipolygons
1 parent 8b3e497 commit 0bbcbae

File tree

2 files changed

+248
-13
lines changed

2 files changed

+248
-13
lines changed

measurement/measurement.go

+80-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/tomchavakis/turf-go/geojson/geometry"
1212
"github.com/tomchavakis/turf-go/internal/common"
1313
"github.com/tomchavakis/turf-go/invariant"
14-
meta "github.com/tomchavakis/turf-go/meta/coordAll"
14+
"github.com/tomchavakis/turf-go/meta/coordAll"
1515
)
1616

1717
// Distance calculates the distance between two points in kilometers. This uses the Haversine formula
@@ -825,20 +825,10 @@ func pointOnFeatureCollection(t interface{}, cent geometry.Point) bool {
825825
}
826826
}
827827
case geometry.Polygon:
828-
for _, c := range gtp.Coordinates {
829-
//todo: decide if cent is on surface
830-
print(len(c.Coordinates))
831-
}
832-
onSurface = true
833828
case geometry.MultiPolygon:
834-
coords := gtp.Coordinates
835-
for _, coord := range coords {
836-
for _, pl := range coord.Coordinates {
837-
//todo: decide if cent is on surface
838-
print(len(pl.Coordinates))
839-
}
829+
if pointOnPolygon(cent, gtp) {
830+
onSurface = true
840831
}
841-
onSurface = true
842832
}
843833

844834
return onSurface || onBorderOrPoint
@@ -850,3 +840,80 @@ func pointOnSegment(x float64, y float64, x1 float64, y1 float64, x2 float64, y2
850840
var pb = math.Sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y))
851841
return ab == (ap + pb)
852842
}
843+
844+
func pointOnPolygon(point geometry.Point, t interface{}) bool {
845+
switch gpt := t.(type) {
846+
case geometry.Polygon:
847+
bbox, err := BBox(gpt)
848+
if err != nil || len(bbox) == 0 || !inBBox(point, bbox) {
849+
return false
850+
}
851+
return pointOnMultipolygon(point, []geometry.Polygon{gpt})
852+
case geometry.MultiPolygon:
853+
bbox, err := BBox(gpt)
854+
if err != nil || len(bbox) == 0 || !inBBox(point, bbox) {
855+
return false
856+
}
857+
return pointOnMultipolygon(point, gpt.Coordinates)
858+
}
859+
860+
return false
861+
}
862+
863+
func pointOnMultipolygon(point geometry.Point, polys []geometry.Polygon) bool {
864+
865+
insidePoly := false;
866+
for i := 0; i < len(polys) && !insidePoly; i++ {
867+
// check if it is in the outer ring first
868+
if inRing(point, polys[i].Coordinates[0].Coordinates, false) {
869+
inHole := false;
870+
// check for the point in any of the holes
871+
for j := 1; j < len(polys[i].Coordinates) && !inHole; j++ {
872+
if inRing(point, polys[i].Coordinates[j].Coordinates, false) {
873+
inHole = true;
874+
}
875+
}
876+
if !inHole {
877+
insidePoly = true;
878+
}
879+
}
880+
}
881+
882+
return insidePoly
883+
}
884+
885+
func inBBox(point geometry.Point, bbox []float64) bool {
886+
return bbox[0] <= point.Lng && bbox[1] <= point.Lat && bbox[2] >= point.Lng && bbox[3] >= point.Lat
887+
}
888+
889+
func inRing(point geometry.Point, ring []geometry.Point, ignoreBoundary bool) bool {
890+
isInside := false
891+
892+
// remove last element if they are the same
893+
if ring[0].Lat == ring[len(ring) - 1].Lat && ring[0].Lng == ring[len(ring) - 1].Lng {
894+
ring = ring[:len(ring) - 1]
895+
}
896+
897+
for i, j := 0, len(ring) - 1 ; i < len(ring); i, j = i+1, i {
898+
xi := ring[i].Lng
899+
yi := ring[i].Lat
900+
xj := ring[j].Lng
901+
yj := ring[j].Lat
902+
903+
onBoundary := point.Lat * (xi - xj) + yi * (xj - point.Lng) + yj * (point.Lng - xi) == 0 &&
904+
(xi - point.Lng) * (xj - point.Lng) <= 0 &&
905+
(yi - point.Lat) * (yj - point.Lat) <= 0
906+
907+
if onBoundary {
908+
return !ignoreBoundary
909+
}
910+
911+
intersect := (yi > point.Lat) != (yj > point.Lat) &&
912+
(point.Lng < ((xj - xi) * (point.Lat - yi)) / (yj - yi) + xi)
913+
914+
if intersect {
915+
isInside = !isInside
916+
}
917+
}
918+
return isInside
919+
}

measurement/measurement_test.go

+168
Original file line numberDiff line numberDiff line change
@@ -1560,3 +1560,171 @@ func TestRhumbDistance(t *testing.T) {
15601560
})
15611561
}
15621562
}
1563+
1564+
func TestInBBox(t *testing.T) {
1565+
tests := map[string]struct {
1566+
point geometry.Point
1567+
bbox []float64
1568+
want bool
1569+
}{
1570+
"point in bbox": {
1571+
point: geometry.Point{
1572+
Lng: 116.0,
1573+
Lat: -20.0,
1574+
},
1575+
bbox: []float64{
1576+
113.0, -39.0, 154.0, -15.0,
1577+
},
1578+
want: true,
1579+
},
1580+
"point on bbox": {
1581+
point: geometry.Point{
1582+
Lng: 116.0,
1583+
Lat: -20.0,
1584+
},
1585+
bbox: []float64{
1586+
116.0, -20.0, 154.0, -15.0,
1587+
},
1588+
want: true,
1589+
},
1590+
"point not in bbox": {
1591+
point: geometry.Point{
1592+
Lng: -20.0,
1593+
Lat: 116.0,
1594+
},
1595+
bbox: []float64{
1596+
113.0, -39.0, 154.0, -15.0,
1597+
},
1598+
want: false,
1599+
},
1600+
}
1601+
for name, tt := range tests {
1602+
t.Run(name, func(t *testing.T) {
1603+
assert.Equal(t, tt.want, inBBox(tt.point, tt.bbox))
1604+
})
1605+
}
1606+
}
1607+
1608+
1609+
func TestInRing(t *testing.T) {
1610+
tests := map[string]struct {
1611+
point geometry.Point
1612+
ring []geometry.Point
1613+
want bool
1614+
} {
1615+
"point in ring": {
1616+
point: geometry.Point{
1617+
Lng: 50.0,
1618+
Lat: 50.0,
1619+
},
1620+
ring: []geometry.Point {
1621+
{
1622+
Lng: 0.0,
1623+
Lat: 0.0,
1624+
},
1625+
{
1626+
Lng: 0.0,
1627+
Lat: 100.0,
1628+
},
1629+
{
1630+
Lng: 100.0,
1631+
Lat: 100.0,
1632+
},
1633+
{
1634+
Lng: 100.0,
1635+
Lat: 0.0,
1636+
},
1637+
{
1638+
Lng: 0.0,
1639+
Lat: 0.0,
1640+
},
1641+
},
1642+
want: true,
1643+
},
1644+
"point not in ring": {
1645+
point: geometry.Point{
1646+
Lng: -50.0,
1647+
Lat: -50.0,
1648+
},
1649+
ring: []geometry.Point {
1650+
{
1651+
Lng: 0.0,
1652+
Lat: 0.0,
1653+
},
1654+
{
1655+
Lng: 0.0,
1656+
Lat: 100.0,
1657+
},
1658+
{
1659+
Lng: 100.0,
1660+
Lat: 100.0,
1661+
},
1662+
{
1663+
Lng: 100.0,
1664+
Lat: 0.0,
1665+
},
1666+
{
1667+
Lng: 0.0,
1668+
Lat: 0.0,
1669+
},
1670+
},
1671+
want: false,
1672+
},
1673+
}
1674+
1675+
for name, tt := range tests {
1676+
t.Run(name, func(t *testing.T) {
1677+
assert.Equal(t, inRing(tt.point, tt.ring, false), tt.want)
1678+
})
1679+
}
1680+
}
1681+
1682+
1683+
func TestInPolygon(t *testing.T) {
1684+
tests := map[string]struct {
1685+
point geometry.Point
1686+
polygon geometry.Polygon
1687+
want bool
1688+
} {
1689+
"point in polygon": {
1690+
point: geometry.Point{
1691+
Lng: 2.0,
1692+
Lat: 102.0,
1693+
},
1694+
polygon: geometry.Polygon {
1695+
Coordinates: []geometry.LineString{
1696+
{
1697+
Coordinates: []geometry.Point{
1698+
{
1699+
Lat: 2.0,
1700+
Lng: 102.0,
1701+
},
1702+
{
1703+
Lat: 2.0,
1704+
Lng: 103.0,
1705+
},
1706+
{
1707+
Lat: 3.0,
1708+
Lng: 103.0,
1709+
},
1710+
{
1711+
Lat: 3.0,
1712+
Lng: 102.0,
1713+
},
1714+
{
1715+
Lat: 2.0,
1716+
Lng: 102.0,
1717+
},
1718+
},
1719+
},
1720+
},
1721+
},
1722+
want: true,
1723+
},
1724+
}
1725+
for name, tt := range tests {
1726+
t.Run(name, func(t *testing.T) {
1727+
assert.Equal(t, pointOnPolygon(tt.point, tt.polygon), tt.want)
1728+
})
1729+
}
1730+
}

0 commit comments

Comments
 (0)