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  }