mirror of https://github.com/gnss-sdr/gnss-sdr
194 lines
5.9 KiB
C++
194 lines
5.9 KiB
C++
/*!
|
|
* \file geohash.cc
|
|
* \brief Implementation of a class for geohash encoding / decoding
|
|
* \author Carles Fernandez-Prades, 2023. cfernandez(at)cttc.es
|
|
*
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
*
|
|
* GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
|
|
* This file is part of GNSS-SDR.
|
|
*
|
|
* Copyright (C) 2010-2023 (see AUTHORS file for a list of contributors)
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "geohash.h"
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
|
|
std::string Geohash::encode(double lat, double lon, int precision) const
|
|
{
|
|
// infer precision?
|
|
if (precision == -1)
|
|
{
|
|
// refine geohash until it matches precision of supplied lat/lon
|
|
for (int p = 1; p <= 12; ++p)
|
|
{
|
|
const auto hash = Geohash::encode(lat, lon, p);
|
|
const auto posn = Geohash::decode(hash);
|
|
|
|
if ((std::fabs(posn[0] - lat) < std::numeric_limits<double>::epsilon()) &&
|
|
(std::fabs(posn[1] - lon) < std::numeric_limits<double>::epsilon()))
|
|
{
|
|
return hash;
|
|
}
|
|
}
|
|
precision = 12; // set to maximum
|
|
}
|
|
|
|
if (std::isnan(lat) || std::isnan(lon) || precision < 1)
|
|
{
|
|
throw std::invalid_argument("Invalid geohash");
|
|
}
|
|
|
|
int idx = 0; // index into base32 map
|
|
int bit = 0; // each char holds 5 bits
|
|
bool evenBit = true;
|
|
std::string geohash = "";
|
|
|
|
double latMin = -90.0;
|
|
double latMax = 90.0;
|
|
double lonMin = -180.0;
|
|
double lonMax = 180.0;
|
|
|
|
while (geohash.length() < static_cast<size_t>(precision))
|
|
{
|
|
if (evenBit)
|
|
{
|
|
// bisect E-W longitude
|
|
const double lonMid = (lonMin + lonMax) / 2.0;
|
|
if (lon >= lonMid)
|
|
{
|
|
idx = idx * 2 + 1;
|
|
lonMin = lonMid;
|
|
}
|
|
else
|
|
{
|
|
idx = idx * 2;
|
|
lonMax = lonMid;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// bisect N-S latitude
|
|
const double latMid = (latMin + latMax) / 2.0;
|
|
if (lat >= latMid)
|
|
{
|
|
idx = idx * 2 + 1;
|
|
latMin = latMid;
|
|
}
|
|
else
|
|
{
|
|
idx = idx * 2;
|
|
latMax = latMid;
|
|
}
|
|
}
|
|
evenBit = !evenBit;
|
|
|
|
if (++bit == 5)
|
|
{
|
|
// 5 bits gives us a character: append it and start over
|
|
geohash += base32[idx];
|
|
bit = 0;
|
|
idx = 0;
|
|
}
|
|
}
|
|
|
|
return geohash;
|
|
}
|
|
|
|
|
|
std::array<double, 2> Geohash::decode(std::string geohash) const
|
|
{
|
|
const auto bounds = Geohash::bounds(std::move(geohash));
|
|
|
|
const double latMin = bounds[0];
|
|
const double lonMin = bounds[1];
|
|
const double latMax = bounds[2];
|
|
const double lonMax = bounds[3];
|
|
|
|
// cell centre
|
|
double lat = (latMin + latMax) / 2.0;
|
|
double lon = (lonMin + lonMax) / 2.0;
|
|
|
|
// round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places
|
|
std::array<double, 2> latlon{};
|
|
int decimalPlaces = std::floor(2.0 - std::log10(latMax - latMin));
|
|
double factor = std::pow(10, decimalPlaces);
|
|
latlon[0] = std::round(lat * factor) / factor;
|
|
int decimalPlaces2 = std::floor(2.0 - std::log10(lonMax - lonMin));
|
|
double factor2 = std::pow(10, decimalPlaces2);
|
|
latlon[1] = std::round(lon * factor2) / factor2;
|
|
|
|
return latlon;
|
|
}
|
|
|
|
|
|
std::array<double, 4> Geohash::bounds(std::string geohash) const
|
|
{
|
|
if (geohash.length() == 0)
|
|
{
|
|
throw std::runtime_error("Invalid geohash");
|
|
}
|
|
|
|
std::transform(geohash.begin(), geohash.end(), geohash.begin(),
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
bool evenBit = true;
|
|
double latMin = -90.0;
|
|
double latMax = 90.0;
|
|
double lonMin = -180.0;
|
|
double lonMax = 180.0;
|
|
|
|
for (char chr : geohash)
|
|
{
|
|
auto idx = static_cast<int>(base32.find(chr));
|
|
if (idx == -1)
|
|
{
|
|
throw std::runtime_error("Invalid geohash");
|
|
}
|
|
|
|
for (int n = 4; n >= 0; n--)
|
|
{
|
|
int bitN = idx >> n & 1;
|
|
if (evenBit)
|
|
{
|
|
// longitude
|
|
double lonMid = (lonMin + lonMax) / 2.0;
|
|
if (bitN == 1)
|
|
{
|
|
lonMin = lonMid;
|
|
}
|
|
else
|
|
{
|
|
lonMax = lonMid;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// latitude
|
|
double latMid = (latMin + latMax) / 2.0;
|
|
if (bitN == 1)
|
|
{
|
|
latMin = latMid;
|
|
}
|
|
else
|
|
{
|
|
latMax = latMid;
|
|
}
|
|
}
|
|
evenBit = !evenBit;
|
|
}
|
|
}
|
|
|
|
return {latMin, lonMin, latMax, lonMax};
|
|
} |