github.com/liucxer/courier@v1.7.1/h3/geo_coord.go (about) 1 package h3 2 3 import ( 4 "math" 5 ) 6 7 /** epsilon of ~0.1mm in degrees */ 8 const EPSILON_DEG = .000000001 9 10 /** epsilon of ~0.1mm in radians */ 11 const EPSILON_RAD = (EPSILON_DEG * M_PI_180) 12 13 /** 14 * Normalizes radians to a value between 0.0 and two PI. 15 * 16 * @param rads The input radians value. 17 * @return The normalized radians value. 18 */ 19 func _posAngleRads(rads float64) float64 { 20 var tmp float64 21 22 if rads < 0.0 { 23 tmp = rads + M_2PI 24 } else { 25 tmp = rads 26 } 27 28 if rads >= M_2PI { 29 tmp -= M_2PI 30 } 31 return tmp 32 } 33 34 /** 35 * Determines if the components of two spherical coordinates are within some 36 * threshold distance of each other. 37 * 38 * @param p1 The first spherical coordinates. 39 * @param p2 The second spherical coordinates. 40 * @param threshold The threshold distance. 41 * @return Whether or not the two coordinates are within the threshold distance 42 * of each other. 43 */ 44 func geoAlmostEqualThreshold(p1 *GeoCoord, p2 *GeoCoord, threshold float64) bool { 45 return math.Abs(p1.Lat-p2.Lat) < threshold && math.Abs(p1.Lon-p2.Lon) < threshold 46 } 47 48 /** 49 * Determines if the components of two spherical coordinates are within our 50 * standard epsilon distance of each other. 51 * 52 * @param p1 The first spherical coordinates. 53 * @param p2 The second spherical coordinates. 54 * @return Whether or not the two coordinates are within the epsilon distance 55 * of each other. 56 */ 57 func geoAlmostEqual(p1 *GeoCoord, p2 *GeoCoord) bool { 58 return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD) 59 } 60 61 /** 62 * Set the components of spherical coordinates in decimal degrees. 63 * 64 * @param p The spherical coodinates. 65 * @param latDegs The desired latitidue in decimal degrees. 66 * @param lonDegs The desired longitude in decimal degrees. 67 */ 68 func setGeoDegs(p *GeoCoord, latDegs float64, lonDegs float64) { 69 _setGeoRads(p, degsToRads(latDegs), degsToRads(lonDegs)) 70 } 71 72 /** 73 * Set the components of spherical coordinates in radians. 74 * 75 * @param p The spherical coodinates. 76 * @param latRads The desired latitidue in decimal radians. 77 * @param lonRads The desired longitude in decimal radians. 78 */ 79 func _setGeoRads(p *GeoCoord, latRads float64, lonRads float64) { 80 p.Lat = latRads 81 p.Lon = lonRads 82 } 83 84 /** 85 * Convert from decimal degrees to radians. 86 * 87 * @param degrees The decimal degrees. 88 * @return The corresponding radians. 89 */ 90 func degsToRads(degrees float64) float64 { return degrees * M_PI_180 } 91 92 /** 93 * Convert from radians to decimal degrees. 94 * 95 * @param radians The radians. 96 * @return The corresponding decimal degrees. 97 */ 98 func radsToDegs(radians float64) float64 { return radians * M_180_PI } 99 100 /** 101 * constrainLat makes sure latitudes are in the proper bounds 102 * 103 * @param Lat The original Lat value 104 * @return The corrected Lat value 105 */ 106 func constrainLat(lat float64) float64 { 107 for lat > M_PI_2 { 108 lat = lat - M_PI 109 } 110 return lat 111 } 112 113 /** 114 * constrainLng makes sure longitudes are in the proper bounds 115 * 116 * @param lng The origin lng value 117 * @return The corrected lng value 118 */ 119 func constrainLng(lng float64) float64 { 120 for lng > M_PI { 121 lng = lng - (2 * M_PI) 122 } 123 for lng < -M_PI { 124 lng = lng + (2 * M_PI) 125 } 126 return lng 127 } 128 129 /** 130 * Find the great circle distance in radians between two spherical coordinates. 131 * 132 * @param p1 The first spherical coordinates. 133 * @param p2 The second spherical coordinates. 134 * @return The great circle distance in radians between p1 and p2. 135 */ 136 func _geoDistRads(p1 *GeoCoord, p2 *GeoCoord) float64 { 137 // use spherical triangle with p1 at A, p2 at B, and north pole at C 138 bigC := math.Abs(p2.Lon - p1.Lon) 139 if bigC > M_PI { 140 // note that in this case they can't both be negative 141 lon1 := p1.Lon 142 if lon1 < 0.0 { 143 lon1 += 2.0 * M_PI 144 } 145 lon2 := p2.Lon 146 147 if lon2 < 0.0 { 148 lon2 += 2.0 * M_PI 149 } 150 151 bigC = math.Abs(lon2 - lon1) 152 } 153 154 b := M_PI_2 - p1.Lat 155 a := M_PI_2 - p2.Lat 156 157 // use law of cosines to find c 158 cosc := math.Cos(a)*math.Cos(b) + math.Sin(a)*math.Sin(b)*math.Cos(bigC) 159 if cosc > 1.0 { 160 cosc = 1.0 161 } 162 if cosc < -1.0 { 163 cosc = -1.0 164 } 165 166 return math.Acos(cosc) 167 } 168 169 /** 170 * Find the great circle distance in kilometers between two spherical 171 * coordinates. 172 * 173 * @param p1 The first spherical coordinates. 174 * @param p2 The second spherical coordinates. 175 * @return The distance in kilometers between p1 and p2. 176 */ 177 func _geoDistKm(p1 *GeoCoord, p2 *GeoCoord) float64 { 178 return EARTH_RADIUS_KM * _geoDistRads(p1, p2) 179 } 180 181 /** 182 * Determines the azimuth to p2 from p1 in radians. 183 * 184 * @param p1 The first spherical coordinates. 185 * @param p2 The second spherical coordinates. 186 * @return The azimuth in radians from p1 to p2. 187 */ 188 func _geoAzimuthRads(p1 *GeoCoord, p2 *GeoCoord) float64 { 189 return math.Atan2(math.Cos(p2.Lat)*math.Sin(p2.Lon-p1.Lon), 190 math.Cos(p1.Lat)*math.Sin(p2.Lat)- 191 math.Sin(p1.Lat)*math.Cos(p2.Lat)*math.Cos(p2.Lon-p1.Lon)) 192 } 193 194 /** 195 * Computes the point on the sphere a specified azimuth and distance from 196 * another point. 197 * 198 * @param p1 The first spherical coordinates. 199 * @param az The desired azimuth from p1. 200 * @param distance The desired distance from p1, must be non-negative. 201 * @param p2 The spherical coordinates at the desired azimuth and distance from 202 * p1. 203 */ 204 func _geoAzDistanceRads(p1 *GeoCoord, az float64, distance float64, p2 *GeoCoord) { 205 if distance < EPSILON { 206 *p2 = *p1 207 return 208 } 209 210 var sinlat, sinlon, coslon float64 211 212 az = _posAngleRads(az) 213 214 // check for due north/south azimuth 215 if az < EPSILON || math.Abs(az-M_PI) < EPSILON { 216 if az < EPSILON { 217 // due north 218 p2.Lat = p1.Lat + distance 219 } else { 220 // due south 221 p2.Lat = p1.Lat - distance 222 } 223 224 // north pole 225 if math.Abs(p2.Lat-M_PI_2) < EPSILON { 226 p2.Lat = M_PI_2 227 p2.Lon = 0.0 228 } else if math.Abs(p2.Lat+M_PI_2) < EPSILON { 229 // south pole 230 p2.Lat = -M_PI_2 231 p2.Lon = 0.0 232 } else { 233 p2.Lon = constrainLng(p1.Lon) 234 } 235 } else { 236 // not due north or south 237 sinlat = math.Sin(p1.Lat)*math.Cos(distance) + 238 math.Cos(p1.Lat)*math.Sin(distance)*math.Cos(az) 239 if sinlat > 1.0 { 240 sinlat = 1.0 241 } 242 if sinlat < -1.0 { 243 sinlat = -1.0 244 } 245 p2.Lat = math.Asin(sinlat) 246 if math.Abs(p2.Lat-M_PI_2) < EPSILON { 247 // north pole 248 p2.Lat = M_PI_2 249 p2.Lon = 0.0 250 } else if math.Abs(p2.Lat+M_PI_2) < EPSILON { 251 // south pole 252 p2.Lat = -M_PI_2 253 p2.Lon = 0.0 254 } else { 255 sinlon = math.Sin(az) * math.Sin(distance) / math.Cos(p2.Lat) 256 coslon = (math.Cos(distance) - math.Sin(p1.Lat)*math.Sin(p2.Lat)) / 257 math.Cos(p1.Lat) / math.Cos(p2.Lat) 258 if sinlon > 1.0 { 259 sinlon = 1.0 260 } 261 if sinlon < -1.0 { 262 sinlon = -1.0 263 } 264 if coslon > 1.0 { 265 coslon = 1.0 266 } 267 if coslon < -1.0 { 268 coslon = -1.0 269 } 270 p2.Lon = constrainLng(p1.Lon + math.Atan2(sinlon, coslon)) 271 } 272 } 273 } 274 275 /* 276 * The following functions provide meta information about the H3 hexagons at 277 * each zoom level. Since there are only 16 total levels, these are current 278 * handled with hardwired static values, but it may be worthwhile to put these 279 * static values into another file that can be autogenerated by source code in 280 * the future. 281 */ 282 283 var areaKM2s = []float64{ 284 4250546.848, 607220.9782, 86745.85403, 12392.26486, 285 1770.323552, 252.9033645, 36.1290521, 5.1612932, 286 0.7373276, 0.1053325, 0.0150475, 0.0021496, 287 0.0003071, 0.0000439, 0.0000063, 0.0000009, 288 } 289 290 func hexAreaKm2(res int) float64 { 291 return areaKM2s[res] 292 } 293 294 var areas = []float64{ 295 4.25055e+12, 6.07221e+11, 86745854035, 12392264862, 296 1770323552, 252903364.5, 36129052.1, 5161293.2, 297 737327.6, 105332.5, 15047.5, 2149.6, 298 307.1, 43.9, 6.3, 0.9, 299 } 300 301 func hexAreaM2(res int) float64 { 302 return areas[res] 303 } 304 305 var edgeLenOfKMs = []float64{ 306 1107.712591, 418.6760055, 158.2446558, 59.81085794, 307 22.6063794, 8.544408276, 3.229482772, 1.220629759, 308 0.461354684, 0.174375668, 0.065907807, 0.024910561, 309 0.009415526, 0.003559893, 0.001348575, 0.000509713} 310 311 func edgeLengthKm(res int) float64 { 312 return edgeLenOfKMs[res] 313 } 314 315 var edgeLenOfMs = []float64{ 316 1107712.591, 418676.0055, 158244.6558, 59810.85794, 317 22606.3794, 8544.408276, 3229.482772, 1220.629759, 318 461.3546837, 174.3756681, 65.90780749, 24.9105614, 319 9.415526211, 3.559893033, 1.348574562, 0.509713273, 320 } 321 322 func edgeLengthM(res int) float64 { 323 return edgeLenOfMs[res] 324 } 325 326 var nums = []int64{ 327 122, 328 842, 329 5882, 330 41162, 331 288122, 332 2016842, 333 14117882, 334 98825162, 335 691776122, 336 4842432842, 337 33897029882, 338 237279209162, 339 1660954464122, 340 11626681248842, 341 81386768741882, 342 569707381193162, 343 } 344 345 /** @brief Number of unique valid H3Indexes at given resolution. */ 346 func numHexagons(res int) int64 { 347 return nums[res] 348 }