Commit 2f56af71 authored by Volker Krause's avatar Volker Krause
Browse files

Add a decoder for the Google Polyline format

This is used by Hafas for encoding geographic paths.
parent 43d676e2
......@@ -10,6 +10,7 @@ ecm_add_test(kgraphqlminimizertest.cpp LINK_LIBRARIES Qt5::Test KGraphQL)
ecm_add_test(indexeddatatabletest LINK_LIBRARIES Qt5::Test)
ecm_add_test(polylinetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(mergeutiltest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(locationtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(linetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <geo/polylinedecoder_p.h>
#include <QTest>
#include <iostream>
using namespace KPublicTransport;
class PolylineDecoderTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testDecode()
{
PolylineDecoder<2> decoder("_p~iF~ps|U_ulLnnqC_mqNvxq`@");
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), 38.5);
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), -120.2);
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), 40.7);
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), -120.95);
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), 43.252);
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextDouble(), -126.453);
QCOMPARE(decoder.canReadMore(), false);
}
void testEmpty()
{
PolylineDecoder<2> decoder("");
QCOMPARE(decoder.canReadMore(), false);
}
void testZero()
{
PolylineDecoder<1> decoder("???");
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextInt(), 0);
QCOMPARE(decoder.readNextDouble(), 0.0);
QCOMPARE(decoder.readNextDouble(), 0.0);
QCOMPARE(decoder.canReadMore(), false);
}
void testNonDifferential()
{
PolylineDecoder<1, false> decoder("NL");
QCOMPARE(decoder.canReadMore(), true);
QCOMPARE(decoder.readNextInt(), -8);
QCOMPARE(decoder.readNextInt(), -7);
QCOMPARE(decoder.canReadMore(), false);
}
#if 0
void dumpEncoded() // mostly for manual testing
{
PolylineDecoder<1> decoder("?cAclA}K{P?gTwE?e@qNyMuEiWmVyNgAuBgAyJqHy[{MsQeWM?eF");
while (decoder.canReadMore()) {
for (int i = 0; i < decoder.dimensions(); ++i) {
std::cout << decoder.readNextDouble() << " ";
}
std::cout << std::endl;
}
}
#endif
};
QTEST_GUILESS_MAIN(PolylineDecoderTest)
#include "polylinetest.moc"
......@@ -75,6 +75,8 @@ set(kpublictransport_srcs
gbfs/gbfsservice.cpp
gbfs/gbfsstore.cpp
geo/polylinedecoder.cpp
gtfs/hvt.cpp
knowledgedb/linemetadata.cpp
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "polylinedecoder_p.h"
using namespace KPublicTransport;
int32_t PolylineDecoderBase::readNextIntNonDifferential()
{
int32_t result = 0;
int shift = 0;
char c = 0;
do {
if (canReadMore()) {
c = *m_it;
++m_it;
c -= 63;
result |= (c & 0b11111) << shift;
shift += 5;
} else {
return std::numeric_limits<int32_t>::max();
}
} while (c >= 0x20);
if (result & 1) {
result = ~result;
}
result >>= 1;
return result;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPUBLICTRANSPORT_POLYLINEDECODER_P_H
#define KPUBLICTRANSPORT_POLYLINEDECODER_P_H
#include "kpublictransport_export.h"
#include <array>
#include <cstdint>
#include <limits>
namespace KPublicTransport {
///@cond internal
class PolylineDecoderBase {
protected:
explicit inline PolylineDecoderBase(const char *const begin, const char *const end)
: m_it(begin)
, m_end(end)
{}
inline bool canReadMore() const
{
return m_it != m_end && *m_it;
}
KPUBLICTRANSPORT_EXPORT int32_t readNextIntNonDifferential();
const char *m_it = nullptr;
const char *m_end = nullptr;
};
///@endcond
/**
* Decoder for Google's Polyline format.
* @see https://developers.google.com/maps/documentation/utilities/polylinealgorithm
*/
template <int Dim = 2, bool Differential = true>
class PolylineDecoder : PolylineDecoderBase
{
public:
explicit inline PolylineDecoder(const char *const begin, const char *const end)
: PolylineDecoderBase(begin, end)
{
m_accu.fill(0);
}
template <std::size_t N>
explicit inline PolylineDecoder(const char (&data)[N])
: PolylineDecoder(std::begin(data), std::end(data)) {}
~PolylineDecoder() = default;
constexpr inline int dimensions() const { return Dim; }
using PolylineDecoderBase::canReadMore;
inline int32_t readNextInt()
{
auto n = readNextIntNonDifferential();
if constexpr(Differential) {
n += m_accu[m_nextDim];
m_accu[m_nextDim++] = n;
m_nextDim %= Dim;
}
return n;
}
inline double readNextDouble()
{
return readNextInt() / 100000.0;
}
private:
int m_nextDim = 0;
std::array<int32_t, Dim> m_accu;
};
}
#endif // KPUBLICTRANSPORT_POLYLINEDECODER_P_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment