github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/model/geo_polygon.go (about) 1 package model 2 3 import ( 4 "math" 5 ) 6 7 // A GeoPolygon is carved out of a 2D plane by a set of (possibly disjoint) contours. 8 // It can thus contain holes, and can be self-intersecting. 9 type GeoPolygon struct { 10 points []*GeoPoint 11 } 12 13 // NewPolygon: Creates and returns a new pointer to a GeoPolygon 14 // composed of the passed in points. Points are 15 // considered to be in order such that the last GeoPoint 16 // forms an edge with the first GeoPoint. 17 func NewPolygon(points []*GeoPoint) *GeoPolygon { 18 return &GeoPolygon{points: points} 19 } 20 21 // Points returns the points of the current GeoPolygon. 22 func (p *GeoPolygon) Points() []*GeoPoint { 23 return p.points 24 } 25 26 // Add: Appends the passed in contour to the current GeoPolygon. 27 func (p *GeoPolygon) Add(GeoPoint *GeoPoint) { 28 p.points = append(p.points, GeoPoint) 29 } 30 31 // IsClosed returns whether or not the GeoPolygon is closed. 32 // TODO: This can obviously be improved, but for now, 33 // this should be sufficient for detecting if points 34 // are contained using the raycast algorithm. 35 func (p *GeoPolygon) IsClosed() bool { 36 if len(p.points) < 3 { 37 return false 38 } 39 40 return true 41 } 42 43 // Contains returns whether or not the current GeoPolygon contains the passed in GeoPoint. 44 func (p *GeoPolygon) Contains(GeoPoint *GeoPoint) bool { 45 if !p.IsClosed() { 46 return false 47 } 48 49 start := len(p.points) - 1 50 end := 0 51 52 contains := p.intersectsWithRaycast(GeoPoint, p.points[start], p.points[end]) 53 54 for i := 1; i < len(p.points); i++ { 55 if p.intersectsWithRaycast(GeoPoint, p.points[i-1], p.points[i]) { 56 contains = !contains 57 } 58 } 59 60 return contains 61 } 62 63 // Using the raycast algorithm, this returns whether or not the passed in GeoPoint 64 // Intersects with the edge drawn by the passed in start and end points. 65 // Original implementation: http://rosettacode.org/wiki/Ray-casting_algorithm#Go 66 func (p *GeoPolygon) intersectsWithRaycast(GeoPoint *GeoPoint, start *GeoPoint, end *GeoPoint) bool { 67 // Always ensure that the the first GeoPoint 68 // has a y coordinate that is less than the second GeoPoint 69 if start.Lng > end.Lng { 70 71 // Switch the points if otherwise. 72 start, end = end, start 73 74 } 75 76 // Move the GeoPoint's y coordinate 77 // outside of the bounds of the testing region 78 // so we can start drawing a ray 79 for GeoPoint.Lng == start.Lng || GeoPoint.Lng == end.Lng { 80 newLng := math.Nextafter(GeoPoint.Lng, math.Inf(1)) 81 GeoPoint = NewPoint(GeoPoint.Lat, newLng) 82 } 83 84 // If we are outside of the GeoPolygon, indicate so. 85 if GeoPoint.Lng < start.Lng || GeoPoint.Lng > end.Lng { 86 return false 87 } 88 89 if start.Lat > end.Lat { 90 if GeoPoint.Lat > start.Lat { 91 return false 92 } 93 if GeoPoint.Lat < end.Lat { 94 return true 95 } 96 97 } else { 98 if GeoPoint.Lat > end.Lat { 99 return false 100 } 101 if GeoPoint.Lat < start.Lat { 102 return true 103 } 104 } 105 106 raySlope := (GeoPoint.Lng - start.Lng) / (GeoPoint.Lat - start.Lat) 107 diagSlope := (end.Lng - start.Lng) / (end.Lat - start.Lat) 108 109 return raySlope >= diagSlope 110 }