diff --git a/src/Canvas/CMakeLists.txt b/src/Canvas/CMakeLists.txt index 6927c04a4..c2c52e251 100644 --- a/src/Canvas/CMakeLists.txt +++ b/src/Canvas/CMakeLists.txt @@ -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 diff --git a/src/Canvas/elements/element.cxx b/src/Canvas/elements/element.cxx index 3c2cdc64b..15934556f 100644 --- a/src/Canvas/elements/element.cxx +++ b/src/Canvas/elements/element.cxx @@ -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; } diff --git a/src/Canvas/elements/group.cxx b/src/Canvas/elements/group.cxx index ebbe3e6f7..96eb49c23 100644 --- a/src/Canvas/elements/group.cxx +++ b/src/Canvas/elements/group.cxx @@ -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 index 000000000..59a7d9bac --- /dev/null +++ b/src/Canvas/elements/map.cxx @@ -0,0 +1,232 @@ +// A group of 2D canvas elements which get automatically transformed according +// to the map parameters. +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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
+#include + +#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 index 000000000..944f8b671 --- /dev/null +++ b/src/Canvas/elements/map.hxx @@ -0,0 +1,77 @@ +// A group of 2D canvas elements which get automatically transformed according +// to the map parameters. +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 +#include + +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 + > 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 index 000000000..14a04c918 --- /dev/null +++ b/src/Canvas/elements/map/geo_node_pair.hxx @@ -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 index 000000000..446230e8e --- /dev/null +++ b/src/Canvas/elements/map/projection.hxx @@ -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_ */