// Copyright (c) 2017 Couchbase, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package geo import ( "reflect" "strconv" "strings" ) // ExtractGeoPoint takes an arbitrary interface{} and tries it's best to // interpret it is as geo point. Supported formats: // Container: // slice length 2 (GeoJSON) // first element lon, second element lat // string (coordinates separated by comma, or a geohash) // first element lat, second element lon // map[string]interface{} // exact keys lat and lon or lng // struct // w/exported fields case-insensitive match on lat and lon or lng // struct // satisfying Later and Loner or Lnger interfaces // // in all cases values must be some sort of numeric-like thing: int/uint/float func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) { var foundLon, foundLat bool thingVal := reflect.ValueOf(thing) if !thingVal.IsValid() { return lon, lat, false } thingTyp := thingVal.Type() // is it a slice if thingVal.Kind() == reflect.Slice { // must be length 2 if thingVal.Len() == 2 { first := thingVal.Index(0) if first.CanInterface() { firstVal := first.Interface() lon, foundLon = extractNumericVal(firstVal) } second := thingVal.Index(1) if second.CanInterface() { secondVal := second.Interface() lat, foundLat = extractNumericVal(secondVal) } } } // is it a string if thingVal.Kind() == reflect.String { geoStr := thingVal.Interface().(string) if strings.Contains(geoStr, ",") { // geo point with coordinates split by comma points := strings.Split(geoStr, ",") for i, point := range points { // trim any leading or trailing white spaces points[i] = strings.TrimSpace(point) } if len(points) == 2 { var err error lat, err = strconv.ParseFloat(points[0], 64) if err == nil { foundLat = true } lon, err = strconv.ParseFloat(points[1], 64) if err == nil { foundLon = true } } } else { // geohash if len(geoStr) <= geoHashMaxLength { lat, lon = DecodeGeoHash(geoStr) foundLat = true foundLon = true } } } // is it a map if l, ok := thing.(map[string]interface{}); ok { if lval, ok := l["lon"]; ok { lon, foundLon = extractNumericVal(lval) } else if lval, ok := l["lng"]; ok { lon, foundLon = extractNumericVal(lval) } if lval, ok := l["lat"]; ok { lat, foundLat = extractNumericVal(lval) } } // now try reflection on struct fields if thingVal.Kind() == reflect.Struct { for i := 0; i < thingVal.NumField(); i++ { fieldName := thingTyp.Field(i).Name if strings.HasPrefix(strings.ToLower(fieldName), "lon") { if thingVal.Field(i).CanInterface() { fieldVal := thingVal.Field(i).Interface() lon, foundLon = extractNumericVal(fieldVal) } } if strings.HasPrefix(strings.ToLower(fieldName), "lng") { if thingVal.Field(i).CanInterface() { fieldVal := thingVal.Field(i).Interface() lon, foundLon = extractNumericVal(fieldVal) } } if strings.HasPrefix(strings.ToLower(fieldName), "lat") { if thingVal.Field(i).CanInterface() { fieldVal := thingVal.Field(i).Interface() lat, foundLat = extractNumericVal(fieldVal) } } } } // last hope, some interfaces // lon if l, ok := thing.(loner); ok { lon = l.Lon() foundLon = true } else if l, ok := thing.(lnger); ok { lon = l.Lng() foundLon = true } // lat if l, ok := thing.(later); ok { lat = l.Lat() foundLat = true } return lon, lat, foundLon && foundLat } // extract numeric value (if possible) and returns a float64 func extractNumericVal(v interface{}) (float64, bool) { val := reflect.ValueOf(v) if !val.IsValid() { return 0, false } typ := val.Type() switch typ.Kind() { case reflect.Float32, reflect.Float64: return val.Float(), true case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return float64(val.Int()), true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return float64(val.Uint()), true } return 0, false } // various support interfaces which can be used to find lat/lon type loner interface { Lon() float64 } type later interface { Lat() float64 } type lnger interface { Lng() float64 }