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:
parent
a876ff93e1
commit
e81db175f4
7 changed files with 601 additions and 8 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
232
src/Canvas/elements/map.cxx
Normal file
232
src/Canvas/elements/map.cxx
Normal 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
|
77
src/Canvas/elements/map.hxx
Normal file
77
src/Canvas/elements/map.hxx
Normal 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_ */
|
120
src/Canvas/elements/map/geo_node_pair.hxx
Normal file
120
src/Canvas/elements/map/geo_node_pair.hxx
Normal 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_ */
|
165
src/Canvas/elements/map/projection.hxx
Normal file
165
src/Canvas/elements/map/projection.hxx
Normal 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_ */
|
Loading…
Add table
Reference in a new issue