1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2014-2019 Kai Pastor
4 *
5 * This file is part of OpenOrienteering.
6 *
7 * OpenOrienteering is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * OpenOrienteering is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "map_view.h"
23
24 #include <algorithm>
25 #include <cmath>
26 #include <iterator>
27 #include <stdexcept>
28
29 #include <QRectF>
30
31 #include <Qt>
32 #include <QLatin1String>
33 #include <QString>
34 #include <QStringRef>
35 #include <QXmlStreamReader>
36
37 #include "core/map.h"
38 #include "core/map_coord.h"
39 #include "gui/util_gui.h"
40 #include "templates/template.h" // IWYU pragma: keep
41 #include "util/util.h"
42 #include "util/xml_stream_util.h"
43
44
45 namespace literal
46 {
47 static const QLatin1String zoom("zoom");
48 static const QLatin1String rotation("rotation");
49 static const QLatin1String position_x("position_x");
50 static const QLatin1String position_y("position_y");
51 static const QLatin1String grid("grid");
52 static const QLatin1String overprinting_simulation_enabled("overprinting_simulation_enabled");
53 static const QLatin1String map("map");
54 static const QLatin1String opacity("opacity");
55 static const QLatin1String visible("visible");
56 static const QLatin1String templates("templates");
57 static const QLatin1String hidden("hidden");
58 static const QLatin1String ref("ref");
59 static const QLatin1String template_string("template");
60 }
61
62
63
64 namespace OpenOrienteering {
65
66 // ### TemplateVisibility ###
67
hasAlpha() const68 bool TemplateVisibility::hasAlpha() const
69 {
70 return visible && opacity > 0 && opacity < 1;
71 }
72
73
74 // ### MapView ###
75
76 const double MapView::zoom_in_limit = 512;
77 const double MapView::zoom_out_limit = 1 / 16.0;
78
79
MapView(QObject * parent,Map * map)80 MapView::MapView(QObject* parent, Map* map)
81 : QObject { parent }
82 , map { map }
83 , zoom { 1.0 }
84 , rotation { 0.0 }
85 , map_visibility{ 1.0f, true }
86 , all_templates_hidden{ false }
87 , grid_visible{ false }
88 , overprinting_simulation_enabled{ false }
89 {
90 Q_ASSERT(map);
91 updateTransform();
92 connect(map, &Map::templateAdded, this, &MapView::onTemplateAdded);
93 connect(map, &Map::templateDeleted, this, &MapView::onTemplateDeleted, Qt::QueuedConnection);
94 }
95
MapView(Map * map)96 MapView::MapView(Map* map)
97 : MapView { map, map }
98 {
99 // nothing else
100 }
101
~MapView()102 MapView::~MapView()
103 {
104 // nothing, not inlined
105 }
106
107
108
save(QXmlStreamWriter & xml,const QLatin1String & element_name,bool template_details) const109 void MapView::save(QXmlStreamWriter& xml, const QLatin1String& element_name, bool template_details) const
110 {
111 // We do not save transient attributes such as rotation (for compass) or pan offset.
112 XmlElementWriter mapview_element(xml, element_name);
113 mapview_element.writeAttribute(literal::zoom, zoom);
114 mapview_element.writeAttribute(literal::position_x, center_pos.nativeX());
115 mapview_element.writeAttribute(literal::position_y, center_pos.nativeY());
116 mapview_element.writeAttribute(literal::grid, grid_visible);
117 mapview_element.writeAttribute(literal::overprinting_simulation_enabled, overprinting_simulation_enabled);
118
119 {
120 XmlElementWriter map_element(xml, literal::map);
121 map_element.writeAttribute(literal::opacity, map_visibility.opacity);
122 map_element.writeAttribute(literal::visible, map_visibility.visible);
123 }
124
125 {
126 XmlElementWriter templates_element(xml, literal::templates);
127 templates_element.writeAttribute(literal::hidden, all_templates_hidden);
128 if (template_details)
129 {
130 templates_element.writeAttribute(XmlStreamLiteral::count, template_visibilities.size());
131 for (auto entry : template_visibilities)
132 {
133 XmlElementWriter ref_element(xml, literal::ref);
134 ref_element.writeAttribute(literal::template_string, map->findTemplateIndex(entry.temp));
135 ref_element.writeAttribute(literal::visible, entry.visible);
136 ref_element.writeAttribute(literal::opacity, entry.opacity);
137 }
138 }
139 }
140 }
141
load(QXmlStreamReader & xml)142 void MapView::load(QXmlStreamReader& xml)
143 {
144 // We do not load transient attributes such as rotation (for compass) or pan offset.
145 XmlElementReader mapview_element(xml);
146 zoom = qMin(mapview_element.attribute<double>(literal::zoom), zoom_in_limit);
147 if (zoom < zoom_out_limit)
148 zoom = 1.0;
149
150 auto center_x = mapview_element.attribute<qint64>(literal::position_x);
151 auto center_y = mapview_element.attribute<qint64>(literal::position_y);
152 try
153 {
154 center_pos = MapCoord::fromNative64withOffset(center_x, center_y);
155 }
156 catch (std::range_error&)
157 {
158 // leave center_pos unchanged
159 }
160 updateTransform();
161
162 grid_visible = mapview_element.attribute<bool>(literal::grid);
163 overprinting_simulation_enabled = mapview_element.attribute<bool>(literal::overprinting_simulation_enabled);
164
165 while (xml.readNextStartElement())
166 {
167 if (xml.name() == literal::map)
168 {
169 XmlElementReader map_element(xml);
170 map_visibility.opacity = map_element.attribute<qreal>(literal::opacity);
171 if (map_element.hasAttribute(literal::visible))
172 map_visibility.visible = map_element.attribute<bool>(literal::visible);
173 else
174 map_visibility.visible = true;
175 }
176 else if (xml.name() == literal::templates)
177 {
178 XmlElementReader templates_element(xml);
179 auto num_template_visibilities = templates_element.attribute<unsigned int>(XmlStreamLiteral::count);
180 template_visibilities.reserve(qBound(20u, num_template_visibilities, 1000u));
181 all_templates_hidden = templates_element.attribute<bool>(literal::hidden);
182
183 while (xml.readNextStartElement())
184 {
185 if (xml.name() == literal::ref)
186 {
187 XmlElementReader ref_element(xml);
188 int pos = ref_element.attribute<int>(literal::template_string);
189 if (pos >= 0 && pos < map->getNumTemplates())
190 {
191 TemplateVisibility vis {
192 qBound(0.0f, ref_element.attribute<float>(literal::opacity), 1.0f),
193 ref_element.attribute<bool>(literal::visible)
194 };
195 setTemplateVisibilityHelper(map->getTemplate(pos), vis);
196 }
197 }
198 else
199 xml.skipCurrentElement();
200 }
201 }
202 else
203 xml.skipCurrentElement(); // unsupported
204 }
205
206 emit viewChanged(CenterChange | ZoomChange | RotationChange);
207 emit visibilityChanged(MultipleFeatures, true);
208 }
209
updateAllMapWidgets()210 void MapView::updateAllMapWidgets()
211 {
212 emit visibilityChanged(MultipleFeatures, true);
213 }
214
mapToView(const MapCoord & coords) const215 QPointF MapView::mapToView(const MapCoord& coords) const
216 {
217 return map_to_view.map(MapCoordF(coords));
218 }
219
mapToView(const QPointF & coords) const220 QPointF MapView::mapToView(const QPointF& coords) const
221 {
222 return map_to_view.map(coords);
223 }
224
lengthToPixel(qreal length) const225 qreal MapView::lengthToPixel(qreal length) const
226 {
227 return Util::mmToPixelPhysical(zoom * length / 1000.0);
228 }
229
pixelToLength(qreal pixel) const230 qreal MapView::pixelToLength(qreal pixel) const
231 {
232 return Util::pixelToMMPhysical(pixel / zoom) * 1000.0;
233 }
234
calculateViewedRect(QRectF rect) const235 QRectF MapView::calculateViewedRect(QRectF rect) const
236 {
237 auto top_left = viewToMapF(rect.topLeft());
238 auto top_right = viewToMapF(rect.topRight());
239 auto bottom_right = viewToMapF(rect.bottomRight());
240 auto bottom_left = viewToMapF(rect.bottomLeft());
241
242 rect = QRectF{ top_left, bottom_right }.normalized();
243 rectInclude(rect, top_right);
244 rectInclude(rect, bottom_left);
245 rect.adjust(-0.001, -0.001, +0.001, +0.001);
246 return rect;
247 }
248
calculateViewBoundingBox(QRectF rect) const249 QRectF MapView::calculateViewBoundingBox(QRectF rect) const
250 {
251 auto top_left = mapToView(static_cast<MapCoordF>(rect.topLeft()));
252 auto top_right = mapToView(static_cast<MapCoordF>(rect.topRight()));
253 auto bottom_right = mapToView(static_cast<MapCoordF>(rect.bottomRight()));
254 auto bottom_left = mapToView(static_cast<MapCoordF>(rect.bottomLeft()));
255
256 rect = QRectF{ top_left, bottom_right }.normalized();
257 rectInclude(rect, top_right);
258 rectInclude(rect, bottom_left);
259 rect.adjust(-1.0, -1.0, +1.0, +1.0);
260 return rect;
261 }
262
setPanOffset(const QPoint & offset)263 void MapView::setPanOffset(const QPoint& offset)
264 {
265 if (offset != pan_offset)
266 {
267 pan_offset = offset;
268 emit panOffsetChanged(offset);
269 }
270 }
271
finishPanning(const QPoint & offset)272 void MapView::finishPanning(const QPoint& offset)
273 {
274 setPanOffset({0,0});
275 try
276 {
277 auto rotated_offset = MapCoord::fromNative64(qRound64(-pixelToLength(offset.x())),
278 qRound64(-pixelToLength(offset.y())) );
279 auto rotated_offset_f = MapCoordF{ rotated_offset };
280 rotated_offset_f.rotate(-rotation);
281 auto move = MapCoord{ rotated_offset_f };
282 setCenter(center() + move);
283 }
284 catch (std::range_error&)
285 {
286 // Do nothing
287 }
288 }
289
zoomSteps(double num_steps,const QPointF & cursor_pos_view)290 void MapView::zoomSteps(double num_steps, const QPointF& cursor_pos_view)
291 {
292 auto zoom_to = getZoom() * pow(sqrt(2.0), num_steps);
293 setZoom(zoom_to, cursor_pos_view);
294 }
295
zoomSteps(double num_steps)296 void MapView::zoomSteps(double num_steps)
297 {
298 auto zoom_to = getZoom() * pow(sqrt(2.0), num_steps);
299 setZoom(zoom_to);
300 }
301
setZoom(double value,const QPointF & center)302 void MapView::setZoom(double value, const QPointF& center)
303 {
304 auto pos = this->center();
305 auto zoom_pos = viewToMap(center);
306 auto old_zoom = getZoom();
307
308 setZoom(value);
309
310 if (!qFuzzyCompare(old_zoom, getZoom()))
311 {
312 auto zoom_factor = getZoom() / old_zoom ;
313 setCenter(zoom_pos + (pos - zoom_pos) / zoom_factor);
314 }
315 }
316
setZoom(double value)317 void MapView::setZoom(double value)
318 {
319 zoom = qBound(zoom_out_limit, value, zoom_in_limit);
320 updateTransform();
321 emit viewChanged(ZoomChange);
322 }
323
setRotation(double value)324 void MapView::setRotation(double value)
325 {
326 rotation = value;
327 updateTransform();
328 emit viewChanged(RotationChange);
329 }
330
setCenter(const MapCoord & pos)331 void MapView::setCenter(const MapCoord& pos)
332 {
333 center_pos = pos;
334 updateTransform();
335 emit viewChanged(CenterChange);
336 }
337
updateTransform()338 void MapView::updateTransform()
339 {
340 double final_zoom = calculateFinalZoomFactor();
341 double final_zoom_cosr = final_zoom * cos(rotation);
342 double final_zoom_sinr = final_zoom * sin(rotation);
343 auto center_x = center_pos.x();
344 auto center_y = center_pos.y();
345
346 // Create map_to_view
347 map_to_view = { final_zoom_cosr, final_zoom_sinr,
348 -final_zoom_sinr, final_zoom_cosr,
349 -final_zoom_cosr * center_x + final_zoom_sinr * center_y,
350 -final_zoom_sinr * center_x - final_zoom_cosr * center_y };
351 view_to_map = map_to_view.inverted();
352 }
353
354
effectiveMapVisibility() const355 TemplateVisibility MapView::effectiveMapVisibility() const
356 {
357 if (all_templates_hidden)
358 return { 1.0f, true };
359 else if (map_visibility.opacity < 0.005)
360 return { 0.0f, false };
361 else
362 return map_visibility;
363 }
364
setMapVisibility(TemplateVisibility vis)365 void MapView::setMapVisibility(TemplateVisibility vis)
366 {
367 if (map_visibility != vis)
368 {
369 map_visibility = vis;
370 emit visibilityChanged(VisibilityFeature::MapVisible, vis.visible && vis.opacity > 0, nullptr);
371 }
372 }
373
findVisibility(const Template * temp) const374 MapView::TemplateVisibilityVector::const_iterator MapView::findVisibility(const Template* temp) const
375 {
376 return std::find_if(begin(template_visibilities), end(template_visibilities), [temp](const TemplateVisibilityEntry& entry)
377 {
378 return entry.temp == temp;
379 } );
380 }
381
findVisibility(const Template * temp)382 MapView::TemplateVisibilityVector::iterator MapView::findVisibility(const Template* temp)
383 {
384 return std::find_if(begin(template_visibilities), end(template_visibilities), [temp](const TemplateVisibilityEntry& entry)
385 {
386 return entry.temp == temp;
387 } );
388 }
389
isTemplateVisible(const Template * temp) const390 bool MapView::isTemplateVisible(const Template* temp) const
391 {
392 auto entry = findVisibility(temp);
393 return entry != end(template_visibilities)
394 && entry->visible
395 && entry->opacity > 0;
396 }
397
getTemplateVisibility(const Template * temp) const398 TemplateVisibility MapView::getTemplateVisibility(const Template* temp) const
399 {
400 auto entry = findVisibility(temp);
401 if (entry != end(template_visibilities))
402 return *entry;
403 else
404 return { 1.0f, false };
405 }
406
setTemplateVisibility(Template * temp,TemplateVisibility vis)407 void MapView::setTemplateVisibility(Template* temp, TemplateVisibility vis)
408 {
409 auto visible = vis.visible && vis.opacity > 0;
410 if (visible
411 && temp->getTemplateState() != Template::Loaded
412 && !templateLoadingBlocked())
413 {
414 vis.visible = visible = temp->loadTemplateFile(false);
415 }
416
417 if (setTemplateVisibilityHelper(temp, vis))
418 {
419 emit visibilityChanged(VisibilityFeature::TemplateVisible, visible, temp);
420 }
421 }
422
setTemplateVisibilityHelper(const Template * temp,TemplateVisibility vis)423 bool MapView::setTemplateVisibilityHelper(const Template *temp, TemplateVisibility vis)
424 {
425 auto stored = findVisibility(temp);
426 if (stored == end(template_visibilities))
427 {
428 template_visibilities.emplace_back(temp, vis);
429 return true;
430 }
431 if (*stored != vis)
432 {
433 stored->opacity = vis.opacity;
434 stored->visible = vis.visible;
435 return true;
436 }
437 return false;
438 }
439
onTemplateAdded(int,Template * temp)440 void MapView::onTemplateAdded(int, Template* temp)
441 {
442 setTemplateVisibility(temp, { 1.0f, true });
443 }
444
onTemplateDeleted(int,const Template * temp)445 void MapView::onTemplateDeleted(int, const Template* temp)
446 {
447 template_visibilities.erase(findVisibility(temp));
448 }
449
450
setAllTemplatesHidden(bool value)451 void MapView::setAllTemplatesHidden(bool value)
452 {
453 if (all_templates_hidden != value)
454 {
455 all_templates_hidden = value;
456 emit visibilityChanged(VisibilityFeature::AllTemplatesHidden, value);
457 }
458 }
459
setGridVisible(bool visible)460 void MapView::setGridVisible(bool visible)
461 {
462 if (grid_visible != visible)
463 {
464 grid_visible = visible;
465 emit visibilityChanged(VisibilityFeature::GridVisible, visible);
466 }
467 }
468
setOverprintingSimulationEnabled(bool enabled)469 void MapView::setOverprintingSimulationEnabled(bool enabled)
470 {
471 if (overprinting_simulation_enabled != enabled)
472 {
473 overprinting_simulation_enabled = enabled;
474 emit visibilityChanged(VisibilityFeature::OverprintingEnabled, enabled);
475 }
476 }
477
478
479
hasAlpha() const480 bool MapView::hasAlpha() const
481 {
482 auto map_visibility = effectiveMapVisibility();
483 if (map_visibility.hasAlpha() || map->hasAlpha())
484 return true;
485
486 if (grid_visible && map->getGrid().hasAlpha())
487 return true;
488
489 for (int i = 0; i < map->getNumTemplates(); ++i)
490 {
491 auto temp = map->getTemplate(i);
492 auto visibility = getTemplateVisibility(temp);
493 if (visibility.hasAlpha() || temp->hasAlpha())
494 return true;
495 }
496
497 return false;
498 }
499
500
501
setTemplateLoadingBlocked(bool blocked)502 void MapView::setTemplateLoadingBlocked(bool blocked)
503 {
504 template_loading_blocked = blocked;
505 }
506
507
508 } // namespace OpenOrienteering
509