]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/elements/map.cxx
Canvas: Support z-index inside Groups/Maps
[flightgear.git] / src / Canvas / elements / map.cxx
1 // A group of 2D canvas elements which get automatically transformed according
2 // to the map parameters.
3 //
4 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 #include "map.hxx"
21 #include "map/geo_node_pair.hxx"
22 #include "map/projection.hxx"
23
24 #include <Main/fg_props.hxx>
25 #include <cmath>
26
27 #include <boost/algorithm/string/predicate.hpp>
28
29 #define LOG_GEO_RET(msg) \
30   {\
31     SG_LOG\
32     (\
33       SG_GENERAL,\
34       SG_WARN,\
35       msg << " (" << child->getStringValue()\
36                   << ", " << child->getPath() << ")"\
37     );\
38     return;\
39   }
40
41 namespace canvas
42 {
43
44   const std::string GEO = "-geo";
45
46   //----------------------------------------------------------------------------
47   Map::Map(SGPropertyNode_ptr node, const Style& parent_style):
48     Group(node, parent_style),
49     // TODO make projection configurable
50     _projection(new SansonFlamsteedProjection),
51     _projection_dirty(true)
52   {
53
54   }
55
56   //----------------------------------------------------------------------------
57   Map::~Map()
58   {
59
60   }
61
62   //----------------------------------------------------------------------------
63   void Map::update(double dt)
64   {
65     for( GeoNodes::iterator it = _geo_nodes.begin();
66          it != _geo_nodes.end();
67          ++it )
68     {
69       GeoNodePair* geo_node = it->second.get();
70       if(    !geo_node->isComplete()
71           || (!geo_node->isDirty() && !_projection_dirty) )
72         continue;
73
74       GeoCoord lat = parseGeoCoord(geo_node->getLat());
75       if( lat.type != GeoCoord::LATITUDE )
76         continue;
77
78       GeoCoord lon = parseGeoCoord(geo_node->getLon());
79       if( lon.type != GeoCoord::LONGITUDE )
80         continue;
81
82       Projection::ScreenPosition pos =
83         _projection->worldToScreen(lat.value, lon.value);
84
85       geo_node->setScreenPos(pos.x, pos.y);
86
87 //      geo_node->print();
88       geo_node->setDirty(false);
89     }
90     _projection_dirty = false;
91
92     Group::update(dt);
93   }
94
95   //----------------------------------------------------------------------------
96   void Map::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
97   {
98     if( !boost::ends_with(child->getNameString(), GEO) )
99       return Element::childAdded(parent, child);
100
101     _geo_nodes[child].reset(new GeoNodePair());
102   }
103
104   //----------------------------------------------------------------------------
105   void Map::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
106   {
107     if( !boost::ends_with(child->getNameString(), GEO) )
108       return Element::childRemoved(parent, child);
109
110     // TODO remove from other node
111     _geo_nodes.erase(child);
112   }
113
114   //----------------------------------------------------------------------------
115   void Map::valueChanged(SGPropertyNode * child)
116   {
117     const std::string& name = child->getNameString();
118
119     if( !boost::ends_with(name, GEO) )
120       return Group::valueChanged(child);
121
122     GeoNodes::iterator it_geo_node = _geo_nodes.find(child);
123     if( it_geo_node == _geo_nodes.end() )
124       LOG_GEO_RET("geo node not found!")
125     GeoNodePair* geo_node = it_geo_node->second.get();
126
127     geo_node->setDirty();
128
129     if( geo_node->getStatus() & GeoNodePair::INCOMPLETE )
130     {
131       // Detect lat, lon tuples...
132       GeoCoord coord = parseGeoCoord(child->getStringValue());
133       int index_other = -1;
134
135       switch( coord.type )
136       {
137         case GeoCoord::LATITUDE:
138           index_other = child->getIndex() + 1;
139           geo_node->setNodeLat(child);
140           break;
141         case GeoCoord::LONGITUDE:
142           index_other = child->getIndex() - 1;
143           geo_node->setNodeLon(child);
144           break;
145         default:
146           LOG_GEO_RET("Invalid geo coord")
147       }
148
149       SGPropertyNode *other = child->getParent()->getChild(name, index_other);
150       if( !other )
151         return;
152
153       GeoCoord coord_other = parseGeoCoord(other->getStringValue());
154       if(    coord_other.type == GeoCoord::INVALID
155           || coord_other.type == coord.type )
156         return;
157
158       GeoNodes::iterator it_geo_node_other = _geo_nodes.find(other);
159       if( it_geo_node_other == _geo_nodes.end() )
160         LOG_GEO_RET("other geo node not found!")
161       GeoNodePair* geo_node_other = it_geo_node_other->second.get();
162
163       // Let use both nodes use the same GeoNodePair instance
164       if( geo_node_other != geo_node )
165         it_geo_node_other->second = it_geo_node->second;
166
167       if( coord_other.type == GeoCoord::LATITUDE )
168         geo_node->setNodeLat(other);
169       else
170         geo_node->setNodeLon(other);
171
172       // Set name for resulting screen coordinate nodes
173       geo_node->setTargetName( name.substr(0, name.length() - GEO.length()) );
174     }
175   }
176
177   //----------------------------------------------------------------------------
178   void Map::childChanged(SGPropertyNode * child)
179   {
180     if( child->getParent() != _node )
181       return Group::childChanged(child);
182
183     if(    child->getNameString() == "ref-lat"
184         || child->getNameString() == "ref-lon" )
185       _projection->setWorldPosition( _node->getDoubleValue("ref-lat"),
186                                      _node->getDoubleValue("ref-lon") );
187     else if( child->getNameString() == "hdg" )
188       _projection->setOrientation(child->getFloatValue());
189     else if( child->getNameString() == "range" )
190       _projection->setRange(child->getDoubleValue());
191     else
192       return Group::childChanged(child);
193
194     _projection_dirty = true;
195   }
196
197   //----------------------------------------------------------------------------
198   Map::GeoCoord Map::parseGeoCoord(const std::string& val) const
199   {
200     GeoCoord coord;
201     if( val.length() < 2 )
202       return coord;
203
204     if( val[0] == 'N' || val[0] == 'S' )
205       coord.type = GeoCoord::LATITUDE;
206     else if( val[0] == 'E' || val[0] == 'W' )
207       coord.type = GeoCoord::LONGITUDE;
208     else
209       return coord;
210
211     char* end;
212     coord.value = strtod(&val[1], &end);
213
214     if( end != &val[val.length()] )
215     {
216       coord.type = GeoCoord::INVALID;
217       return coord;
218     }
219
220     if( val[0] == 'S' || val[0] == 'W' )
221       coord.value *= -1;
222
223     return coord;
224   }
225
226 } // namespace canvas