Implemented GeoCoordinate::convertFrom, iterated unit tests
authorSami Rämö <sami.ramo@ixonos.com>
Wed, 14 Jul 2010 14:34:57 +0000 (17:34 +0300)
committerSami Rämö <sami.ramo@ixonos.com>
Wed, 14 Jul 2010 14:34:57 +0000 (17:34 +0300)
 - implemented GeoCoordinate::convertFrom(SceneCoordinate) and
   unit tests for it

 - modified SceneCoordinate::convertFrom(GeoCoordinate);
   removed assertions and implemented normalizing the
   x value to be always inside the map

 - iterated/fixed unit tests for SceneCoordinate;
   added formatted print for failing double values,
   added more test data into conversion tests,
   comparing only integer part of the result

src/coordinates/geocoordinate.cpp
src/coordinates/geocoordinate.h
src/coordinates/scenecoordinate.cpp
src/coordinates/scenecoordinate.h
tests/coordinates/geocoordinate/geocoordinate.pro
tests/coordinates/geocoordinate/testgeocoordinate.cpp
tests/coordinates/scenecoordinate/scenecoordinate.pro
tests/coordinates/scenecoordinate/testscenecoordinate.cpp

index 1fcdb31..f58697a 100644 (file)
 
 #include <QDebug>
 
+/// @todo remove mapcommon.h include after refactored the convertFrom()
+#include "map/mapcommon.h"
+#include "scenecoordinate.h"
+
 #include "geocoordinate.h"
 
 GeoCoordinate::GeoCoordinate() :
@@ -37,6 +41,45 @@ GeoCoordinate::GeoCoordinate(double latitude, double longitude) :
     qDebug() << __PRETTY_FUNCTION__;
 }
 
+GeoCoordinate::GeoCoordinate(SceneCoordinate &coordinate)
+{
+    qDebug() << __PRETTY_FUNCTION__;
+
+    convertFrom(coordinate);
+}
+
+void GeoCoordinate::convertFrom(const SceneCoordinate &coordinate)
+{
+    qDebug() << __PRETTY_FUNCTION__;
+
+    const int zoomLevel = 18; // replacing the parameter temporarily
+
+    double tileFactor = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
+    double xFactor = (coordinate.x() / (TILE_SIZE_X*tileFactor));
+    double yFactor = (coordinate.y() / (TILE_SIZE_Y*tileFactor));
+
+    tileFactor = 1 << zoomLevel;
+    double longitude = xFactor / tileFactor * 360.0 - 180;
+
+    double n = M_PI - 2.0 * M_PI * yFactor / tileFactor;
+    double latitude = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
+
+//    return QPointF(longitude, latitude);
+    setLatitude(latitude);
+    setLongitude(longitude);
+}
+
+//double tilex2long(int x, int z)
+//{
+//     return x / pow(2.0, z) * 360.0 - 180;
+//}
+
+//double tiley2lat(int y, int z)
+//{
+//     double n = M_PI - 2.0 * M_PI * y / pow(2.0, z);
+//     return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
+//}
+
 bool GeoCoordinate::isNull() const
 {
     qDebug() << __PRETTY_FUNCTION__;
index c31f9bd..52a6562 100644 (file)
@@ -26,6 +26,8 @@
 #include <QDebug>
 #include <QMetaType>
 
+class SceneCoordinate;
+
 /**
 * @brief Geographic coordinate
 *
@@ -48,6 +50,17 @@ public:
     GeoCoordinate(double latitude, double longitude);
 
     /**
+    * @brief Constructs a coordinate with values converted from the given SceneCoordinate
+    *
+    * @param coordinate Scene coordinate
+    */
+    GeoCoordinate(SceneCoordinate &coordinate);
+
+/*******************************************************************************
+ * MEMBER FUNCTIONS AND SLOTS
+ ******************************************************************************/
+public:
+    /**
     * @brief Check if coordinate is (0.0, 0.0)
     *
     * @returns True if both latitude and longitude are 0.0, otherwise false
@@ -83,6 +96,17 @@ public:
     void setLongitude(double longitude);
 
 private:
+    /**
+     * @brief Convert values from SceneCoordinate
+     *
+     * @param coordinate Scene coordinate
+     */
+    void convertFrom(const SceneCoordinate &coordinate);
+
+/*******************************************************************************
+ * DATA MEMBERS
+ ******************************************************************************/
+private:
     double m_latitude;      ///< Latitude value
     double m_longitude;     ///< Longitude value
 };
index 48f5df7..923ad90 100644 (file)
@@ -51,22 +51,15 @@ void SceneCoordinate::convertFrom(const GeoCoordinate &coordinate)
 {
     qDebug() << __PRETTY_FUNCTION__;
 
-    Q_ASSERT(coordinate.longitude() >= MIN_LONGITUDE);
-    Q_ASSERT(coordinate.longitude() <= MAX_LONGITUDE);
-    Q_ASSERT(coordinate.latitude() >= MIN_LATITUDE);
-    Q_ASSERT(coordinate.latitude() <= MAX_LATITUDE);
-
-
     // calculate x & y positions in the map (0..1)
     double worldX = static_cast<double>((coordinate.longitude() + 180.0) / 360.0);
     double worldY = static_cast<double>((1.0 - log(tan(coordinate.latitude() * M_PI / 180.0) + 1.0
                                 / cos(coordinate.latitude() * M_PI / 180.0)) / M_PI) / 2.0);
 
-    setX(worldX * MAX_TILES_PER_SIDE * TILE_SIZE_X);
-    setY(worldY * MAX_TILES_PER_SIDE * TILE_SIZE_Y);
+    m_x = worldX * MAX_TILES_PER_SIDE * TILE_SIZE_X;
+    m_y = worldY * MAX_TILES_PER_SIDE * TILE_SIZE_Y;
 
-    Q_ASSERT((x() >= MAP_MIN_PIXEL_X) && (x() <= MAP_MAX_PIXEL_X));
-    Q_ASSERT((y() >= MAP_MIN_PIXEL_Y) && (y() <= MAP_MAX_PIXEL_Y));
+    normalize(m_x, MAP_MIN_PIXEL_X, MAP_MAX_PIXEL_X);
 }
 
 bool SceneCoordinate::isNull() const
@@ -79,6 +72,18 @@ bool SceneCoordinate::isNull() const
     return false;
 }
 
+void SceneCoordinate::normalize(double &value, int min, int max)
+{
+    qDebug() << __PRETTY_FUNCTION__;
+    Q_ASSERT_X(max >= min, "parameters", "max can't be smaller than min");
+
+    while (int(value) < min)
+        value += max - min + 1;
+
+    while (int(value) > max)
+        value -= max - min + 1;
+}
+
 double SceneCoordinate::x() const
 {
     qDebug() << __PRETTY_FUNCTION__;
index 7c172fd..3d06728 100644 (file)
@@ -52,6 +52,8 @@ public:
     /**
     * @brief Constructs a coordinate with values converted from the given GeoCoordinate
     *
+    * Uses convertFrom() method.
+    *
     * @param coordinate Geological coordinate
     */
     SceneCoordinate(GeoCoordinate &coordinate);
@@ -99,10 +101,33 @@ private:
     /**
      * @brief Convert values from GeoCoordinate
      *
+     * Does run normalize() for the x value after the conversion to make sure that the result
+     * is inside the allowed map pixel values.
+     *
+     * In horizontal direction:
+     *    -180º equals scene pixel 0 (first scene pixel)
+     *    +180º equals -180º
+     *
+     *    scene has 2^18 * 256 - 1 = 67108864 pixels per side
+     *    one pixel width is 360º / 67108864 = 0.00000536441802978516º
+     *    so the last scene pixel is 180º - 0.00000536441802978516º = 179.99999463558197021484º     *
+     *
      * @param coordinate Geological coordinate
      */
     void convertFrom(const GeoCoordinate &coordinate);
 
+    /**
+     * @brief Translate integer part of the given value between min and max
+     *
+     * If given value is not inside the given range (min <= value <= max), then the allowed range
+     * is adder or subtracted until the value does fit in the range. Only integer part is compared.
+     *
+     * @param value Value to be normalized
+     * @param min Minimum allowed value
+     * @param max Maximum allowed value
+     */
+    void normalize(double &value, int min, int max);
+
 /*******************************************************************************
  * DATA MEMBERS
  ******************************************************************************/
index 93043a9..ab0685c 100644 (file)
@@ -15,13 +15,16 @@ TEMPLATE = app
 
 
 SOURCES += testgeocoordinate.cpp \
-    ../../../src/coordinates/geocoordinate.cpp
+    ../../../src/coordinates/geocoordinate.cpp \
+    ../../../src/coordinates/scenecoordinate.cpp
 DEFINES += SRCDIR=\\\"$$PWD/\\\"
 
 INCLUDEPATH += . \
     ../../../src/
 
 HEADERS += \
-    ../../../src/coordinates/geocoordinate.h
+    ../../../src/coordinates/geocoordinate.h \
+    ../../../src/coordinates/scenecoordinate.h \
+    ../../../src/map/mapcommon.h
 
 DEFINES += QT_NO_DEBUG_OUTPUT
index efeb59e..0c95012 100644 (file)
 #include <QtCore/QString>
 #include <QtTest/QtTest>
 
+#include "coordinates/scenecoordinate.h"
+#include "map/mapcommon.h"
+
 #include "coordinates/geocoordinate.h"
 
 const double LATITUDE = 12.345678;
 const double LONGITUDE = -89.765432;
 
+const double ONE_SCENE_PIXEL_WIDTH_IN_DEGREES = 0.00000536441802978516;
+
 class TestGeoCoordinate : public QObject
 {
     Q_OBJECT
 
-public:
-    TestGeoCoordinate();
-
 private Q_SLOTS:
     void constructors();
     void isNull();
+    void conversion();
+    void conversion_data();
     void settersAndGetters();
 };
 
-TestGeoCoordinate::TestGeoCoordinate()
-{
+// for formatting the output of double valuest into the test log
+namespace QTest {
+    template<>
+    char *toString(const double &number)
+    {
+        QByteArray ba;
+        ba += QByteArray::number(number, 'f', 9);
+        return qstrdup(ba.data());
+    }
 }
 
 void TestGeoCoordinate::constructors()
@@ -52,6 +63,37 @@ void TestGeoCoordinate::constructors()
     GeoCoordinate coordinate2(LATITUDE, LONGITUDE);
     QCOMPARE(coordinate2.latitude(), LATITUDE);
     QCOMPARE(coordinate2.longitude(), LONGITUDE);
+
+    // NOTE: constructor with conversion from GeoCoordinate is tested in conversion() test slot
+}
+
+void TestGeoCoordinate::conversion()
+{
+    // allow rounding error of one tenth of one scene pixel
+    const double MAX_ERROR = ONE_SCENE_PIXEL_WIDTH_IN_DEGREES * 0.1;
+
+    QFETCH(SceneCoordinate, sceneCoordinate);
+    QFETCH(GeoCoordinate, result);
+
+    GeoCoordinate geoCoordinate(sceneCoordinate);
+
+    QVERIFY(qAbs(geoCoordinate.latitude() - result.latitude()) < MAX_ERROR);
+    QVERIFY(qAbs(geoCoordinate.longitude() - result.longitude()) < MAX_ERROR);
+}
+
+void TestGeoCoordinate::conversion_data()
+{
+    QTest::addColumn<SceneCoordinate>("sceneCoordinate");
+    QTest::addColumn<GeoCoordinate>("result");
+
+    QTest::newRow("top left") << SceneCoordinate(MAP_MIN_PIXEL_X, MAP_MIN_PIXEL_Y)
+                              << GeoCoordinate(MAX_LATITUDE, MIN_LONGITUDE);
+
+    const double LAST_SCENE_HORIZONTAL_PIXEL_LONGITUDE = MAX_LONGITUDE
+                                                         - ONE_SCENE_PIXEL_WIDTH_IN_DEGREES;
+
+    QTest::newRow("bottom right") << SceneCoordinate(MAP_MAX_PIXEL_X, MAP_MAX_PIXEL_Y)
+                                  << GeoCoordinate(MIN_LATITUDE, LAST_SCENE_HORIZONTAL_PIXEL_LONGITUDE);
 }
 
 void TestGeoCoordinate::isNull()
index 8c8257e..06a39ab 100644 (file)
@@ -21,7 +21,8 @@ DEFINES += SRCDIR=\\\"$$PWD/\\\"
 
 HEADERS += \
     ../../../src/coordinates/scenecoordinate.h \
-    ../../../src/coordinates/geocoordinate.h
+    ../../../src/coordinates/geocoordinate.h \
+    ../../../src/map/mapcommon.h
 
 INCLUDEPATH += . \
     ../../../src/
index a607c96..6767e25 100644 (file)
@@ -42,6 +42,17 @@ private Q_SLOTS:
     void settersAndGetters();
 };
 
+// for formatting the output of double valuest into the test log
+namespace QTest {
+    template<>
+    char *toString(const double &number)
+    {
+        QByteArray ba;
+        ba += QByteArray::number(number, 'f', 9);
+        return qstrdup(ba.data());
+    }
+}
+
 void TestSceneCoordinate::constructors()
 {
     SceneCoordinate coordinate;
@@ -61,8 +72,12 @@ void TestSceneCoordinate::conversion()
 
     SceneCoordinate sceneCoordinate(geoCoordinate);
 
-    QCOMPARE(sceneCoordinate.x(), result.x());
-    QCOMPARE(sceneCoordinate.y(), result.y());
+    // Comparison is done using only the integer parts because data type of the scene coordinate
+    // values is double so the result is not exact pixel value but is one containing a fractional
+    // part. Also the rounding errors below one pixel are insignificant.
+
+    QCOMPARE(int(sceneCoordinate.x()), int(result.x()));
+    QCOMPARE(int(sceneCoordinate.y()), int(result.y()));
 }
 
 void TestSceneCoordinate::conversion_data()
@@ -70,13 +85,23 @@ void TestSceneCoordinate::conversion_data()
     QTest::addColumn<GeoCoordinate>("geoCoordinate");
     QTest::addColumn<SceneCoordinate>("result");
 
-    QTest::newRow("top left") << GeoCoordinate(MAX_LATITUDE, MIN_LONGITUDE)
-                              << SceneCoordinate(0, 0);
+    QTest::newRow("top left pixel") << GeoCoordinate(MAX_LATITUDE, MIN_LONGITUDE)
+                                    << SceneCoordinate(0, 0);
+
+    const double ONE_SCENE_PIXEL_WIDTH_IN_DEGREES = 0.00000536441802978516;
+    const double LAST_SCENE_HORIZONTAL_PIXEL_LONGITUDE = MAX_LONGITUDE
+                                                         - ONE_SCENE_PIXEL_WIDTH_IN_DEGREES;
+    QTest::newRow("bottom right pixel")
+            << GeoCoordinate(MIN_LATITUDE, LAST_SCENE_HORIZONTAL_PIXEL_LONGITUDE)
+            << SceneCoordinate(MAP_MAX_PIXEL_X, MAP_MAX_PIXEL_Y);
+
+    QTest::newRow("southeast corner with 180 degrees longitude")
+            << GeoCoordinate(MIN_LATITUDE, MAX_LONGITUDE)
+            << SceneCoordinate(MAP_MIN_PIXEL_X, MAP_MAX_PIXEL_Y);
 
-    int x = (1 << MAX_MAP_ZOOM_LEVEL) * TILE_SIZE_X;
-    int y = (1 << MAX_MAP_ZOOM_LEVEL) * TILE_SIZE_Y;
-    QTest::newRow("bottom right") << GeoCoordinate(MIN_LATITUDE, MAX_LONGITUDE)
-                                  << SceneCoordinate(x, y);
+    QTest::newRow("southeast corner just little over west edge of the map")
+            << GeoCoordinate(MIN_LATITUDE, MIN_LONGITUDE - ONE_SCENE_PIXEL_WIDTH_IN_DEGREES)
+            << SceneCoordinate(MAP_MAX_PIXEL_X, MAP_MAX_PIXEL_Y);
 }
 
 void TestSceneCoordinate::isNull()