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