1 /*
2  *    Copyright 2012-2019 Kai Pastor
3  *
4  *    This file is part of OpenOrienteering.
5  *
6  *    OpenOrienteering is free software: you can redistribute it and/or modify
7  *    it under the terms of the GNU General Public License as published by
8  *    the Free Software Foundation, either version 3 of the License, or
9  *    (at your option) any later version.
10  *
11  *    OpenOrienteering is distributed in the hope that it will be useful,
12  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *    GNU General Public License for more details.
15  *
16  *    You should have received a copy of the GNU General Public License
17  *    along with OpenOrienteering.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 
21 #ifndef OPENORIENTEERING_GEOREFERENCING_H
22 #define OPENORIENTEERING_GEOREFERENCING_H
23 
24 #include <cmath>
25 #include <vector>
26 
27 #include <QObject>
28 #include <QPointF>
29 #include <QString>
30 #include <QTransform>
31 
32 #include "core/latlon.h"
33 #include "core/map_coord.h"
34 
35 class QDebug;
36 class QXmlStreamReader;
37 class QXmlStreamWriter;
38 // IWYU pragma: no_forward_declare QPointF
39 
40 #ifdef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
41 using ProjTransformData = void;
42 #else
43 using ProjTransformData = struct PJconsts;
44 #endif
45 
46 namespace OpenOrienteering {
47 
48 
49 /**
50  * A utility which encapsulates PROJ API variants and resource management.
51  */
52 struct ProjTransform
53 {
54 	ProjTransform() noexcept = default;
55 	ProjTransform(const ProjTransform&) = delete;
56 	ProjTransform(ProjTransform&& other) noexcept;
57 	ProjTransform(const QString& crs_spec);
58 	~ProjTransform();
59 
60 	ProjTransform& operator=(const ProjTransform& other) = delete;
61 	ProjTransform& operator=(ProjTransform&& other) noexcept;
62 
63 	bool isValid() const noexcept;
64 	bool isGeographic() const;
65 
66 	QPointF forward(const LatLon& lat_lon, bool* ok) const;
67 	LatLon inverse(const QPointF& projected, bool* ok) const;
68 
69 	QString errorText() const;
70 
71 private:
72 	ProjTransform(ProjTransformData* pj) noexcept;
73 
74 	ProjTransformData* pj = nullptr;
75 
76 };
77 
78 
79 
80 /**
81  * A Georeferencing defines a mapping between "map coordinates" (as measured on
82  * paper) and coordinates in the real world. It provides functions for
83  * converting coordinates from one coordinate system to another.
84  *
85  * Conversions between map coordinates and "projected coordinates" (flat metric
86  * coordinates in a projected coordinate reference system) are made as affine
87  * transformation based on the map scale (principal scale), a scale factor
88  * combining grid scale and elevation, the grivation and a defined reference
89  * point.
90  *
91  * Conversions between projected coordinates and geographic coordinates (here:
92  * latitude/longitude for the WGS84 datum) are made based on a specification
93  * of the coordinate reference system of the projected coordinates. The actual
94  * geographic transformation is done by the PROJ library for geographic
95  * projections.
96  *
97  * If no (valid) specification is given, the projected coordinates are regarded
98  * as local coordinates. Local coordinates cannot be converted to other
99  * geographic coordinate systems. The georeferencing is "local".
100  *
101  * Conversions between "map coordinates" and "geographic coordinates" use the
102  * projected coordinates as intermediate step.
103  */
104 class Georeferencing : public QObject  // clazy:exclude=copyable-polymorphic
105 {
106 Q_OBJECT
107 
108 friend QDebug operator<<(QDebug dbg, const Georeferencing& georef);
109 
110 public:
111 	/// Georeferencing state
112 	enum State
113 	{
114 		/// Only conversions between map and local projected coordinates are possible.
115 		Local  = 1,
116 
117 		/// All coordinate conversions are possible (if there is no error in the
118 		/// crs specification).
119 		Normal = 2
120 	};
121 
122 
123 	/**
124 	 * A shared PROJ specification of a WGS84 geographic CRS.
125 	 */
126 	static const QString geographic_crs_spec;
127 
128 
129 	/**
130 	 * @brief Returns the precision of the grid scale factor.
131 	 *
132 	 * The precision is given in number of decimal places,
133 	 * i.e. digits after the decimal point.
134 	 */
135 	static constexpr unsigned int scaleFactorPrecision();
136 
137 	/**
138 	 * @brief Rounds according to the defined precision of the grid scale factor.
139 	 *
140 	 * @see scaleFactorPrecision();
141 	 */
142 	static double roundScaleFactor(double value);
143 
144 
145 	/**
146 	 * @brief Returns the precision of declination/grivation/convergence.
147 	 *
148 	 * The precision is given in number of decimal places,
149 	 * i.e. digits after the decimal point.
150 	 *
151 	 * All values set as declination or grivation will be rounded to this precisison.
152 	 */
153 	static constexpr unsigned int declinationPrecision();
154 
155 	/**
156 	 * @brief Rounds according to the defined precision of declination/grivation/convergence.
157 	 *
158 	 * @see declinationPrecision();
159 	 */
160 	static double roundDeclination(double);
161 
162 
163 	/**
164 	 * Constructs a scale-only georeferencing.
165 	 */
166 	Georeferencing();
167 
168 	/**
169 	 * Constructs a georeferencing which is a copy of an existing georeferencing.
170 	 *
171 	 * Note: Since QObjects may not be copied, this is better understood as
172 	 * creating a new object with the same settings.
173 	 */
174 	Georeferencing(const Georeferencing& other);
175 
176 	/**
177 	 * Cleans up memory allocated by the georeferencing
178 	 */
179 	~Georeferencing() override;
180 
181 
182 	/**
183 	 * Saves the georeferencing to an XML stream.
184 	 */
185 	void save(QXmlStreamWriter& xml) const;
186 
187 	/**
188 	 * Creates a georeferencing from an XML stream.
189 	 */
190 	void load(QXmlStreamReader& xml, bool load_scale_only);
191 
192 
193 	/**
194 	 * Assigns the properties of another Georeferencing to this one.
195 	 *
196 	 * Having this method in a QObject is in contradiction to Qt conventions.
197 	 * But we really need to assign properties from one object to another,
198 	 * where each object maintains its own identity.
199 	 */
200 	Georeferencing& operator=(const Georeferencing& other);
201 
202 
203 	/**
204 	 * Returns if the georeferencing settings are valid.
205 	 *
206 	 * This means that coordinates can be converted between map and projected
207 	 * coordinates. isLocal() can be checked to determine if a conversion
208 	 * to geographic coordinates is also possible.
209 	 */
210 	bool isValid() const;
211 
212 	/**
213 	 * Returns true if this georeferencing is local.
214 	 *
215 	 * A georeferencing is local if no (valid) coordinate system specification
216 	 * is given for the projected coordinates. A local georeferencing cannot
217 	 * convert coordinates from and to geographic coordinate systems.
218 	 */
219 	bool isLocal() const;
220 
221 	/**
222 	 * Returns true if the "projected CRS" is actually geographic.
223 	 *
224 	 * \see proj_angular_output(PJ *, enum PJ_DIRECTION) in PROJ
225 	 */
226 	bool isGeographic() const;
227 
228 
229 	/**
230 	 * Returns the georeferencing state.
231 	 */
232 	State getState() const;
233 
234 	/**
235 	 * Sets the georeferencing state.
236 	 *
237 	 * This is only necessary to decrease the state to Local, as otherwise it
238 	 * will be automatically changed when setting the respective values.
239 	 */
240 	void setState(State value);
241 
242 
243 	/**
244 	 * Returns the principal scale denominator.
245 	 *
246 	 * The principal scale - or representative fraction - is the ratio between
247 	 * units on the printed map and units on ground.
248 	 */
249 	unsigned int getScaleDenominator() const;
250 
251 	/**
252 	 * Sets the principal scale denominator.
253 	 */
254 	void setScaleDenominator(int value);
255 
256 
257 	/**
258 	 * Returns the combined scale factor.
259 	 *
260 	 * The combined factor is applied to ground distances to get grid plane
261 	 * distances. The combined scale factor is the ratio between a length
262 	 * in the grid and the length on the ground.
263 	 *
264 	 * The combined factor incoroprates the grid scale factor and the
265 	 * auxiliary scale factor.
266 	 */
267 	double getCombinedScaleFactor() const;
268 
269 	/**
270 	 * Sets the combined scale factor.
271 	 *
272 	 * \see getCombinedScaleFactor()
273 	 */
274 	void setCombinedScaleFactor(double value);
275 
276 	/**
277 	 * Returns the auxiliary scale factor.
278 	 *
279 	 * The auxiliary scale factor is typically the elevation factor,
280 	 * i.e. the ratio between ground distance and distance on the ellipsoid.
281 	 * If the combined scale factor was set using an earlier Mapper
282 	 * that did not calculate the grid scale factor, then the auxiliary
283 	 * factor deals with the discrepancy between the distance on the
284 	 * map and distance on the ellipsoid.
285 	 *
286 	 * Mapper doesn't explicitly deal with other factors (e.g. unit of
287 	 * measurement). Technically, this property can be used for purposes
288 	 * other than elevation.
289 	 */
290 	double getAuxiliaryScaleFactor() const;
291 
292 	/**
293 	 * Sets the auxiliary scale factor.
294 	 *
295 	 * \see getAuxiliaryScaleFactor()
296 	 */
297 	void setAuxiliaryScaleFactor(double value);
298 
299 
300 	/**
301 	 * Returns the magnetic declination (in degrees).
302 	 *
303 	 * @see setDeclination()
304 	 */
305 	double getDeclination() const;
306 
307 	/**
308 	 * Sets the magnetic declination (in degrees).
309 	 *
310 	 * Magnetic declination is the angle between magnetic north and true north.
311 	 * In the context of OpenOrienteering Mapper, it is the angle between the
312 	 * y axis of map coordinates and the latitude axis of geographic
313 	 * coordinates.
314 	 *
315 	 * This will also affect the grivation value and the transformations.
316 	 */
317 	void setDeclination(double value);
318 
319 
320 	/**
321 	 * Returns the grivation.
322 	 *
323 	 * @see setGrivation()
324 	 */
325 	double getGrivation() const;
326 
327 	/**
328 	 * Returns the deviation of the grivation from the one given in pre-0.6 files.
329 	 *
330 	 * Only valid immediately after loading a georeferencing from a file.
331 	 * Returns 0.0 in any other context.
332 	 *
333 	 * Files from Mapper versions before 0.6 may have used any number of decimal
334 	 * places for grivation. Since version 0.6, grivation is rounded to the
335 	 * number of decimal places defined by declinationPrecision(). When this
336 	 * rounding takes place (i.e. only when opening a file which has not been
337 	 * saved by 0.6 or later), the difference between the original value and the
338 	 * rounded value is temporarily provided by this function. This value can be
339 	 * used to for correcting dependent data. Any changes to declination or
340 	 * grivation will invalidate this value.
341 	 *
342 	 * @see getGrivation()
343 	 * @see declinationPrecision()
344 	 */
345 	double getGrivationError() const;
346 
347 	/**
348 	 * Sets the grivation (in degrees).
349 	 *
350 	 * Grivation is the angle between magnetic north and grid north.
351 	 * In the context of OpenOrienteering Mapper, it is the angle between the y
352 	 * axes of map coordinates and projected coordinates.
353 	 *
354 	 * This will also affect the declination value and the transformations.
355 	 */
356 	void setGrivation(double value);
357 
358 
359 	/**
360 	 * Returns the map coordinates of the reference point.
361 	 */
362 	MapCoord getMapRefPoint() const;
363 
364 	/**
365 	 * Defines the map coordinates of the reference point.
366 	 *
367 	 * This will <b>not</b> update the map and geographic coordinates of the reference point.
368 	 */
369 	void setMapRefPoint(const MapCoord& point);
370 
371 
372 	/**
373 	 * Returns the projected coordinates of the reference point.
374 	 */
375 	QPointF getProjectedRefPoint() const;
376 
377 	/**
378 	 * Defines the projected coordinates of the reference point.
379 	 *
380 	 * This may trigger changes of the geographic coordinates of the reference
381 	 * point, the convergence, the grivation, the transformations, and the
382 	 * scale factors.
383 	 */
384 	void setProjectedRefPoint(const QPointF& point, bool update_grivation = true, bool update_scale_factor = true);
385 
386 
387 	/**
388 	 * Returns the unique id of the projected CRS.
389 	 */
390 	QString getProjectedCRSId() const;
391 
392 	/**
393 	 * Returns the name of the coordinate reference system (CRS) of the
394 	 * projected coordinates.
395 	 */
396 	QString getProjectedCRSName() const;
397 
398 	/**
399 	 * Returns the name of the projected coordinates.
400 	 */
401 	QString getProjectedCoordinatesName() const;
402 
403 	/**
404 	 * Returns the array of projected crs parameter values.
405 	 */
406 	const std::vector<QString>& getProjectedCRSParameters() const;
407 
408 	/**
409 	 * Returns the specification of the coordinate reference system (CRS) of the
410 	 * projected coordinates
411 	 * @return a PROJ specification of the CRS
412 	 */
getProjectedCRSSpec()413 	QString getProjectedCRSSpec() const { return projected_crs_spec; }
414 
415 	/**
416 	 * Sets the coordinate reference system (CRS) of the projected coordinates.
417 	 *
418 	 * This will not touch any of the reference points, the declination, the
419 	 * grivation. It is up to the user to decide how to reestablish a valid
420 	 * configuration of geographic reference point, projected reference point,
421 	 * declination and grivation.
422 	 *
423 	 * @param id  an identifier
424 	 * @param spec the PROJ specification of the CRS
425 	 * @param params parameter values (ignore for empty spec)
426 	 * @return true if the specification is valid or empty, false otherwise
427 	 */
428 	bool setProjectedCRS(const QString& id, QString spec, std::vector< QString > params = std::vector<QString>());
429 
430 	/**
431 	 * Calculates the convergence at the reference point.
432 	 *
433 	 * The convergence is the angle between grid north and true north.
434 	 * In case of deformation, convergence varies with direction; this is an average.
435 	 *
436 	 * @return zero for a local georeferencing, or a calculated approximation
437 	 */
438 	double getConvergence() const;
439 
440 
441 	/**
442 	 * Returns the geographic coordinates of the reference point.
443 	 */
444 	LatLon getGeographicRefPoint() const;
445 
446 	/**
447 	 * Defines the geographic coordinates of the reference point.
448 	 *
449 	 * This may trigger changes of the projected coordinates of the reference
450 	 * point, the convergence, the grivation, the transformations, and the
451 	 * scale factors.
452 	 */
453 	void setGeographicRefPoint(LatLon lat_lon, bool update_grivation = true, bool update_scale_factor = true);
454 
455 
456 	/**
457 	 * Transforms map (paper) coordinates to projected coordinates.
458 	 */
459 	QPointF toProjectedCoords(const MapCoord& map_coords) const;
460 
461 	/**
462 	 * Transforms map (paper) coordinates to projected coordinates.
463 	 */
464 	QPointF toProjectedCoords(const MapCoordF& map_coords) const;
465 
466 	/**
467 	 * Transforms projected coordinates to map (paper) coordinates.
468 	 */
469 	MapCoord toMapCoords(const QPointF& projected_coords) const;
470 
471 	/**
472 	 * Transforms projected coordinates to map (paper) coordinates.
473 	 */
474 	MapCoordF toMapCoordF(const QPointF& projected_coords) const;
475 
476 
477 	/**
478 	 * Transforms map (paper) coordinates to geographic coordinates (lat/lon).
479 	 */
480 	LatLon toGeographicCoords(const MapCoordF& map_coords, bool* ok = 0) const;
481 
482 	/**
483 	 * Transforms CRS coordinates to geographic coordinates (lat/lon).
484 	 */
485 	LatLon toGeographicCoords(const QPointF& projected_coords, bool* ok = 0) const;
486 
487 	/**
488 	 * Transforms geographic coordinates (lat/lon) to CRS coordinates.
489 	 */
490 	QPointF toProjectedCoords(const LatLon& lat_lon, bool* ok = 0) const;
491 
492 	/**
493 	 * Transforms geographic coordinates (lat/lon) to map coordinates.
494 	 */
495 	MapCoord toMapCoords(const LatLon& lat_lon, bool* ok = nullptr) const;
496 
497 	/**
498 	 * Transforms geographic coordinates (lat/lon) to map coordinates.
499 	 */
500 	MapCoordF toMapCoordF(const LatLon& lat_lon, bool* ok = nullptr) const;
501 
502 
503 	/**
504 	 * Transforms map coordinates from the other georeferencing to
505 	 * map coordinates of this georeferencing, if possible.
506 	 */
507 	MapCoordF toMapCoordF(const Georeferencing* other, const MapCoordF& map_coords, bool* ok = nullptr) const;
508 
509 
510 	/**
511 	 * Returns the current error text.
512 	 */
513 	QString getErrorText() const;
514 
515 	/**
516 	 * Converts a value from radians to degrees.
517 	 */
518 	static double radToDeg(double val);
519 
520 	/**
521 	 * Converts a value from degrees to radians.
522 	 */
523 	static double degToRad(double val);
524 
525 	/**
526 	 * Converts a value from degrees to a D°M'S" string.
527 	 */
528 	static QString degToDMS(double val);
529 
530 
531 	/**
532 	 * Updates the transformation parameters between map coordinates and
533 	 * projected coordinates. This depends on the map and projected
534 	 * reference point coordinates, the grivation, the scale, and the
535 	 * combined scale factor.
536 	 */
537 	void updateTransformation();
538 
539 	/**
540 	 * Updates the combined scale factor.
541 	 *
542 	 * The new value is calculated from the grid scale factor and
543 	 * auxiliary scale factor, overriding the present combined_scale_factor
544 	 * value.
545 	 */
546 	void updateCombinedScaleFactor();
547 
548 	/**
549 	 * Initializes the auxiliary scale factor.
550 	 *
551 	 * The new value is calculated from the combined scale factor
552 	 * and grid scale factor, overriding the present auxiliary_scale_factor
553 	 * value.
554 	 */
555 	void initAuxiliaryScaleFactor();
556 
557 	/**
558 	 * Updates the grivation.
559 	 *
560 	 * The new value is calculated from the declination and the convergence.
561 	 * For a local georeferencing, the convergence is zero, and grivation
562 	 * is set to the same value as declination.
563 	 */
564 	void updateGrivation();
565 
566 	/**
567 	 * Initializes the declination.
568 	 *
569 	 * The new value is calculated from the grivation and the convergence.
570 	 * For a local georeferencing, the convergence is zero, and declination
571 	 * is set to the same value as grivation.
572 	 */
573 	void initDeclination();
574 
575 	/**
576 	 * Updates convergence and grid scale factor.
577 	 *
578 	 * The new values are calculated from the CRS and the geographic reference
579 	 * point.
580 	 */
581 	void updateGridCompensation();
582 
583 	/**
584 	 * Sets the transformation matrix from map coordinates to projected
585 	 * coordinates directly.
586 	 */
587 	void setTransformationDirectly(const QTransform& transform);
588 
589 
590 	const QTransform& mapToProjected() const;
591 
592 	const QTransform& projectedToMap() const;
593 
594 
595 signals:
596 	/**
597 	 * Indicates a change of the state property.
598 	 */
599 	void stateChanged();
600 
601 	/**
602 	 * Indicates a change to the transformation rules between map coordinates
603 	 * and projected coordinates.
604 	 */
605 	void transformationChanged();
606 
607 	/**
608 	 * Indicates a change to the projection rules between geographic coordinates
609 	 * and projected coordinates.
610 	 */
611 	void projectionChanged();
612 
613 	/**
614 	 * Indicates a change of the declination.
615 	 *
616 	 * The declination has no direct influence on projection or transformation.
617 	 * That's why there is an independent signal.
618 	 */
619 	void declinationChanged();
620 
621 	/**
622 	 * Indicates a change of the "auxiliary scale factor", as presented in
623 	 * the georeferencing dialog.
624 	 *
625 	 * The "auxiliary scale factor" has no direct influence on projection
626 	 * or transformation. That's why there is an independent signal.
627 	 */
628 	void auxiliaryScaleFactorChanged();
629 
630 
631 private:
632 	void setScaleFactors(double combined_scale_factor, double auxiliary_scale_factor);
633 	void setDeclinationAndGrivation(double declination, double grivation);
634 
635 	State state;
636 
637 	unsigned int scale_denominator;
638 	double combined_scale_factor;
639 	double auxiliary_scale_factor;
640 	double grid_scale_factor;
641 
642 	double declination;
643 	double grivation;
644 	double grivation_error;
645 	double convergence;
646 	MapCoord map_ref_point;
647 
648 	QPointF projected_ref_point;
649 
650 	QTransform from_projected;
651 	QTransform to_projected;
652 
653 	/// Projected CRS id, used for lookup in the CRS registry.
654 	/// If the spec is directly entered as string, the id is empty.
655 	QString projected_crs_id;
656 	QString projected_crs_spec;
657 	std::vector< QString > projected_crs_parameters;
658 
659 	ProjTransform proj_transform;
660 
661 	LatLon geographic_ref_point;
662 
663 };
664 
665 /**
666  * Dumps a Georeferencing to the debug output.
667  *
668  * Note that this requires a *reference*, not a pointer.
669  */
670 QDebug operator<<(QDebug dbg, const Georeferencing &georef);
671 
672 
673 
674 //### Georeferencing inline code ###
675 
676 inline
scaleFactorPrecision()677 constexpr unsigned int Georeferencing::scaleFactorPrecision()
678 {
679 	return 6u;
680 }
681 
682 inline
roundScaleFactor(double value)683 double Georeferencing::roundScaleFactor(double value)
684 {
685 	// This must match the implementation in scaleFactorPrecision().
686 	return floor(value*1000000.0+0.5)/1000000.0;
687 }
688 
689 inline
declinationPrecision()690 constexpr unsigned int Georeferencing::declinationPrecision()
691 {
692 	// This must match the implementation in declinationRound().
693 	return 2u;
694 }
695 
696 inline
roundDeclination(double value)697 double Georeferencing::roundDeclination(double value)
698 {
699 	// This must match the implementation in declinationPrecision().
700 	return floor(value*100.0+0.5)/100.0;
701 }
702 
703 inline
isValid()704 bool Georeferencing::isValid() const
705 {
706 	return state == Local || proj_transform.isValid();
707 }
708 
709 inline
isLocal()710 bool Georeferencing::isLocal() const
711 {
712 	return state == Local;
713 }
714 
715 inline
getState()716 Georeferencing::State Georeferencing::getState() const
717 {
718 	return state;
719 }
720 
721 inline
getScaleDenominator()722 unsigned int Georeferencing::getScaleDenominator() const
723 {
724 	return scale_denominator;
725 }
726 
727 inline
getCombinedScaleFactor()728 double Georeferencing::getCombinedScaleFactor() const
729 {
730 	return combined_scale_factor;
731 }
732 
733 inline
getAuxiliaryScaleFactor()734 double Georeferencing::getAuxiliaryScaleFactor() const
735 {
736 	return auxiliary_scale_factor;
737 }
738 
739 inline
getDeclination()740 double Georeferencing::getDeclination() const
741 {
742 	return declination;
743 }
744 
745 inline
getGrivation()746 double Georeferencing::getGrivation() const
747 {
748 	return grivation;
749 }
750 
751 inline
getGrivationError()752 double Georeferencing::getGrivationError() const
753 {
754 	return grivation_error;
755 }
756 
757 inline
getMapRefPoint()758 MapCoord Georeferencing::getMapRefPoint() const
759 {
760 	return map_ref_point;
761 }
762 
763 inline
getProjectedRefPoint()764 QPointF Georeferencing::getProjectedRefPoint() const
765 {
766 	return projected_ref_point;
767 }
768 
769 inline
getProjectedCRSId()770 QString Georeferencing::getProjectedCRSId() const
771 {
772 	return projected_crs_id;
773 }
774 
775 inline
getProjectedCRSParameters()776 const std::vector<QString>& Georeferencing::getProjectedCRSParameters() const
777 {
778 	return projected_crs_parameters;
779 }
780 
781 inline
getGeographicRefPoint()782 LatLon Georeferencing::getGeographicRefPoint() const
783 {
784 	return geographic_ref_point;
785 }
786 
787 
788 }  // namespace OpenOrienteering
789 
790 #endif
791