parse.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. "reflect"
  17. "strconv"
  18. "strings"
  19. )
  20. // ExtractGeoPoint takes an arbitrary interface{} and tries it's best to
  21. // interpret it is as geo point. Supported formats:
  22. // Container:
  23. // slice length 2 (GeoJSON)
  24. // first element lon, second element lat
  25. // string (coordinates separated by comma, or a geohash)
  26. // first element lat, second element lon
  27. // map[string]interface{}
  28. // exact keys lat and lon or lng
  29. // struct
  30. // w/exported fields case-insensitive match on lat and lon or lng
  31. // struct
  32. // satisfying Later and Loner or Lnger interfaces
  33. //
  34. // in all cases values must be some sort of numeric-like thing: int/uint/float
  35. func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
  36. var foundLon, foundLat bool
  37. thingVal := reflect.ValueOf(thing)
  38. if !thingVal.IsValid() {
  39. return lon, lat, false
  40. }
  41. thingTyp := thingVal.Type()
  42. // is it a slice
  43. if thingVal.Kind() == reflect.Slice {
  44. // must be length 2
  45. if thingVal.Len() == 2 {
  46. first := thingVal.Index(0)
  47. if first.CanInterface() {
  48. firstVal := first.Interface()
  49. lon, foundLon = extractNumericVal(firstVal)
  50. }
  51. second := thingVal.Index(1)
  52. if second.CanInterface() {
  53. secondVal := second.Interface()
  54. lat, foundLat = extractNumericVal(secondVal)
  55. }
  56. }
  57. }
  58. // is it a string
  59. if thingVal.Kind() == reflect.String {
  60. geoStr := thingVal.Interface().(string)
  61. if strings.Contains(geoStr, ",") {
  62. // geo point with coordinates split by comma
  63. points := strings.Split(geoStr, ",")
  64. for i, point := range points {
  65. // trim any leading or trailing white spaces
  66. points[i] = strings.TrimSpace(point)
  67. }
  68. if len(points) == 2 {
  69. var err error
  70. lat, err = strconv.ParseFloat(points[0], 64)
  71. if err == nil {
  72. foundLat = true
  73. }
  74. lon, err = strconv.ParseFloat(points[1], 64)
  75. if err == nil {
  76. foundLon = true
  77. }
  78. }
  79. } else {
  80. // geohash
  81. lat, lon = DecodeGeoHash(geoStr)
  82. foundLat = true
  83. foundLon = true
  84. }
  85. }
  86. // is it a map
  87. if l, ok := thing.(map[string]interface{}); ok {
  88. if lval, ok := l["lon"]; ok {
  89. lon, foundLon = extractNumericVal(lval)
  90. } else if lval, ok := l["lng"]; ok {
  91. lon, foundLon = extractNumericVal(lval)
  92. }
  93. if lval, ok := l["lat"]; ok {
  94. lat, foundLat = extractNumericVal(lval)
  95. }
  96. }
  97. // now try reflection on struct fields
  98. if thingVal.Kind() == reflect.Struct {
  99. for i := 0; i < thingVal.NumField(); i++ {
  100. fieldName := thingTyp.Field(i).Name
  101. if strings.HasPrefix(strings.ToLower(fieldName), "lon") {
  102. if thingVal.Field(i).CanInterface() {
  103. fieldVal := thingVal.Field(i).Interface()
  104. lon, foundLon = extractNumericVal(fieldVal)
  105. }
  106. }
  107. if strings.HasPrefix(strings.ToLower(fieldName), "lng") {
  108. if thingVal.Field(i).CanInterface() {
  109. fieldVal := thingVal.Field(i).Interface()
  110. lon, foundLon = extractNumericVal(fieldVal)
  111. }
  112. }
  113. if strings.HasPrefix(strings.ToLower(fieldName), "lat") {
  114. if thingVal.Field(i).CanInterface() {
  115. fieldVal := thingVal.Field(i).Interface()
  116. lat, foundLat = extractNumericVal(fieldVal)
  117. }
  118. }
  119. }
  120. }
  121. // last hope, some interfaces
  122. // lon
  123. if l, ok := thing.(loner); ok {
  124. lon = l.Lon()
  125. foundLon = true
  126. } else if l, ok := thing.(lnger); ok {
  127. lon = l.Lng()
  128. foundLon = true
  129. }
  130. // lat
  131. if l, ok := thing.(later); ok {
  132. lat = l.Lat()
  133. foundLat = true
  134. }
  135. return lon, lat, foundLon && foundLat
  136. }
  137. // extract numeric value (if possible) and returns a float64
  138. func extractNumericVal(v interface{}) (float64, bool) {
  139. val := reflect.ValueOf(v)
  140. if !val.IsValid() {
  141. return 0, false
  142. }
  143. typ := val.Type()
  144. switch typ.Kind() {
  145. case reflect.Float32, reflect.Float64:
  146. return val.Float(), true
  147. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  148. return float64(val.Int()), true
  149. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  150. return float64(val.Uint()), true
  151. }
  152. return 0, false
  153. }
  154. // various support interfaces which can be used to find lat/lon
  155. type loner interface {
  156. Lon() float64
  157. }
  158. type later interface {
  159. Lat() float64
  160. }
  161. type lnger interface {
  162. Lng() float64
  163. }