@@ -2,16 +2,15 @@ package measurement
2
2
3
3
import (
4
4
"errors"
5
- "math"
6
-
7
5
"github.com/tomchavakis/turf-go/constants"
8
6
"github.com/tomchavakis/turf-go/conversions"
9
7
"github.com/tomchavakis/turf-go/geojson"
10
8
"github.com/tomchavakis/turf-go/geojson/feature"
11
9
"github.com/tomchavakis/turf-go/geojson/geometry"
12
10
"github.com/tomchavakis/turf-go/internal/common"
13
11
"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"
15
14
)
16
15
17
16
// 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
744
743
745
744
return dist
746
745
}
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