4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "GlueMapWindow.hpp"
25 #include "Terrain/RasterTerrain.hpp"
26 #include "Interface.hpp"
27 #include "Profile/Profile.hpp"
28 #include "Screen/Layout.hpp"
29 #include "Util/Clamp.hpp"
32 OffsetHistory::Reset()
34 offsets
.fill(RasterPoint
{0, 0});
38 OffsetHistory::Add(RasterPoint p
)
41 pos
= (pos
+ 1) % offsets
.size();
45 OffsetHistory::GetAverage() const
50 for (auto i
= offsets
.begin(), end
= offsets
.end(); i
!= end
; ++i
) {
56 avg
.x
= x
/ (int) offsets
.size();
57 avg
.y
= y
/ (int) offsets
.size();
63 GlueMapWindow::SetPan(bool enable
)
65 switch (follow_mode
) {
70 follow_mode
= FOLLOW_PAN
;
77 follow_mode
= FOLLOW_SELF
;
86 GlueMapWindow::TogglePan()
88 switch (follow_mode
) {
90 follow_mode
= FOLLOW_PAN
;
94 follow_mode
= FOLLOW_SELF
;
103 GlueMapWindow::PanTo(const GeoPoint
&location
)
105 follow_mode
= FOLLOW_PAN
;
106 visible_projection
.SetGeoLocation(location
);
113 GlueMapWindow::SetMapScale(fixed scale
)
115 MapWindow::SetMapScale(scale
);
117 const bool circling
=
118 CommonInterface::GetUIState().display_mode
== DisplayMode::CIRCLING
;
119 MapSettings
&settings
= CommonInterface::SetMapSettings();
121 if (circling
&& settings
.circle_zoom_enabled
)
123 settings
.circling_scale
= visible_projection
.GetScale();
125 settings
.cruise_scale
= visible_projection
.GetScale();
127 SaveDisplayModeScales();
131 GlueMapWindow::SaveDisplayModeScales()
133 const MapSettings
&settings
= CommonInterface::GetMapSettings();
135 Profile::Set(ProfileKeys::ClimbMapScale
, (int)(settings
.circling_scale
* 10000));
136 Profile::Set(ProfileKeys::CruiseMapScale
, (int)(settings
.cruise_scale
* 10000));
140 GlueMapWindow::SwitchZoomClimb(bool circling
)
142 const MapSettings
&settings
= CommonInterface::GetMapSettings();
144 if (!settings
.circle_zoom_enabled
)
148 visible_projection
.SetScale(settings
.circling_scale
);
150 visible_projection
.SetScale(settings
.cruise_scale
);
154 GlueMapWindow::UpdateDisplayMode()
156 /* not using MapWindowBlackboard here because these methods are
157 called by the main thread */
158 enum DisplayMode new_mode
= CommonInterface::GetUIState().display_mode
;
160 const bool was_circling
= last_display_mode
== DisplayMode::CIRCLING
;
161 const bool is_circling
= new_mode
== DisplayMode::CIRCLING
;
163 if (!was_circling
&& is_circling
)
164 offset_history
.Reset();
166 last_display_mode
= new_mode
;
168 if (is_circling
!= was_circling
)
169 SwitchZoomClimb(is_circling
);
173 GlueMapWindow::UpdateScreenAngle()
175 /* not using MapWindowBlackboard here because these methods are
176 called by the main thread */
177 const NMEAInfo
&basic
= CommonInterface::Basic();
178 const DerivedInfo
&calculated
= CommonInterface::Calculated();
179 const MapSettings
&settings
= CommonInterface::GetMapSettings();
180 const UIState
&ui_state
= CommonInterface::GetUIState();
182 DisplayOrientation orientation
=
183 ui_state
.display_mode
== DisplayMode::CIRCLING
184 ? settings
.circling_orientation
185 : settings
.cruise_orientation
;
187 if (orientation
== DisplayOrientation::TARGET_UP
&&
188 calculated
.task_stats
.current_leg
.vector_remaining
.IsValid())
189 visible_projection
.SetScreenAngle(calculated
.task_stats
.current_leg
.
190 vector_remaining
.bearing
);
191 else if (orientation
== DisplayOrientation::HEADING_UP
)
192 visible_projection
.SetScreenAngle(
193 basic
.attitude
.IsHeadingUseable() ? basic
.attitude
.heading
: Angle::Zero());
194 else if (orientation
== DisplayOrientation::NORTH_UP
)
195 visible_projection
.SetScreenAngle(Angle::Zero());
196 else if (orientation
== DisplayOrientation::WIND_UP
&&
197 calculated
.wind_available
&&
198 calculated
.wind
.norm
>= fixed(0.5))
199 visible_projection
.SetScreenAngle(calculated
.wind
.bearing
);
201 // normal, glider forward
202 visible_projection
.SetScreenAngle(
203 basic
.track_available
? basic
.track
: Angle::Zero());
205 compass_visible
= orientation
!= DisplayOrientation::NORTH_UP
;
209 GlueMapWindow::UpdateMapScale()
211 /* not using MapWindowBlackboard here because these methods are
212 called by the main thread */
213 const DerivedInfo
&calculated
= CommonInterface::Calculated();
214 const MapSettings
&settings
= CommonInterface::GetMapSettings();
215 const bool circling
=
216 CommonInterface::GetUIState().display_mode
== DisplayMode::CIRCLING
;
218 if (circling
&& settings
.circle_zoom_enabled
)
224 fixed distance
= calculated
.auto_zoom_distance
;
225 if (settings
.auto_zoom_enabled
&& positive(distance
)) {
226 // Calculate distance percentage between plane symbol and map edge
227 // 50: centered 100: at edge of map
228 int auto_zoom_factor
= circling
230 : 100 - settings
.glider_screen_position
;
232 // Leave 5% of full distance for target display
233 auto_zoom_factor
-= 5;
234 // Adjust to account for map scale units
235 auto_zoom_factor
*= 8;
237 distance
/= fixed(auto_zoom_factor
) / 100;
239 // Clip map auto zoom range to reasonable values
240 distance
= Clamp(distance
, fixed(525),
241 settings
.max_auto_zoom_distance
/ 10);
243 visible_projection
.SetFreeMapScale(distance
);
248 GlueMapWindow::SetLocationLazy(const GeoPoint location
)
250 if (!visible_projection
.IsValid()) {
251 SetLocation(location
);
255 const fixed distance_meters
=
256 visible_projection
.GetGeoLocation().Distance(location
);
257 const fixed distance_pixels
=
258 visible_projection
.DistanceMetersToPixels(distance_meters
);
259 if (distance_pixels
> fixed(0.5))
260 SetLocation(location
);
264 GlueMapWindow::UpdateProjection()
266 const PixelRect rc
= GetClientRect();
268 /* not using MapWindowBlackboard here because these methods are
269 called by the main thread */
270 const NMEAInfo
&basic
= CommonInterface::Basic();
271 const DerivedInfo
&calculated
= CommonInterface::Calculated();
272 const MapSettings
&settings_map
= CommonInterface::GetMapSettings();
273 const bool circling
=
274 CommonInterface::GetUIState().display_mode
== DisplayMode::CIRCLING
;
276 const RasterPoint center
= rc
.GetCenter();
278 if (circling
|| !IsNearSelf())
279 visible_projection
.SetScreenOrigin(center
.x
, center
.y
);
280 else if (settings_map
.cruise_orientation
== DisplayOrientation::NORTH_UP
||
281 settings_map
.cruise_orientation
== DisplayOrientation::WIND_UP
) {
282 RasterPoint offset
{0, 0};
283 if (settings_map
.glider_screen_position
!= 50 &&
284 settings_map
.map_shift_bias
!= MapShiftBias::NONE
) {
287 if (settings_map
.map_shift_bias
== MapShiftBias::TRACK
) {
288 if (basic
.track_available
&&
289 basic
.ground_speed_available
&&
290 /* 8 m/s ~ 30 km/h */
291 basic
.ground_speed
> fixed(8)) {
292 auto angle
= basic
.track
.Reciprocal() - visible_projection
.GetScreenAngle();
294 const auto sc
= angle
.SinCos();
298 } else if (settings_map
.map_shift_bias
== MapShiftBias::TARGET
) {
299 if (calculated
.task_stats
.current_leg
.solution_remaining
.IsDefined()) {
300 auto angle
= calculated
.task_stats
.current_leg
.solution_remaining
301 .vector
.bearing
.Reciprocal() - visible_projection
.GetScreenAngle();
303 const auto sc
= angle
.SinCos();
308 fixed position_factor
= fixed(50 - settings_map
.glider_screen_position
) / 100;
309 offset
.x
= PixelScalar(x
* (rc
.right
- rc
.left
) * position_factor
);
310 offset
.y
= PixelScalar(y
* (rc
.top
- rc
.bottom
) * position_factor
);
311 offset_history
.Add(offset
);
312 offset
= offset_history
.GetAverage();
314 visible_projection
.SetScreenOrigin(center
.x
+ offset
.x
, center
.y
+ offset
.y
);
316 visible_projection
.SetScreenOrigin(center
.x
,
317 ((rc
.top
- rc
.bottom
) * settings_map
.glider_screen_position
/ 100) + rc
.bottom
);
320 /* no-op - the Projection's location is updated manually */
321 } else if (circling
&& calculated
.thermal_locator
.estimate_valid
) {
322 const fixed d_t
= calculated
.thermal_locator
.estimate_location
.Distance(basic
.location
);
323 if (!positive(d_t
)) {
324 SetLocationLazy(basic
.location
);
326 const fixed d_max
= Double(visible_projection
.GetMapScale());
327 const fixed t
= std::min(d_t
, d_max
)/d_t
;
328 SetLocation(basic
.location
.Interpolate(calculated
.thermal_locator
.estimate_location
,
331 } else if (basic
.location_available
)
333 SetLocationLazy(basic
.location
);
334 else if (!visible_projection
.IsValid() && terrain
!= nullptr)
335 /* if there's no GPS fix yet and no home waypoint, start at the
336 map center, to avoid showing a fully white map, which confuses
338 SetLocation(terrain
->GetTerrainCenter());
340 visible_projection
.UpdateScreenBounds();