You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parse.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. if len(geoStr) <= geoHashMaxLength {
  82. lat, lon = DecodeGeoHash(geoStr)
  83. foundLat = true
  84. foundLon = true
  85. }
  86. }
  87. }
  88. // is it a map
  89. if l, ok := thing.(map[string]interface{}); ok {
  90. if lval, ok := l["lon"]; ok {
  91. lon, foundLon = extractNumericVal(lval)
  92. } else if lval, ok := l["lng"]; ok {
  93. lon, foundLon = extractNumericVal(lval)
  94. }
  95. if lval, ok := l["lat"]; ok {
  96. lat, foundLat = extractNumericVal(lval)
  97. }
  98. }
  99. // now try reflection on struct fields
  100. if thingVal.Kind() == reflect.Struct {
  101. for i := 0; i < thingVal.NumField(); i++ {
  102. fieldName := thingTyp.Field(i).Name
  103. if strings.HasPrefix(strings.ToLower(fieldName), "lon") {
  104. if thingVal.Field(i).CanInterface() {
  105. fieldVal := thingVal.Field(i).Interface()
  106. lon, foundLon = extractNumericVal(fieldVal)
  107. }
  108. }
  109. if strings.HasPrefix(strings.ToLower(fieldName), "lng") {
  110. if thingVal.Field(i).CanInterface() {
  111. fieldVal := thingVal.Field(i).Interface()
  112. lon, foundLon = extractNumericVal(fieldVal)
  113. }
  114. }
  115. if strings.HasPrefix(strings.ToLower(fieldName), "lat") {
  116. if thingVal.Field(i).CanInterface() {
  117. fieldVal := thingVal.Field(i).Interface()
  118. lat, foundLat = extractNumericVal(fieldVal)
  119. }
  120. }
  121. }
  122. }
  123. // last hope, some interfaces
  124. // lon
  125. if l, ok := thing.(loner); ok {
  126. lon = l.Lon()
  127. foundLon = true
  128. } else if l, ok := thing.(lnger); ok {
  129. lon = l.Lng()
  130. foundLon = true
  131. }
  132. // lat
  133. if l, ok := thing.(later); ok {
  134. lat = l.Lat()
  135. foundLat = true
  136. }
  137. return lon, lat, foundLon && foundLat
  138. }
  139. // extract numeric value (if possible) and returns a float64
  140. func extractNumericVal(v interface{}) (float64, bool) {
  141. val := reflect.ValueOf(v)
  142. if !val.IsValid() {
  143. return 0, false
  144. }
  145. typ := val.Type()
  146. switch typ.Kind() {
  147. case reflect.Float32, reflect.Float64:
  148. return val.Float(), true
  149. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  150. return float64(val.Int()), true
  151. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  152. return float64(val.Uint()), true
  153. }
  154. return 0, false
  155. }
  156. // various support interfaces which can be used to find lat/lon
  157. type loner interface {
  158. Lon() float64
  159. }
  160. type later interface {
  161. Lat() float64
  162. }
  163. type lnger interface {
  164. Lng() float64
  165. }