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