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.

140 lines
3.8 KiB

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