Skip to content

Commit eec52ad

Browse files
committed
Add measurement for Point On Feature
1 parent d242c16 commit eec52ad

File tree

2 files changed

+504
-5
lines changed

2 files changed

+504
-5
lines changed

measurement/measurement.go

+205-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ package measurement
22

33
import (
44
"errors"
5-
"math"
6-
75
"github.com/tomchavakis/turf-go/constants"
86
"github.com/tomchavakis/turf-go/conversions"
97
"github.com/tomchavakis/turf-go/geojson"
108
"github.com/tomchavakis/turf-go/geojson/feature"
119
"github.com/tomchavakis/turf-go/geojson/geometry"
1210
"github.com/tomchavakis/turf-go/internal/common"
1311
"github.com/tomchavakis/turf-go/invariant"
14-
meta "github.com/tomchavakis/turf-go/meta/coordAll"
12+
"github.com/tomchavakis/turf-go/meta/coordAll"
13+
"math"
1514
)
1615

1716
// Distance calculates the distance between two points in kilometers. This uses the Haversine formula
@@ -744,3 +743,206 @@ func calculateRhumbDistance(origin []float64, destination []float64, radius *flo
744743

745744
return dist
746745
}
746+
747+
// PointOnFeature takes a Feature and returns a point guaranteed to be on the surface of the feature.
748+
func PointOnFeature(f feature.Feature, properties map[string]interface{}, id string) (*geometry.Point, error) {
749+
fs := []feature.Feature{}
750+
fs = append(fs, f)
751+
fc, err := feature.NewFeatureCollection(fs)
752+
if err != nil {
753+
return nil, err
754+
}
755+
return PointOnFeatureCollection(*fc, properties, id)
756+
}
757+
758+
// PointOnFeatureCollection gets a feature collection and returns a point guaranteed to be on the surface of the feature.
759+
func PointOnFeatureCollection(fc feature.Collection, properties map[string]interface{}, id string) (*geometry.Point, error) {
760+
centroid, err := CentroidFeatureCollection(fc, properties, id)
761+
if err != nil {
762+
return nil, err
763+
}
764+
cent, err := centroid.ToPoint()
765+
var onSurface = false
766+
767+
for i := 0; !onSurface && i < len(fc.Features); i++ {
768+
var geom = fc.Features[i].Geometry
769+
onSurface = pointOnFeatureCollection(geom, *cent)
770+
}
771+
772+
return cent, nil
773+
}
774+
775+
func pointOnFeatureCollection(g geometry.Geometry, cent geometry.Point) bool {
776+
var x, y, x1, y1, x2, y2 float64
777+
var onBorderOrPoint = false
778+
var onSurface = false
779+
780+
switch g.GeoJSONType {
781+
case geojson.Point:
782+
point, err := g.ToPoint()
783+
if err != nil {
784+
return false
785+
}
786+
if cent.Lat == point.Lat && cent.Lng == point.Lng {
787+
onSurface = true
788+
}
789+
case geojson.MultiPoint:
790+
var onMultiPoint = false
791+
points, err := g.ToMultiPoint()
792+
if err != nil {
793+
return false
794+
}
795+
796+
for k := 0; !onMultiPoint && k < len(points.Coordinates); k++ {
797+
if cent.Lat == points.Coordinates[k].Lat && cent.Lng == points.Coordinates[k].Lng {
798+
onSurface = true
799+
onMultiPoint = true
800+
}
801+
}
802+
803+
onBorderOrPoint = onMultiPoint
804+
case geojson.LineString:
805+
lineString, err := g.ToLineString()
806+
if err != nil {
807+
return false
808+
}
809+
for k := 0; !onBorderOrPoint && k < (len(lineString.Coordinates) - 1); k++ {
810+
x = cent.Lat
811+
y = cent.Lng
812+
x1 = lineString.Coordinates[k].Lat
813+
y1 = lineString.Coordinates[k].Lng
814+
x2 = lineString.Coordinates[k + 1].Lat
815+
y2 = lineString.Coordinates[k + 1].Lng
816+
if pointOnSegment(x, y, x1, y1, x2, y2) {
817+
onBorderOrPoint = true
818+
onSurface = true
819+
}
820+
}
821+
case geojson.MultiLineString:
822+
lineStrings, err := g.ToMultiLineString()
823+
if err != nil {
824+
return false
825+
}
826+
for j:= 0; j < len(lineStrings.Coordinates); j++ {
827+
onBorderOrPoint = false
828+
var line = lineStrings.Coordinates[j];
829+
for k := 0; !onBorderOrPoint && k < len(line.Coordinates) - 1; k++ {
830+
x = cent.Lat
831+
y = cent.Lng
832+
x1 = line.Coordinates[k].Lat;
833+
y1 = line.Coordinates[k].Lng;
834+
x2 = line.Coordinates[k + 1].Lat;
835+
y2 = line.Coordinates[k + 1].Lng;
836+
if pointOnSegment(x, y, x1, y1, x2, y2) {
837+
onBorderOrPoint = true
838+
onSurface = true
839+
}
840+
}
841+
}
842+
case geojson.Polygon:
843+
if pointOnPolygon(cent, g) {
844+
onSurface = true
845+
}
846+
case geojson.MultiPolygon:
847+
if pointOnPolygon(cent, g) {
848+
onSurface = true
849+
}
850+
}
851+
return onSurface || onBorderOrPoint
852+
}
853+
854+
func pointOnSegment(x float64, y float64, x1 float64, y1 float64, x2 float64, y2 float64) bool {
855+
var ab = math.Sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
856+
var ap = math.Sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1))
857+
var pb = math.Sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y))
858+
return ab == (ap + pb)
859+
}
860+
861+
func pointOnPolygon(point geometry.Point, g geometry.Geometry) bool {
862+
switch g.GeoJSONType {
863+
case geojson.Polygon:
864+
var coords []geometry.Point
865+
polygon, err := g.ToPolygon()
866+
if err != nil {
867+
return false
868+
}
869+
for i:=0; i < len(polygon.Coordinates); i++ {
870+
coords = append(coords, polygon.Coordinates[i].Coordinates...)
871+
}
872+
bbox := bboxCalculator(coords)
873+
874+
if len(bbox) == 0 || !inBBox(point, bbox) {
875+
return false
876+
}
877+
return pointOnMultipolygon(point, []geometry.Polygon{*polygon})
878+
case geojson.MultiPolygon:
879+
multiPolygon, err := g.ToMultiPolygon()
880+
if err != nil {
881+
return false
882+
}
883+
bbox, err := BBox(multiPolygon)
884+
if err != nil || len(bbox) == 0 || !inBBox(point, bbox) {
885+
return false
886+
}
887+
return pointOnMultipolygon(point, multiPolygon.Coordinates)
888+
}
889+
890+
return false
891+
}
892+
893+
func pointOnMultipolygon(point geometry.Point, polys []geometry.Polygon) bool {
894+
895+
insidePoly := false;
896+
for i := 0; i < len(polys) && !insidePoly; i++ {
897+
// check if it is in the outer ring first
898+
if inRing(point, polys[i].Coordinates[0].Coordinates, false) {
899+
inHole := false;
900+
// check for the point in any of the holes
901+
for j := 1; j < len(polys[i].Coordinates) && !inHole; j++ {
902+
if inRing(point, polys[i].Coordinates[j].Coordinates, false) {
903+
inHole = true;
904+
}
905+
}
906+
if !inHole {
907+
insidePoly = true;
908+
}
909+
}
910+
}
911+
912+
return insidePoly
913+
}
914+
915+
func inBBox(point geometry.Point, bbox []float64) bool {
916+
return bbox[0] <= point.Lng && bbox[1] <= point.Lat && bbox[2] >= point.Lng && bbox[3] >= point.Lat
917+
}
918+
919+
func inRing(point geometry.Point, ring []geometry.Point, ignoreBoundary bool) bool {
920+
isInside := false
921+
// remove last element if they are the same
922+
if ring[0].Lat == ring[len(ring) - 1].Lat && ring[0].Lng == ring[len(ring) - 1].Lng {
923+
ring = ring[:len(ring) - 1]
924+
}
925+
926+
for i, j := 0, len(ring) - 1 ; i < len(ring); i, j = i+1, i {
927+
xi := ring[i].Lng
928+
yi := ring[i].Lat
929+
xj := ring[j].Lng
930+
yj := ring[j].Lat
931+
932+
onBoundary := point.Lat * (xi - xj) + yi * (xj - point.Lng) + yj * (point.Lng - xi) == 0 &&
933+
(xi - point.Lng) * (xj - point.Lng) <= 0 &&
934+
(yi - point.Lat) * (yj - point.Lat) <= 0
935+
936+
if onBoundary {
937+
return !ignoreBoundary
938+
}
939+
940+
intersect := (yi > point.Lat) != (yj > point.Lat) &&
941+
(point.Lng < ((xj - xi) * (point.Lat - yi)) / (yj - yi) + xi)
942+
943+
if intersect {
944+
isInside = !isInside
945+
}
946+
}
947+
return isInside
948+
}

0 commit comments

Comments
 (0)