]> git.mxchange.org Git - flightgear.git/commitdiff
Canvas: Add new element type map for geo mapping.
authorThomas Geymayer <tomgey@gmail.com>
Wed, 11 Jul 2012 22:23:29 +0000 (00:23 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Tue, 31 Jul 2012 21:19:23 +0000 (23:19 +0200)
 - The new map element automatically transforms geo coordinates
   (lat, lon) to the according screen coordinates.
 - Currently one type of projection is supported
   (Sanson-Flamsteed projection)

src/Canvas/CMakeLists.txt
src/Canvas/elements/element.cxx
src/Canvas/elements/group.cxx
src/Canvas/elements/map.cxx [new file with mode: 0644]
src/Canvas/elements/map.hxx [new file with mode: 0644]
src/Canvas/elements/map/geo_node_pair.hxx [new file with mode: 0644]
src/Canvas/elements/map/projection.hxx [new file with mode: 0644]

index 6927c04a4b510d5b03ba4984dcecbec376e19b12..c2c52e251484615b9d2bfdcf35c8ea4486a3c100 100644 (file)
@@ -5,6 +5,7 @@ set(SOURCES
   canvas_mgr.cxx
   elements/element.cxx
   elements/group.cxx
+  elements/map.cxx
   elements/path.cxx
   elements/text.cxx
   property_helper.cxx
@@ -15,6 +16,7 @@ set(HEADERS
   canvas_mgr.hxx
   elements/element.hxx
   elements/group.hxx
+  elements/map.hxx
   elements/path.hxx
   elements/text.hxx
   property_helper.hxx
index 3c2cdc64b386e69d242c22a5fd03c4be4dfe4b01..15934556f5d84f3b2dc792e85f600d8e5f136477 100644 (file)
@@ -159,13 +159,6 @@ namespace canvas
         type = TT_ROTATE;
       else if( name == "s" )
         type = TT_SCALE;
-      else
-        SG_LOG
-        (
-          SG_GL,
-          SG_WARN,
-          "Unknown transform element " << child->getPath()
-        );
 
       _transform_dirty = true;
     }
index ebbe3e6f747589295db4dc5b9e2730458976d57d..96eb49c23563518fd531ffa2757b57dfc7a9e44e 100644 (file)
@@ -17,6 +17,7 @@
 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 #include "group.hxx"
+#include "map.hxx"
 #include "path.hxx"
 #include "text.hxx"
 
@@ -56,6 +57,8 @@ namespace canvas
       element.reset( new Text(child) );
     else if( child->getNameString() == "group" )
       element.reset( new Group(child) );
+    else if( child->getNameString() == "map" )
+      element.reset( new Map(child) );
     else if( child->getNameString() == "path" )
       element.reset( new Path(child) );
 
@@ -72,7 +75,8 @@ namespace canvas
   {
     if(    node->getNameString() == "text"
         || node->getNameString() == "group"
-        || node->getNameString() == "path")
+        || node->getNameString() == "map"
+        || node->getNameString() == "path" )
     {
       ChildMap::iterator child = _children.find(node);
 
diff --git a/src/Canvas/elements/map.cxx b/src/Canvas/elements/map.cxx
new file mode 100644 (file)
index 0000000..59a7d9b
--- /dev/null
@@ -0,0 +1,232 @@
+// A group of 2D canvas elements which get automatically transformed according
+// to the map parameters.
+//
+// Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#include "map.hxx"
+#include "map/geo_node_pair.hxx"
+#include "map/projection.hxx"
+
+#include <Main/fg_props.hxx>
+#include <cmath>
+
+#define LOG_GEO_RET(msg) \
+  {\
+    SG_LOG\
+    (\
+      SG_GENERAL,\
+      SG_WARN,\
+      msg << " (" << child->getStringValue()\
+                  << ", " << child->getPath() << ")"\
+    );\
+    return;\
+  }
+
+namespace canvas
+{
+
+  // TODO make projection configurable
+  SansonFlamsteedProjection projection;
+  const std::string GEO = "-geo";
+
+  //----------------------------------------------------------------------------
+  Map::Map(SGPropertyNode_ptr node):
+    Group(node),
+    _projection_dirty(true)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  Map::~Map()
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  void Map::update(double dt)
+  {
+    for( GeoNodes::iterator it = _geo_nodes.begin();
+         it != _geo_nodes.end();
+         ++it )
+    {
+      GeoNodePair* geo_node = it->second.get();
+      if(    !geo_node->isComplete()
+          || (!geo_node->isDirty() && !_projection_dirty) )
+        continue;
+
+      GeoCoord lat = parseGeoCoord(geo_node->getLat());
+      if( lat.type != GeoCoord::LATITUDE )
+        continue;
+
+      GeoCoord lon = parseGeoCoord(geo_node->getLon());
+      if( lon.type != GeoCoord::LONGITUDE )
+        continue;
+
+      Projection::ScreenPosition pos =
+        projection.worldToScreen(lat.value, lon.value);
+
+      geo_node->setScreenPos(pos.x, pos.y);
+
+//      geo_node->print();
+      geo_node->setDirty(false);
+    }
+    _projection_dirty = false;
+
+    Group::update(dt);
+  }
+
+  //----------------------------------------------------------------------------
+  void Map::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
+  {
+    if( !hasSuffix(child->getNameString(), GEO) )
+      return Element::childAdded(parent, child);
+
+    _geo_nodes[child].reset(new GeoNodePair());
+  }
+
+  //----------------------------------------------------------------------------
+  void Map::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
+  {
+    if( !hasSuffix(child->getNameString(), GEO) )
+      return Element::childRemoved(parent, child);
+
+    // TODO remove from other node
+    _geo_nodes.erase(child);
+  }
+
+  //----------------------------------------------------------------------------
+  void Map::valueChanged(SGPropertyNode * child)
+  {
+    const std::string& name = child->getNameString();
+
+    if( !hasSuffix(name, GEO) )
+      return Group::valueChanged(child);
+
+    GeoNodes::iterator it_geo_node = _geo_nodes.find(child);
+    if( it_geo_node == _geo_nodes.end() )
+      LOG_GEO_RET("geo node not found!")
+    GeoNodePair* geo_node = it_geo_node->second.get();
+
+    geo_node->setDirty();
+
+    if( geo_node->getStatus() & GeoNodePair::INCOMPLETE )
+    {
+      // Detect lat, lon tuples...
+      GeoCoord coord = parseGeoCoord(child->getStringValue());
+      int index_other = -1;
+
+      switch( coord.type )
+      {
+        case GeoCoord::LATITUDE:
+          index_other = child->getIndex() + 1;
+          geo_node->setNodeLat(child);
+          break;
+        case GeoCoord::LONGITUDE:
+          index_other = child->getIndex() - 1;
+          geo_node->setNodeLon(child);
+          break;
+        default:
+          LOG_GEO_RET("Invalid geo coord")
+      }
+
+      SGPropertyNode *other = child->getParent()->getChild(name, index_other);
+      if( !other )
+        return;
+
+      GeoCoord coord_other = parseGeoCoord(other->getStringValue());
+      if(    coord_other.type == GeoCoord::INVALID
+          || coord_other.type == coord.type )
+        return;
+
+      GeoNodes::iterator it_geo_node_other = _geo_nodes.find(other);
+      if( it_geo_node_other == _geo_nodes.end() )
+        LOG_GEO_RET("other geo node not found!")
+      GeoNodePair* geo_node_other = it_geo_node_other->second.get();
+
+      // Let use both nodes use the same GeoNodePair instance
+      if( geo_node_other != geo_node )
+        it_geo_node_other->second = it_geo_node->second;
+
+      if( coord_other.type == GeoCoord::LATITUDE )
+        geo_node->setNodeLat(other);
+      else
+        geo_node->setNodeLon(other);
+
+      // Set name for resulting screen coordinate nodes
+      geo_node->setTargetName( name.substr(0, name.length() - GEO.length()) );
+    }
+  }
+
+  //----------------------------------------------------------------------------
+  void Map::childChanged(SGPropertyNode * child)
+  {
+    if(    child->getNameString() == "ref-lat"
+        || child->getNameString() == "ref-lon" )
+      projection.setWorldPosition( _node->getDoubleValue("ref-lat"),
+                                   _node->getDoubleValue("ref-lon") );
+    else if( child->getNameString() == "hdg" )
+      projection.setOrientation(child->getFloatValue());
+    else if( child->getNameString() == "range" )
+      projection.setRange(child->getDoubleValue());
+    else
+      return;
+
+    _projection_dirty = true;
+  }
+
+  //----------------------------------------------------------------------------
+  Map::GeoCoord Map::parseGeoCoord(const std::string& val) const
+  {
+    GeoCoord coord;
+    if( val.length() < 2 )
+      return coord;
+
+    if( val[0] == 'N' || val[0] == 'S' )
+      coord.type = GeoCoord::LATITUDE;
+    else if( val[0] == 'E' || val[0] == 'W' )
+      coord.type = GeoCoord::LONGITUDE;
+    else
+      return coord;
+
+    char* end;
+    coord.value = strtod(&val[1], &end);
+
+    if( end != &val[val.length()] )
+    {
+      coord.type = GeoCoord::INVALID;
+      return coord;
+    }
+
+    if( val[0] == 'S' || val[0] == 'W' )
+      coord.value *= -1;
+
+    return coord;
+  }
+
+  //----------------------------------------------------------------------------
+  bool Map::hasSuffix(const std::string& str, const std::string& suffix) const
+  {
+    if( suffix.length() > str.length() )
+      return false;
+
+    return ( str.compare( str.length() - suffix.length(),
+                          suffix.length(),
+                          suffix ) == 0 );
+  }
+
+} // namespace canvas
diff --git a/src/Canvas/elements/map.hxx b/src/Canvas/elements/map.hxx
new file mode 100644 (file)
index 0000000..944f8b6
--- /dev/null
@@ -0,0 +1,77 @@
+// A group of 2D canvas elements which get automatically transformed according
+// to the map parameters.
+//
+// Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifndef CANVAS_MAP_HXX_
+#define CANVAS_MAP_HXX_
+
+#include "group.hxx"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/unordered_map.hpp>
+
+namespace canvas
+{
+  class GeoNodePair;
+  class Map:
+    public Group
+  {
+    public:
+      Map(SGPropertyNode_ptr node);
+      virtual ~Map();
+
+      virtual void update(double dt);
+
+      virtual void childAdded( SGPropertyNode * parent,
+                               SGPropertyNode * child );
+      virtual void childRemoved( SGPropertyNode * parent,
+                                 SGPropertyNode * child );
+      virtual void valueChanged(SGPropertyNode * child);
+
+    protected:
+
+      virtual void childChanged(SGPropertyNode * child);
+
+      typedef boost::unordered_map< SGPropertyNode*,
+                                    boost::shared_ptr<GeoNodePair>
+                                  > GeoNodes;
+      GeoNodes _geo_nodes;
+      bool _projection_dirty;
+
+      struct GeoCoord
+      {
+        GeoCoord():
+          type(INVALID)
+        {}
+        enum
+        {
+          INVALID,
+          LATITUDE,
+          LONGITUDE
+        } type;
+        double value;
+      };
+
+      GeoCoord parseGeoCoord(const std::string& val) const;
+
+      bool hasSuffix(const std::string& str, const std::string& suffix) const;
+  };
+
+} // namespace canvas
+
+#endif /* CANVAS_MAP_HXX_ */
diff --git a/src/Canvas/elements/map/geo_node_pair.hxx b/src/Canvas/elements/map/geo_node_pair.hxx
new file mode 100644 (file)
index 0000000..14a04c9
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * geo_node_pair.hxx
+ *
+ *  Created on: 11.07.2012
+ *      Author: tom
+ */
+
+#ifndef CANVAS_GEO_NODE_PAIR_HXX_
+#define CANVAS_GEO_NODE_PAIR_HXX_
+
+namespace canvas
+{
+  class GeoNodePair
+  {
+    public:
+      enum StatusFlags
+      {
+        LAT_MISSING = 1,
+        LON_MISSING = LAT_MISSING << 1,
+        INCOMPLETE = LAT_MISSING | LON_MISSING,
+        DIRTY = LON_MISSING << 1
+      };
+
+      GeoNodePair():
+        _status(INCOMPLETE),
+        _node_lat(0),
+        _node_lon(0)
+      {}
+
+      uint8_t getStatus() const
+      {
+        return _status;
+      }
+
+      void setDirty(bool flag = true)
+      {
+        if( flag )
+          _status |= DIRTY;
+        else
+          _status &= ~DIRTY;
+      }
+
+      bool isDirty() const
+      {
+        return _status & DIRTY;
+      }
+
+      bool isComplete() const
+      {
+        return !(_status & INCOMPLETE);
+      }
+
+      void setNodeLat(SGPropertyNode* node)
+      {
+        _node_lat = node;
+        _status &= ~LAT_MISSING;
+
+        if( node == _node_lon )
+        {
+          _node_lon = 0;
+          _status |= LON_MISSING;
+        }
+      }
+
+      void setNodeLon(SGPropertyNode* node)
+      {
+        _node_lon = node;
+        _status &= ~LON_MISSING;
+
+        if( node == _node_lat )
+        {
+          _node_lat = 0;
+          _status |= LAT_MISSING;
+        }
+      }
+
+      const char* getLat() const
+      {
+        return _node_lat ? _node_lat->getStringValue() : "";
+      }
+
+      const char* getLon() const
+      {
+        return _node_lon ? _node_lon->getStringValue() : "";
+      }
+
+      void setTargetName(const std::string& name)
+      {
+        _target_name = name;
+      }
+
+      void setScreenPos(float x, float y)
+      {
+        assert( isComplete() );
+        SGPropertyNode *parent = _node_lat->getParent();
+        parent->getChild(_target_name, _node_lat->getIndex(), true)
+              ->setDoubleValue(x);
+        parent->getChild(_target_name, _node_lon->getIndex(), true)
+              ->setDoubleValue(y);
+      }
+
+      void print()
+      {
+        std::cout << "lat=" << (_node_lat ? _node_lat->getPath() : "")
+                  << ", lon=" << (_node_lon ? _node_lon->getPath() : "")
+                  << std::endl;
+      }
+
+    private:
+
+      uint8_t _status;
+      SGPropertyNode *_node_lat,
+                     *_node_lon;
+      std::string   _target_name;
+
+  };
+
+} // namespace canvas
+
+#endif /* CANVAS_GEO_NODE_PAIR_HXX_ */
diff --git a/src/Canvas/elements/map/projection.hxx b/src/Canvas/elements/map/projection.hxx
new file mode 100644 (file)
index 0000000..446230e
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * projection.hxx
+ *
+ *  Created on: 12.07.2012
+ *      Author: tom
+ */
+
+#ifndef CANVAS_MAP_PROJECTION_HXX_
+#define CANVAS_MAP_PROJECTION_HXX_
+
+const double DEG2RAD = M_PI / 180.0;
+
+namespace canvas
+{
+
+  /**
+   * Base class for all projections
+   */
+  class Projection
+  {
+    public:
+      struct ScreenPosition
+      {
+        ScreenPosition() {}
+
+        ScreenPosition(double x, double y):
+          x(x),
+          y(y)
+        {}
+
+        double x, y;
+      };
+
+      virtual ~Projection() {}
+
+      void setScreenRange(double range)
+      {
+        _screen_range = range;
+      }
+
+      virtual ScreenPosition worldToScreen(double x, double y) = 0;
+
+    protected:
+
+      double _screen_range;
+  };
+
+  /**
+   * Base class for horizontal projections
+   */
+  class HorizontalProjection:
+    public Projection
+  {
+    public:
+
+      HorizontalProjection():
+        _cos_rot(1),
+        _sin_rot(0),
+        _range(5)
+      {
+        setScreenRange(200);
+      }
+
+      /**
+       * Set world position of center point used for the projection
+       */
+      void setWorldPosition(double lat, double lon)
+      {
+        _ref_lat = lat * DEG2RAD;
+        _ref_lon = lon * DEG2RAD;
+      }
+
+      /**
+       * Set up heading
+       */
+      void setOrientation(float hdg)
+      {
+        hdg *= DEG2RAD;
+        _sin_rot = sin(hdg);
+        _cos_rot = cos(hdg);
+      }
+
+      void setRange(double range)
+      {
+        _range = range;
+      }
+
+      /**
+       * Transform given world position to screen position
+       *
+       * @param lat   Latitude in degrees
+       * @param lon   Longitude in degrees
+       */
+      ScreenPosition worldToScreen(double lat, double lon)
+      {
+        lat *= DEG2RAD;
+        lon *= DEG2RAD;
+        ScreenPosition pos = project(lat, lon);
+        double scale = _screen_range / _range;
+        pos.x *= scale;
+        pos.y *= scale;
+        return ScreenPosition
+        (
+          _cos_rot * pos.x - _sin_rot * pos.y,
+         -_sin_rot * pos.x - _cos_rot * pos.y
+        );
+      }
+
+    protected:
+
+      /**
+       * Project given geographic world position to screen space
+       *
+       * @param lat   Latitude in radians
+       * @param lon   Longitude in radians
+       */
+      virtual ScreenPosition project(double lat, double lon) const = 0;
+
+      double  _ref_lat,
+              _ref_lon,
+              _cos_rot,
+              _sin_rot,
+              _range;
+  };
+
+  /**
+   * Sanson-Flamsteed projection, relative to the projection center
+   */
+  class SansonFlamsteedProjection:
+    public HorizontalProjection
+  {
+    protected:
+
+      virtual ScreenPosition project(double lat, double lon) const
+      {
+        double d_lat = lat - _ref_lat,
+               d_lon = lon - _ref_lon;
+        double r = getEarthRadius(lat);
+
+        ScreenPosition pos;
+
+        pos.x = r * cos(lat) * d_lon;
+        pos.y = r * d_lat;
+
+        return pos;
+      }
+
+      /**
+       * Returns Earth radius at a given latitude (Ellipsoide equation with two
+       * equal axis)
+       */
+      float getEarthRadius(float lat) const
+      {
+        const float rec  = 6378137.f / 1852;      // earth radius, equator (?)
+        const float rpol = 6356752.314f / 1852;   // earth radius, polar   (?)
+
+        double a = cos(lat) / rec;
+        double b = sin(lat) / rpol;
+        return 1.0f / sqrt( a * a + b * b );
+      }
+  };
+
+} // namespace canvas
+
+#endif /* CANVAS_MAP_PROJECTION_HXX_ */