1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2012-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 "georeferencing_dialog.h"
23
24 #include <cmath>
25 #include <vector>
26
27 #include <Qt>
28 #include <QtGlobal>
29 #include <QAbstractButton>
30 #include <QCheckBox>
31 #include <QCursor>
32 #include <QDate>
33 #include <QDebug>
34 #include <QDesktopServices> // IWYU pragma: keep
35 #include <QDialogButtonBox>
36 #include <QDoubleSpinBox>
37 #include <QFlags>
38 #include <QFormLayout>
39 #include <QHBoxLayout>
40 #include <QLabel>
41 #include <QLatin1Char>
42 #include <QLatin1String>
43 #include <QLocale>
44 #include <QMessageBox>
45 #include <QMouseEvent>
46 #include <QPixmap>
47 #include <QPointF>
48 #include <QPushButton>
49 #include <QRadioButton>
50 #include <QSignalBlocker>
51 #include <QSize>
52 #include <QSpacerItem>
53 #include <QStringRef>
54 #include <QTimer>
55 #include <QUrl>
56 #include <QUrlQuery>
57 #include <QVariant>
58 #include <QVBoxLayout>
59 #include <QWidget>
60 #include <QXmlStreamReader>
61 // IWYU pragma: no_include <qxmlstream.h>
62
63 #if defined(QT_NETWORK_LIB)
64 #include <QNetworkAccessManager>
65 #include <QNetworkReply>
66 #include <QNetworkRequest>
67 #endif
68
69 #include "settings.h"
70 #include "core/crs_template.h"
71 #include "core/georeferencing.h"
72 #include "core/latlon.h"
73 #include "core/map.h"
74 #include "gui/main_window.h"
75 #include "gui/map/map_dialog_rotate.h"
76 #include "gui/map/map_dialog_stretch.h"
77 #include "gui/map/map_editor.h"
78 #include "gui/widgets/crs_selector.h"
79 #include "gui/util_gui.h"
80 #include "util/backports.h" // IWYU pragma: keep
81 #include "util/scoped_signals_blocker.h"
82
83
84 #ifdef __clang_analyzer__
85 #define singleShot(A, B, C) singleShot(A, B, #C) // NOLINT
86 #endif
87
88
89 namespace OpenOrienteering {
90
91 Q_STATIC_ASSERT(Georeferencing::declinationPrecision() == Util::InputProperties<Util::RotationalDegrees>::decimals());
92
93
94 namespace {
95
setValueIfChanged(QDoubleSpinBox * field,qreal value)96 void setValueIfChanged(QDoubleSpinBox* field, qreal value) {
97 if (!qFuzzyCompare(field->value(), value))
98 field->setValue(value);
99 }
100
101 } // namespace
102
103
104
105 // ### GeoreferencingDialog ###
106
GeoreferencingDialog(MapEditorController * controller,const Georeferencing * initial,bool allow_no_georeferencing)107 GeoreferencingDialog::GeoreferencingDialog(MapEditorController* controller, const Georeferencing* initial, bool allow_no_georeferencing)
108 : GeoreferencingDialog(controller->getWindow(), controller, controller->getMap(), initial, allow_no_georeferencing)
109 {
110 // nothing else
111 }
112
GeoreferencingDialog(QWidget * parent,Map * map,const Georeferencing * initial,bool allow_no_georeferencing)113 GeoreferencingDialog::GeoreferencingDialog(QWidget* parent, Map* map, const Georeferencing* initial, bool allow_no_georeferencing)
114 : GeoreferencingDialog(parent, nullptr, map, initial, allow_no_georeferencing)
115 {
116 // nothing else
117 }
118
GeoreferencingDialog(QWidget * parent,MapEditorController * controller,Map * map,const Georeferencing * initial,bool allow_no_georeferencing)119 GeoreferencingDialog::GeoreferencingDialog(
120 QWidget* parent,
121 MapEditorController* controller,
122 Map* map,
123 const Georeferencing* initial,
124 bool allow_no_georeferencing )
125 : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint)
126 , controller(controller)
127 , map(map)
128 , initial_georef(initial ? initial : &map->getGeoreferencing())
129 , georef(new Georeferencing(*initial_georef))
130 , allow_no_georeferencing(allow_no_georeferencing)
131 , tool_active(false)
132 , declination_query_in_progress(false)
133 , grivation_locked(!initial_georef->isValid() || initial_georef->getState() != Georeferencing::Normal)
134 , scale_factor_locked(grivation_locked)
135 {
136 setWindowTitle(tr("Map Georeferencing"));
137 setWindowModality(Qt::WindowModal);
138
139 // Create widgets
140 auto map_crs_label = Util::Headline::create(tr("Map coordinate reference system"));
141
142 crs_selector = new CRSSelector(*georef, nullptr);
143 crs_selector->addCustomItem(tr("- local -"), Georeferencing::Local);
144
145 status_label = new QLabel(tr("Status:"));
146 status_field = new QLabel();
147
148 auto reference_point_label = Util::Headline::create(tr("Reference point"));
149
150 ref_point_button = new QPushButton(tr("&Pick on map"));
151 int ref_point_button_width = ref_point_button->sizeHint().width();
152 auto geographic_datum_label = new QLabel(tr("(Datum: WGS84)"));
153 int geographic_datum_label_width = geographic_datum_label->sizeHint().width();
154
155 map_x_edit = Util::SpinBox::create<MapCoordF>(tr("mm"));
156 map_y_edit = Util::SpinBox::create<MapCoordF>(tr("mm"));
157 ref_point_button->setEnabled(controller);
158 auto map_ref_layout = new QHBoxLayout();
159 map_ref_layout->addWidget(map_x_edit, 1);
160 map_ref_layout->addWidget(new QLabel(tr("X", "x coordinate")), 0);
161 map_ref_layout->addWidget(map_y_edit, 1);
162 map_ref_layout->addWidget(new QLabel(tr("Y", "y coordinate")), 0);
163 if (ref_point_button_width < geographic_datum_label_width)
164 map_ref_layout->addSpacing(geographic_datum_label_width - ref_point_button_width);
165 map_ref_layout->addWidget(ref_point_button, 0);
166
167 easting_edit = Util::SpinBox::create<Util::RealMeters>(tr("m"));
168 northing_edit = Util::SpinBox::create<Util::RealMeters>(tr("m"));
169 auto projected_ref_layout = new QHBoxLayout();
170 projected_ref_layout->addWidget(easting_edit, 1);
171 projected_ref_layout->addWidget(new QLabel(tr("E", "west / east")), 0);
172 projected_ref_layout->addWidget(northing_edit, 1);
173 projected_ref_layout->addWidget(new QLabel(tr("N", "north / south")), 0);
174 projected_ref_layout->addSpacing(qMax(ref_point_button_width, geographic_datum_label_width));
175
176 projected_ref_label = new QLabel();
177 lat_edit = Util::SpinBox::create(8, -90.0, +90.0, Util::InputProperties<Util::RotationalDegrees>::unit());
178 lon_edit = Util::SpinBox::create(8, -180.0, +180.0, Util::InputProperties<Util::RotationalDegrees>::unit());
179 lon_edit->setWrapping(true);
180 auto geographic_ref_layout = new QHBoxLayout();
181 geographic_ref_layout->addWidget(lat_edit, 1);
182 geographic_ref_layout->addWidget(new QLabel(tr("N", "north")), 0);
183 geographic_ref_layout->addWidget(lon_edit, 1);
184 geographic_ref_layout->addWidget(new QLabel(tr("E", "east")), 0);
185 if (geographic_datum_label_width < ref_point_button_width)
186 geographic_ref_layout->addSpacing(ref_point_button_width - geographic_datum_label_width);
187 geographic_ref_layout->addWidget(geographic_datum_label, 0);
188
189 show_refpoint_label = new QLabel(tr("Show reference point in:"));
190 link_label = new QLabel();
191 link_label->setOpenExternalLinks(true);
192
193 keep_projected_radio = new QRadioButton(tr("Projected coordinates"));
194 keep_geographic_radio = new QRadioButton(tr("Geographic coordinates"));
195 if (georef->getState() == Georeferencing::Normal && georef->isValid())
196 {
197 keep_geographic_radio->setChecked(true);
198 }
199 else
200 {
201 keep_geographic_radio->setEnabled(false);
202 keep_projected_radio->setCheckable(true);
203 }
204
205 auto map_north_label = Util::Headline::create(tr("Map north"));
206
207 declination_edit = Util::SpinBox::create<Util::RotationalDegrees>();
208 declination_button = new QPushButton(tr("Lookup..."));
209 auto declination_layout = new QHBoxLayout();
210 declination_layout->addWidget(declination_edit, 1);
211 declination_layout->addWidget(declination_button, 0);
212
213 grivation_label = new QLabel();
214
215 show_scale_check = new QCheckBox(tr("Show scale factors"));
216 auto scale_compensation_label = Util::Headline::create(tr("Scale compensation"));
217
218 /*: The combined scale factor is the ratio between a length on the ground
219 and the corresponding length on the curved earth model. It is applied
220 as a factor to ground distances to get grid plane distances. */
221 auto combined_factor_label = new QLabel(tr("Combined scale factor:"));
222 combined_factor_display = new QLabel();
223
224 /*: The auxiliary scale factor is the ratio between a length in the curved
225 earth model and the corresponding length on the ground. It is applied
226 as a factor to ground distances to get curved earth model distances. */
227 auto auxiliary_factor_label = new QLabel(tr("Auxiliary scale factor:"));
228 scale_factor_edit = Util::SpinBox::create(Georeferencing::scaleFactorPrecision(), 0.001, 1000.0);
229 scale_widget_list = {
230 scale_compensation_label,
231 auxiliary_factor_label, scale_factor_edit,
232 combined_factor_label, combined_factor_display
233 };
234
235 buttons_box = new QDialogButtonBox(
236 QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Reset | QDialogButtonBox::Help,
237 Qt::Horizontal);
238 reset_button = buttons_box->button(QDialogButtonBox::Reset);
239 reset_button->setEnabled(initial);
240 auto help_button = buttons_box->button(QDialogButtonBox::Help);
241
242 auto edit_layout = new QFormLayout();
243
244 edit_layout->addRow(map_crs_label);
245 edit_layout->addRow(tr("&Coordinate reference system:"), crs_selector);
246 crs_selector->setDialogLayout(edit_layout);
247 edit_layout->addRow(status_label, status_field);
248 edit_layout->addItem(Util::SpacerItem::create(this));
249
250 edit_layout->addRow(reference_point_label);
251 edit_layout->addRow(tr("Map coordinates:"), map_ref_layout);
252 edit_layout->addRow(projected_ref_label, projected_ref_layout);
253 edit_layout->addRow(tr("Geographic coordinates:"), geographic_ref_layout);
254 edit_layout->addRow(show_refpoint_label, link_label);
255 edit_layout->addRow(show_refpoint_label, link_label);
256 edit_layout->addRow(tr("On CRS changes, keep:"), keep_projected_radio);
257 edit_layout->addRow({}, keep_geographic_radio);
258 edit_layout->addItem(Util::SpacerItem::create(this));
259
260 edit_layout->addRow(map_north_label);
261 edit_layout->addRow(tr("Declination:"), declination_layout);
262 edit_layout->addRow(tr("Grivation:"), grivation_label);
263
264 bool control_scale_factor = Settings::getInstance().getSetting(Settings::MapGeoreferencing_ControlScaleFactor).toBool();
265 edit_layout->addItem(Util::SpacerItem::create(this));
266 edit_layout->addRow(show_scale_check);
267 edit_layout->addRow(scale_compensation_label);
268 edit_layout->addRow(auxiliary_factor_label, scale_factor_edit);
269 edit_layout->addRow(combined_factor_label, combined_factor_display);
270 show_scale_check->setChecked(control_scale_factor);
271 for (auto scale_widget: scale_widget_list)
272 scale_widget->setVisible(control_scale_factor);
273
274 auto layout = new QVBoxLayout();
275 layout->addLayout(edit_layout);
276 layout->addStretch();
277 layout->addSpacing(16);
278 layout->addWidget(buttons_box);
279
280 setLayout(layout);
281
282 connect(crs_selector, &CRSSelector::crsChanged, this, &GeoreferencingDialog::crsEdited);
283
284 connect(show_scale_check, &QAbstractButton::clicked, this, &GeoreferencingDialog::showScaleChanged);
285 connect(scale_factor_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::auxiliaryFactorEdited);
286
287 connect(map_x_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::mapRefChanged);
288 connect(map_y_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::mapRefChanged);
289 connect(ref_point_button, &QPushButton::clicked, this, &GeoreferencingDialog::selectMapRefPoint);
290
291 connect(easting_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::eastingNorthingEdited);
292 connect(northing_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::eastingNorthingEdited);
293
294 connect(lat_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::latLonEdited);
295 connect(lon_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::latLonEdited);
296 connect(keep_geographic_radio, &QRadioButton::toggled, this, &GeoreferencingDialog::keepCoordsChanged);
297
298 connect(declination_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &GeoreferencingDialog::declinationEdited);
299 connect(declination_button, &QPushButton::clicked, this, &GeoreferencingDialog::requestDeclination);
300
301 connect(buttons_box, &QDialogButtonBox::accepted, this, &GeoreferencingDialog::accept);
302 connect(buttons_box, &QDialogButtonBox::rejected, this, &GeoreferencingDialog::reject);
303 connect(reset_button, &QPushButton::clicked, this, &GeoreferencingDialog::reset);
304 connect(help_button, &QPushButton::clicked, this, &GeoreferencingDialog::showHelp);
305
306 connect(georef.data(), &Georeferencing::stateChanged, this, &GeoreferencingDialog::georefStateChanged);
307 connect(georef.data(), &Georeferencing::transformationChanged, this, &GeoreferencingDialog::transformationChanged);
308 connect(georef.data(), &Georeferencing::projectionChanged, this, &GeoreferencingDialog::projectionChanged);
309 connect(georef.data(), &Georeferencing::declinationChanged, this, &GeoreferencingDialog::declinationChanged);
310 connect(georef.data(), &Georeferencing::auxiliaryScaleFactorChanged, this, &GeoreferencingDialog::auxiliaryFactorChanged);
311
312 transformationChanged();
313 georefStateChanged();
314 declinationChanged();
315 auxiliaryFactorChanged();
316 }
317
~GeoreferencingDialog()318 GeoreferencingDialog::~GeoreferencingDialog()
319 {
320 if (tool_active)
321 controller->setOverrideTool(nullptr);
322 }
323
324 // slot
georefStateChanged()325 void GeoreferencingDialog::georefStateChanged()
326 {
327 const QSignalBlocker block(crs_selector);
328
329 switch (georef->getState())
330 {
331 case Georeferencing::Local:
332 crs_selector->setCurrentItem(Georeferencing::Local);
333 keep_geographic_radio->setEnabled(false);
334 keep_projected_radio->setChecked(true);
335 break;
336 default:
337 qDebug() << "Unhandled georeferencing state:" << georef->getState();
338 Q_FALLTHROUGH();
339 case Georeferencing::Normal:
340 projectionChanged();
341 keep_geographic_radio->setEnabled(true);
342 }
343
344 updateWidgets();
345 }
346
347 // slot
transformationChanged()348 void GeoreferencingDialog::transformationChanged()
349 {
350 ScopedMultiSignalsBlocker block(
351 map_x_edit, map_y_edit,
352 easting_edit, northing_edit,
353 scale_factor_edit
354 );
355
356 setValueIfChanged(map_x_edit, georef->getMapRefPoint().x());
357 setValueIfChanged(map_y_edit, -georef->getMapRefPoint().y());
358
359 setValueIfChanged(easting_edit, georef->getProjectedRefPoint().x());
360 setValueIfChanged(northing_edit, georef->getProjectedRefPoint().y());
361
362 setValueIfChanged(scale_factor_edit, georef->getAuxiliaryScaleFactor());
363
364 updateGrivation();
365 updateCombinedFactor();
366 }
367
368 // slot
projectionChanged()369 void GeoreferencingDialog::projectionChanged()
370 {
371 ScopedMultiSignalsBlocker block(
372 crs_selector,
373 lat_edit, lon_edit
374 );
375
376 if (georef->getState() == Georeferencing::Normal)
377 {
378 const std::vector< QString >& parameters = georef->getProjectedCRSParameters();
379 auto temp = CRSTemplateRegistry().find(georef->getProjectedCRSId());
380 if (!temp || temp->parameters().size() != parameters.size())
381 {
382 // The CRS id is not there anymore or the number of parameters has changed.
383 // Enter as custom spec.
384 crs_selector->setCurrentCRS(CRSTemplateRegistry().find(QString::fromLatin1("PROJ.4")), { georef->getProjectedCRSSpec() });
385 }
386 else
387 {
388 crs_selector->setCurrentCRS(temp, parameters);
389 }
390 }
391
392 LatLon latlon = georef->getGeographicRefPoint();
393 double latitude = latlon.latitude();
394 double longitude = latlon.longitude();
395 setValueIfChanged(lat_edit, latitude);
396 setValueIfChanged(lon_edit, longitude);
397 QString osm_link =
398 QString::fromLatin1("http://www.openstreetmap.org/?lat=%1&lon=%2&zoom=18&layers=M").
399 arg(latitude).arg(longitude);
400 QString worldofo_link =
401 QString::fromLatin1("http://maps.worldofo.com/?zoom=15&lat=%1&lng=%2").
402 arg(latitude).arg(longitude);
403 link_label->setText(
404 tr("<a href=\"%1\">OpenStreetMap</a> | <a href=\"%2\">World of O Maps</a>").
405 arg(osm_link, worldofo_link)
406 );
407
408 QString error = georef->getErrorText();
409 if (error.length() == 0)
410 status_field->setText(tr("valid"));
411 else
412 status_field->setText(QLatin1String("<b style=\"color:red\">") + error + QLatin1String("</b>"));
413 }
414
415 // slot
declinationChanged()416 void GeoreferencingDialog::declinationChanged()
417 {
418 const QSignalBlocker block(declination_edit);
419 setValueIfChanged(declination_edit, georef->getDeclination());
420 }
421
422 // slot
auxiliaryFactorChanged()423 void GeoreferencingDialog::auxiliaryFactorChanged()
424 {
425 const QSignalBlocker block(scale_factor_edit);
426 setValueIfChanged(scale_factor_edit, georef->getAuxiliaryScaleFactor());
427 updateCombinedFactor();
428 }
429
requestDeclination(bool no_confirm)430 void GeoreferencingDialog::requestDeclination(bool no_confirm)
431 {
432 if (georef->isLocal())
433 return;
434
435 /// \todo Move URL (template) to settings.
436 QString user_url(QString::fromLatin1("https://www.ngdc.noaa.gov/geomag-web/"));
437 QUrl service_url(user_url + QLatin1String("calculators/calculateDeclination"));
438 LatLon latlon(georef->getGeographicRefPoint());
439
440 if (!no_confirm)
441 {
442 int result = QMessageBox::question(this, tr("Online declination lookup"),
443 trUtf8("The magnetic declination for the reference point %1° %2° will now be retrieved from <a href=\"%3\">%3</a>. Do you want to continue?").
444 arg(latlon.latitude()).arg(latlon.longitude()).arg(user_url),
445 QMessageBox::Yes | QMessageBox::No,
446 QMessageBox::Yes );
447 if (result != QMessageBox::Yes)
448 return;
449 }
450
451 QUrlQuery query;
452 QDate today = QDate::currentDate();
453 query.addQueryItem(QString::fromLatin1("lat1"), QString::number(latlon.latitude()));
454 query.addQueryItem(QString::fromLatin1("lon1"), QString::number(latlon.longitude()));
455 query.addQueryItem(QString::fromLatin1("startYear"), QString::number(today.year()));
456 query.addQueryItem(QString::fromLatin1("startMonth"), QString::number(today.month()));
457 query.addQueryItem(QString::fromLatin1("startDay"), QString::number(today.day()));
458
459 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) || defined(Q_OS_ANDROID) || !defined(QT_NETWORK_LIB)
460 // No QtNetwork or no OpenSSL: open result in system browser.
461 query.addQueryItem(QString::fromLatin1("resultFormat"), QString::fromLatin1("html"));
462 service_url.setQuery(query);
463 QDesktopServices::openUrl(service_url);
464 #else
465 // Use result directly
466 query.addQueryItem(QString::fromLatin1("resultFormat"), QString::fromLatin1("xml"));
467 service_url.setQuery(query);
468
469 declination_query_in_progress = true;
470 updateDeclinationButton();
471
472 auto network = new QNetworkAccessManager(this);
473 connect(network, &QNetworkAccessManager::finished, this, &GeoreferencingDialog::declinationReplyFinished);
474 network->get(QNetworkRequest(service_url));
475 #endif
476 }
477
setMapRefPoint(const MapCoord & coords)478 void GeoreferencingDialog::setMapRefPoint(const MapCoord& coords)
479 {
480 georef->setMapRefPoint(coords);
481 reset_button->setEnabled(true);
482 }
483
setKeepProjectedRefCoords()484 void GeoreferencingDialog::setKeepProjectedRefCoords()
485 {
486 keep_projected_radio->setChecked(true);
487 reset_button->setEnabled(true);
488 }
489
setKeepGeographicRefCoords()490 void GeoreferencingDialog::setKeepGeographicRefCoords()
491 {
492 keep_geographic_radio->setChecked(true);
493 reset_button->setEnabled(true);
494 }
495
toolDeleted()496 void GeoreferencingDialog::toolDeleted()
497 {
498 tool_active = false;
499 }
500
showHelp()501 void GeoreferencingDialog::showHelp()
502 {
503 Util::showHelp(parentWidget(), "georeferencing.html");
504 }
505
reset()506 void GeoreferencingDialog::reset()
507 {
508 scale_factor_locked = grivation_locked = ( !initial_georef->isValid() || initial_georef->getState() != Georeferencing::Normal );
509 *georef.data() = *initial_georef;
510 reset_button->setEnabled(false);
511 }
512
accept()513 void GeoreferencingDialog::accept()
514 {
515 auto const declination_change_degrees = georef->getDeclination() - initial_georef->getDeclination();
516 auto const scale_factor_change = georef->getAuxiliaryScaleFactor() / initial_georef->getAuxiliaryScaleFactor();
517 if (grivation_locked)
518 {
519 georef->updateGrivation();
520 }
521 else if (!qIsNull(declination_change_degrees)
522 && (map->getNumObjects() > 0 || map->getNumTemplates() > 0))
523 {
524 int result = QMessageBox::question(this, tr("Declination change"), tr("The declination has been changed. Do you want to rotate the map content accordingly, too?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
525 if (result == QMessageBox::Cancel)
526 {
527 return;
528 }
529 else if (result == QMessageBox::Yes)
530 {
531 RotateMapDialog dialog(this, map);
532 dialog.setWindowModality(Qt::WindowModal);
533 dialog.setRotationDegrees(declination_change_degrees);
534 dialog.setRotateAroundGeorefRefPoint();
535 dialog.setAdjustDeclination(false);
536 dialog.showAdjustDeclination(false);
537 int result = dialog.exec();
538 if (result == QDialog::Rejected)
539 return;
540 }
541 }
542 if (scale_factor_locked)
543 {
544 georef->updateCombinedScaleFactor();
545 }
546 else if (!qIsNull(std::log(scale_factor_change))
547 && (map->getNumObjects() > 0 || map->getNumTemplates() > 0))
548 {
549 int result = QMessageBox::question(this, tr("Scale factor change"), tr("The scale factor has been changed. Do you want to stretch/shrink the map content accordingly, too?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
550 if (result == QMessageBox::Cancel)
551 {
552 return;
553 }
554 else if (result == QMessageBox::Yes)
555 {
556 StretchMapDialog dialog(this, map, 1.0/scale_factor_change);
557 dialog.setWindowModality(Qt::WindowModal);
558 int result = dialog.exec();
559 if (result == QDialog::Rejected)
560 return;
561 }
562 }
563
564 map->setGeoreferencing(*georef);
565 QDialog::accept();
566 }
567
updateWidgets()568 void GeoreferencingDialog::updateWidgets()
569 {
570 ref_point_button->setEnabled(controller);
571
572 if (crs_selector->currentCRSTemplate())
573 projected_ref_label->setText(crs_selector->currentCRSTemplate()->coordinatesName(crs_selector->parameters()) + QLatin1Char(':'));
574 else
575 projected_ref_label->setText(tr("Local coordinates:"));
576
577 bool geographic_coords_enabled = crs_selector->currentCustomItem() != Georeferencing::Local;
578 status_label->setVisible(geographic_coords_enabled);
579 status_field->setVisible(geographic_coords_enabled);
580 lat_edit->setEnabled(geographic_coords_enabled);
581 lon_edit->setEnabled(geographic_coords_enabled);
582 link_label->setEnabled(geographic_coords_enabled);
583 //keep_geographic_radio->setEnabled(geographic_coords_enabled);
584
585 updateDeclinationButton();
586
587 buttons_box->button(QDialogButtonBox::Ok)->setEnabled(georef->isValid());
588 }
589
updateDeclinationButton()590 void GeoreferencingDialog::updateDeclinationButton()
591 {
592 /*
593 bool dialog_enabled = crs_edit->getSelectedCustomItemId() != 0;
594 bool proj_spec_visible = crs_edit->getSelectedCustomItemId() == 1;
595 bool geographic_coords_enabled =
596 dialog_enabled &&
597 (proj_spec_visible ||
598 crs_edit->getSelectedCustomItemId() == -1);
599 */
600 bool enabled = lat_edit->isEnabled() && !declination_query_in_progress;
601 declination_button->setEnabled(enabled);
602 declination_button->setText(declination_query_in_progress ? tr("Loading...") : tr("Lookup..."));
603 }
604
updateCombinedFactor()605 void GeoreferencingDialog::updateCombinedFactor()
606 {
607 QString text = trUtf8("%1", "scale factor value").arg(QLocale().toString(georef->getCombinedScaleFactor(), 'f', Georeferencing::scaleFactorPrecision()));
608 if (scale_factor_locked)
609 text.append(QString::fromLatin1(" (%1)").arg(tr("locked")));
610 combined_factor_display->setText(text);
611 }
612
updateGrivation()613 void GeoreferencingDialog::updateGrivation()
614 {
615 QString text = trUtf8("%1 °", "degree value").arg(QLocale().toString(georef->getGrivation(), 'f', Georeferencing::declinationPrecision()));
616 if (grivation_locked)
617 text.append(QString::fromLatin1(" (%1)").arg(tr("locked")));
618 grivation_label->setText(text);
619 }
620
crsEdited()621 void GeoreferencingDialog::crsEdited()
622 {
623 Georeferencing georef_copy = *georef;
624
625 auto crs_template = crs_selector->currentCRSTemplate();
626 auto spec = crs_selector->currentCRSSpec();
627
628 auto selected_item_id = crs_selector->currentCustomItem();
629 switch (selected_item_id)
630 {
631 default:
632 qWarning("Unsupported CRS item id");
633 Q_FALLTHROUGH();
634 case Georeferencing::Local:
635 // Local
636 georef_copy.setState(Georeferencing::Local);
637 grivation_locked = true;
638 updateGrivation();
639 scale_factor_locked = true;
640 updateCombinedFactor();
641 break;
642 case -1:
643 // CRS from list
644 Q_ASSERT(crs_template);
645 georef_copy.setProjectedCRS(crs_template->id(), spec, crs_selector->parameters());
646 georef_copy.setState(Georeferencing::Normal); // Allow invalid spec
647 if (keep_geographic_radio->isChecked())
648 georef_copy.setGeographicRefPoint(georef->getGeographicRefPoint(), !grivation_locked, !scale_factor_locked);
649 else
650 georef_copy.setProjectedRefPoint(georef->getProjectedRefPoint(), !grivation_locked, !scale_factor_locked);
651 break;
652 }
653
654 // Apply all changes at once
655 *georef = georef_copy;
656 reset_button->setEnabled(true);
657 }
658
showScaleChanged(bool checked)659 void GeoreferencingDialog::showScaleChanged(bool checked)
660 {
661 Settings::getInstance().setSetting(Settings::MapGeoreferencing_ControlScaleFactor, checked);
662 for (auto scale_widget: scale_widget_list)
663 scale_widget->setVisible(checked);
664 }
665
auxiliaryFactorEdited(double value)666 void GeoreferencingDialog::auxiliaryFactorEdited(double value)
667 {
668 if (scale_factor_locked)
669 {
670 scale_factor_locked = false;
671 updateCombinedFactor();
672 }
673 georef->setAuxiliaryScaleFactor(value);
674 reset_button->setEnabled(true);
675 }
676
selectMapRefPoint()677 void GeoreferencingDialog::selectMapRefPoint()
678 {
679 if (controller)
680 {
681 controller->setOverrideTool(new GeoreferencingTool(this, controller));
682 tool_active = true;
683 hide();
684 }
685 }
686
mapRefChanged()687 void GeoreferencingDialog::mapRefChanged()
688 {
689 MapCoord coord(map_x_edit->value(), -1 * map_y_edit->value());
690 setMapRefPoint(coord);
691 }
692
eastingNorthingEdited()693 void GeoreferencingDialog::eastingNorthingEdited()
694 {
695 const QSignalBlocker block1(keep_geographic_radio), block2(keep_projected_radio);
696 double easting = easting_edit->value();
697 double northing = northing_edit->value();
698 georef->setProjectedRefPoint(QPointF(easting, northing), !grivation_locked, !scale_factor_locked);
699 keep_projected_radio->setChecked(true);
700 reset_button->setEnabled(true);
701 }
702
latLonEdited()703 void GeoreferencingDialog::latLonEdited()
704 {
705 const QSignalBlocker block1(keep_geographic_radio), block2(keep_projected_radio);
706 double latitude = lat_edit->value();
707 double longitude = lon_edit->value();
708 georef->setGeographicRefPoint(LatLon(latitude, longitude), !grivation_locked, !scale_factor_locked);
709 keep_geographic_radio->setChecked(true);
710 reset_button->setEnabled(true);
711 }
712
keepCoordsChanged()713 void GeoreferencingDialog::keepCoordsChanged()
714 {
715 if (keep_geographic_radio->isChecked())
716 {
717 if (grivation_locked)
718 {
719 grivation_locked = false;
720 updateGrivation();
721 georef->updateGrivation();
722 }
723 if (scale_factor_locked)
724 {
725 scale_factor_locked = false;
726 updateCombinedFactor();
727 georef->updateCombinedScaleFactor();
728 }
729 }
730 reset_button->setEnabled(true);
731 }
732
declinationEdited(double value)733 void GeoreferencingDialog::declinationEdited(double value)
734 {
735 if (grivation_locked)
736 {
737 grivation_locked = false;
738 updateGrivation();
739 }
740 georef->setDeclination(value);
741 reset_button->setEnabled(true);
742 }
743
declinationReplyFinished(QNetworkReply * reply)744 void GeoreferencingDialog::declinationReplyFinished(QNetworkReply* reply)
745 {
746 #if defined(QT_NETWORK_LIB)
747 declination_query_in_progress = false;
748 updateDeclinationButton();
749
750 QString error_string;
751 if (reply->error() != QNetworkReply::NoError)
752 {
753 error_string = reply->errorString();
754 }
755 else
756 {
757 QXmlStreamReader xml(reply);
758 while (xml.readNextStartElement())
759 {
760 if (xml.name() == QLatin1String("maggridresult"))
761 {
762 while(xml.readNextStartElement())
763 {
764 if (xml.name() == QLatin1String("result"))
765 {
766 while (xml.readNextStartElement())
767 {
768 if (xml.name() == QLatin1String("declination"))
769 {
770 QString text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
771 bool ok;
772 double declination = text.toDouble(&ok);
773 if (ok)
774 {
775 setValueIfChanged(declination_edit, Georeferencing::roundDeclination(declination));
776 return;
777 }
778 else
779 {
780 error_string = tr("Could not parse data.") + QLatin1Char(' ');
781 }
782 }
783
784 xml.skipCurrentElement(); // child of result
785 }
786 }
787
788 xml.skipCurrentElement(); // child of mapgridresult
789 }
790 }
791 else if (xml.name() == QLatin1String("errors"))
792 {
793 error_string.append(xml.readElementText(QXmlStreamReader::IncludeChildElements) + QLatin1Char(' '));
794 }
795
796 xml.skipCurrentElement(); // child of root
797 }
798
799 if (xml.error() != QXmlStreamReader::NoError)
800 {
801 error_string.append(xml.errorString());
802 }
803 else if (error_string.isEmpty())
804 {
805 error_string = tr("Declination value not found.");
806 }
807 }
808
809 int result = QMessageBox::critical(this, tr("Online declination lookup"),
810 tr("The online declination lookup failed:\n%1").arg(error_string),
811 QMessageBox::Retry | QMessageBox::Close,
812 QMessageBox::Close );
813 if (result == QMessageBox::Retry)
814 requestDeclination(true);
815 #else
816 Q_UNUSED(reply)
817 #endif
818 }
819
820
821
822 // ### GeoreferencingTool ###
823
GeoreferencingTool(GeoreferencingDialog * dialog,MapEditorController * controller,QAction * action)824 GeoreferencingTool::GeoreferencingTool(GeoreferencingDialog* dialog, MapEditorController* controller, QAction* action)
825 : MapEditorTool(controller, Other, action)
826 , dialog(dialog)
827 {
828 // nothing
829 }
830
~GeoreferencingTool()831 GeoreferencingTool::~GeoreferencingTool()
832 {
833 dialog->toolDeleted();
834 }
835
init()836 void GeoreferencingTool::init()
837 {
838 setStatusBarText(tr("<b>Click</b>: Set the reference point. <b>Right click</b>: Cancel."));
839 MapEditorTool::init();
840 }
841
mousePressEvent(QMouseEvent * event,const MapCoordF &,MapWidget *)842 bool GeoreferencingTool::mousePressEvent(QMouseEvent* event, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
843 {
844 bool handled = false;
845 switch (event->button())
846 {
847 case Qt::LeftButton:
848 case Qt::RightButton:
849 handled = true;
850 break;
851 default:
852 ; // nothing
853 }
854
855 return handled;
856 }
857
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget *)858 bool GeoreferencingTool::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* /*widget*/)
859 {
860 bool handled = false;
861 switch (event->button())
862 {
863 case Qt::LeftButton:
864 dialog->setMapRefPoint(MapCoord(map_coord));
865 Q_FALLTHROUGH();
866 case Qt::RightButton:
867 QTimer::singleShot(0, dialog, &QDialog::exec);
868 handled = true;
869 break;
870 default:
871 ; // nothing
872 }
873
874 return handled;
875 }
876
getCursor() const877 const QCursor& GeoreferencingTool::getCursor() const
878 {
879 static auto const cursor = scaledToScreen(QCursor{ QPixmap{ QString::fromLatin1(":/images/cursor-crosshair.png") }, 11, 11 });
880 return cursor;
881 }
882
883
884 } // namespace OpenOrienteering
885