photoprism-client-go/vendor/github.com/google/open-location-code/go/shorten.go

121 lines
3.9 KiB
Go

package olc
import (
"errors"
"fmt"
"math"
"strings"
)
// MinTrimmableCodeLen is the minimum length of a code that is able to be shortened.
const MinTrimmableCodeLen = 6
var (
pairResolutions = [...]float64{20.0, 1.0, .05, .0025, .000125}
)
// Shorten removes characters from the start of an OLC code.
//
// This uses a reference location to determine how many initial characters
// can be removed from the OLC code. The number of characters that can be
// removed depends on the distance between the code center and the reference
// location.
//
// The minimum number of characters that will be removed is four. At most eight
// characters will be removed.
//
// The reference location must be within 50% of the maximum range. This ensures
// that the shortened code will be able to be recovered using slightly different
// locations.
func Shorten(code string, lat, lng float64) (string, error) {
if err := CheckFull(code); err != nil {
return code, err
}
if strings.IndexByte(code, Padding) >= 0 {
return code, errors.New("cannot shorten padded code")
}
code = strings.ToUpper(code)
area, err := Decode(code)
if err != nil {
return code, err
}
if area.Len < MinTrimmableCodeLen {
return code, fmt.Errorf("code length must be at least %d", MinTrimmableCodeLen)
}
lat, lng = clipLatitude(lat), normalizeLng(lng)
// How close are the latitude and longitude to the code center.
centerLat, centerLng := area.Center()
distance := math.Max(math.Abs(centerLat-lat), math.Abs(centerLng-lng))
for i := len(pairResolutions) - 2; i >= 1; i-- {
// Check if we're close enough to shorten. The range must be less than 1/2
// the resolution to shorten at all, and we want to allow some safety, so
// use 0.3 instead of 0.5 as a multiplier.
if distance < pairResolutions[i]*0.3 {
// Trim it.
return code[(i+1)*2:], nil
}
}
return code, nil
}
// RecoverNearest recovers the nearest matching code to a specified location.
//
// Given a short Open Location Code with from four to eight digits missing,
// this recovers the nearest matching full code to the specified location.
func RecoverNearest(code string, lat, lng float64) (string, error) {
// Return uppercased code if a full code was passed.
if err := CheckFull(code); err == nil {
return strings.ToUpper(code), nil
}
// Return error if not a short code
if err := CheckShort(code); err != nil {
return code, ErrNotShort
}
// Ensure that latitude and longitude are valid.
lat, lng = clipLatitude(lat), normalizeLng(lng)
// Clean up the passed code.
code = strings.ToUpper(code)
// Compute the number of digits we need to recover.
padLen := sepPos - strings.IndexByte(code, Separator)
// The resolution (height and width) of the padded area in degrees.
resolution := math.Pow(20, float64(2-(padLen/2)))
// Distance from the center to an edge (in degrees).
halfRes := float64(resolution) / 2
// Use the reference location to pad the supplied short code and decode it.
area, err := Decode(Encode(lat, lng, 0)[:padLen] + code)
if err != nil {
return code, err
}
// How many degrees latitude is the code from the reference? If it is more
// than half the resolution, we need to move it south or north but keep it
// within -90 to 90 degrees.
centerLat, centerLng := area.Center()
if lat+halfRes < centerLat && centerLat-resolution >= -latMax {
// If the proposed code is more than half a cell north of the reference location,
// it's too far, and the best match will be one cell south.
centerLat -= resolution
} else if lat-halfRes > centerLat && centerLat+resolution <= latMax {
// If the proposed code is more than half a cell south of the reference location,
// it's too far, and the best match will be one cell north.
centerLat += resolution
}
// How many degrees longitude is the code from the reference?
if lng+halfRes < centerLng {
centerLng -= resolution
} else if lng-halfRes > centerLng {
centerLng += resolution
}
return Encode(centerLat, centerLng, area.Len), nil
}