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