geo_dist.go 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // Copyright (c) 2017 Couchbase, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package geo
  15. import (
  16. "fmt"
  17. "math"
  18. "strconv"
  19. "strings"
  20. )
  21. type distanceUnit struct {
  22. conv float64
  23. suffixes []string
  24. }
  25. var inch = distanceUnit{0.0254, []string{"in", "inch"}}
  26. var yard = distanceUnit{0.9144, []string{"yd", "yards"}}
  27. var feet = distanceUnit{0.3048, []string{"ft", "feet"}}
  28. var kilom = distanceUnit{1000, []string{"km", "kilometers"}}
  29. var nauticalm = distanceUnit{1852.0, []string{"nm", "nauticalmiles"}}
  30. var millim = distanceUnit{0.001, []string{"mm", "millimeters"}}
  31. var centim = distanceUnit{0.01, []string{"cm", "centimeters"}}
  32. var miles = distanceUnit{1609.344, []string{"mi", "miles"}}
  33. var meters = distanceUnit{1, []string{"m", "meters"}}
  34. var distanceUnits = []*distanceUnit{
  35. &inch, &yard, &feet, &kilom, &nauticalm, &millim, &centim, &miles, &meters,
  36. }
  37. // ParseDistance attempts to parse a distance string and return distance in
  38. // meters. Example formats supported:
  39. // "5in" "5inch" "7yd" "7yards" "9ft" "9feet" "11km" "11kilometers"
  40. // "3nm" "3nauticalmiles" "13mm" "13millimeters" "15cm" "15centimeters"
  41. // "17mi" "17miles" "19m" "19meters"
  42. // If the unit cannot be determined, the entire string is parsed and the
  43. // unit of meters is assumed.
  44. // If the number portion cannot be parsed, 0 and the parse error are returned.
  45. func ParseDistance(d string) (float64, error) {
  46. for _, unit := range distanceUnits {
  47. for _, unitSuffix := range unit.suffixes {
  48. if strings.HasSuffix(d, unitSuffix) {
  49. parsedNum, err := strconv.ParseFloat(d[0:len(d)-len(unitSuffix)], 64)
  50. if err != nil {
  51. return 0, err
  52. }
  53. return parsedNum * unit.conv, nil
  54. }
  55. }
  56. }
  57. // no unit matched, try assuming meters?
  58. parsedNum, err := strconv.ParseFloat(d, 64)
  59. if err != nil {
  60. return 0, err
  61. }
  62. return parsedNum, nil
  63. }
  64. // ParseDistanceUnit attempts to parse a distance unit and return the
  65. // multiplier for converting this to meters. If the unit cannot be parsed
  66. // then 0 and the error message is returned.
  67. func ParseDistanceUnit(u string) (float64, error) {
  68. for _, unit := range distanceUnits {
  69. for _, unitSuffix := range unit.suffixes {
  70. if u == unitSuffix {
  71. return unit.conv, nil
  72. }
  73. }
  74. }
  75. return 0, fmt.Errorf("unknown distance unit: %s", u)
  76. }
  77. // Haversin computes the distance between two points.
  78. // This implemenation uses the sloppy math implemenations which trade off
  79. // accuracy for performance. The distance returned is in kilometers.
  80. func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
  81. x1 := lat1 * degreesToRadian
  82. x2 := lat2 * degreesToRadian
  83. h1 := 1 - cos(x1-x2)
  84. h2 := 1 - cos((lon1-lon2)*degreesToRadian)
  85. h := (h1 + cos(x1)*cos(x2)*h2) / 2
  86. avgLat := (x1 + x2) / 2
  87. diameter := earthDiameter(avgLat)
  88. return diameter * asin(math.Min(1, math.Sqrt(h)))
  89. }