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

124 lines
4.2 KiB
Go

// Copyright 2015 Tamás Gulácsi. All rights reserved.
//
// 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 olc
import (
"errors"
"math"
"strings"
)
var (
// ErrShort indicates the provided code was a short code.
ErrShort = errors.New("short code")
// ErrNotShort indicates the provided code was not a short code.
ErrNotShort = errors.New("not short code")
)
const (
minTrimmableCodeLen = 6
)
// Encode a location into an Open Location Code.
//
// Produces a code of the specified codeLen, or the default length if
// codeLen < 8;
// if codeLen is odd, it is incremented to be even.
//
// latitude is signed decimal degrees. Will be clipped to the range -90 to 90.
// longitude is signed decimal degrees. Will be normalised to the range -180 to 180.
// The length determines the accuracy of the code. The default length is
// 10 characters, returning a code of approximately 13.5x13.5 meters. Longer
// codes represent smaller areas, but lengths > 14 are sub-centimetre and so
// 11 or 12 are probably the limit of useful codes.
func Encode(lat, lng float64, codeLen int) string {
if codeLen <= 0 {
codeLen = pairCodeLen
} else if codeLen < 2 {
codeLen = 2
} else if codeLen < pairCodeLen && codeLen%2 == 1 {
codeLen++
} else if codeLen > maxCodeLen {
codeLen = maxCodeLen
}
// Clip the latitude. Normalise the longitude.
lat, lng = clipLatitude(lat), normalizeLng(lng)
// Latitude 90 needs to be adjusted to be just less, so the returned code
// can also be decoded.
if lat == latMax {
lat = normalizeLat(lat - computeLatPrec(codeLen))
}
// Use a char array so we can build it up from the end digits, without having
// to keep reallocating strings.
var code [15]byte
// Compute the code.
// This approach converts each value to an integer after multiplying it by
// the final precision. This allows us to use only integer operations, so
// avoiding any accumulation of floating point representation errors.
// Multiply values by their precision and convert to positive.
// Note: Go requires rounding before truncating to ensure precision!
var latVal int64 = int64(math.Round((lat+latMax)*finalLatPrecision*1e6) / 1e6)
var lngVal int64 = int64(math.Round((lng+lngMax)*finalLngPrecision*1e6) / 1e6)
pos := maxCodeLen - 1
// Compute the grid part of the code if necessary.
if codeLen > pairCodeLen {
for i := 0; i < gridCodeLen; i++ {
latDigit := latVal % int64(gridRows)
lngDigit := lngVal % int64(gridCols)
ndx := latDigit*gridCols + lngDigit
code[pos] = Alphabet[ndx]
pos -= 1
latVal /= int64(gridRows)
lngVal /= int64(gridCols)
}
} else {
latVal /= gridLatFullValue
lngVal /= gridLngFullValue
}
pos = pairCodeLen - 1
// Compute the pair section of the code.
for i := 0; i < pairCodeLen/2; i++ {
latNdx := latVal % int64(encBase)
lngNdx := lngVal % int64(encBase)
code[pos] = Alphabet[lngNdx]
pos -= 1
code[pos] = Alphabet[latNdx]
pos -= 1
latVal /= int64(encBase)
lngVal /= int64(encBase)
}
// If we don't need to pad the code, return the requested section.
if codeLen >= sepPos {
return string(code[:sepPos]) + string(Separator) + string(code[sepPos:codeLen])
}
// Pad and return the code.
return string(code[:codeLen]) + strings.Repeat(string(Padding), sepPos-codeLen) + string(Separator)
}
// computeLatPrec computes the precision value for a given code length.
// Lengths <= 10 have the same precision for latitude and longitude,
// but lengths > 10 have different precisions due to the grid method
// having fewer columns than rows.
func computeLatPrec(codeLen int) float64 {
if codeLen <= pairCodeLen {
return math.Pow(float64(encBase), math.Floor(float64(codeLen/-2+2)))
}
return math.Pow(float64(encBase), -3) / math.Pow(float64(gridRows), float64(codeLen-pairCodeLen))
}