1
0
Fork 0

Canvas: Add new element type map for geo mapping.

- 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)
This commit is contained in:
Thomas Geymayer 2012-07-12 00:23:29 +02:00
parent a876ff93e1
commit e81db175f4
7 changed files with 601 additions and 8 deletions

View file

@ -5,6 +5,7 @@ set(SOURCES
canvas_mgr.cxx canvas_mgr.cxx
elements/element.cxx elements/element.cxx
elements/group.cxx elements/group.cxx
elements/map.cxx
elements/path.cxx elements/path.cxx
elements/text.cxx elements/text.cxx
property_helper.cxx property_helper.cxx
@ -15,6 +16,7 @@ set(HEADERS
canvas_mgr.hxx canvas_mgr.hxx
elements/element.hxx elements/element.hxx
elements/group.hxx elements/group.hxx
elements/map.hxx
elements/path.hxx elements/path.hxx
elements/text.hxx elements/text.hxx
property_helper.hxx property_helper.hxx

View file

@ -159,13 +159,6 @@ namespace canvas
type = TT_ROTATE; type = TT_ROTATE;
else if( name == "s" ) else if( name == "s" )
type = TT_SCALE; type = TT_SCALE;
else
SG_LOG
(
SG_GL,
SG_WARN,
"Unknown transform element " << child->getPath()
);
_transform_dirty = true; _transform_dirty = true;
} }

View file

@ -17,6 +17,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "group.hxx" #include "group.hxx"
#include "map.hxx"
#include "path.hxx" #include "path.hxx"
#include "text.hxx" #include "text.hxx"
@ -56,6 +57,8 @@ namespace canvas
element.reset( new Text(child) ); element.reset( new Text(child) );
else if( child->getNameString() == "group" ) else if( child->getNameString() == "group" )
element.reset( new Group(child) ); element.reset( new Group(child) );
else if( child->getNameString() == "map" )
element.reset( new Map(child) );
else if( child->getNameString() == "path" ) else if( child->getNameString() == "path" )
element.reset( new Path(child) ); element.reset( new Path(child) );
@ -72,7 +75,8 @@ namespace canvas
{ {
if( node->getNameString() == "text" if( node->getNameString() == "text"
|| node->getNameString() == "group" || node->getNameString() == "group"
|| node->getNameString() == "path") || node->getNameString() == "map"
|| node->getNameString() == "path" )
{ {
ChildMap::iterator child = _children.find(node); ChildMap::iterator child = _children.find(node);

232
src/Canvas/elements/map.cxx Normal file
View file

@ -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

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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_ */