1 /**********************************************************************************************
2 Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 **********************************************************************************************/
18
19 #include "canvas/CCanvas.h"
20 #include "CMainWindow.h"
21 #include "gis/CGisWorkspace.h"
22 #include "gis/proj_x.h"
23 #include "gis/rte/CGisItemRte.h"
24 #include "gis/rte/router/CRouterRoutino.h"
25 #include "gis/rte/router/routino/CRouterRoutinoPathSetup.h"
26 #include "helpers/CProgressDialog.h"
27 #include "helpers/CSettings.h"
28 #include "setup/IAppSetup.h"
29
30 #include <QtWidgets>
31 #include <routino.h>
32
33 QPointer<CProgressDialog> CRouterRoutino::progress;
34
ProgressFunc(double complete)35 int ProgressFunc(double complete)
36 {
37 if(CRouterRoutino::progress.isNull())
38 {
39 return true;
40 }
41
42 CRouterRoutino::progress->setValue(complete * 100);
43
44 return !CRouterRoutino::progress->wasCanceled();
45 }
46
47 CRouterRoutino* CRouterRoutino::pSelf = nullptr;
48
CRouterRoutino(QWidget * parent)49 CRouterRoutino::CRouterRoutino(QWidget* parent)
50 : IRouter(true, parent)
51 {
52 pSelf = this;
53 setupUi(this);
54
55 connect(labelHelp, &QLabel::linkActivated, &CMainWindow::self(), static_cast<void (CMainWindow::*)(const QString&)>(&CMainWindow::slotLinkActivated));
56
57 if(Routino_CheckAPIVersion() != ROUTINO_ERROR_NONE)
58 {
59 QMessageBox::warning(this, tr("Warning..."), tr("Found Routino with a wrong version. Expected %1 found %2").arg(ROUTINO_API_VERSION).arg(Routino_APIVersion), QMessageBox::Ok);
60 return;
61 }
62
63 comboMode->addItem(tr("Shortest"));
64 comboMode->addItem(tr("Quickest"));
65
66
67 int res = 0;
68 IAppSetup* setup = IAppSetup::getPlatformInstance();
69 res = Routino_ParseXMLTranslations(setup->routinoPath("translations.xml").toUtf8());
70 if(res)
71 {
72 QMessageBox::critical(this, "Routino...", xlateRoutinoError(Routino_errno), QMessageBox::Abort);
73 return;
74 }
75
76 comboProfile->addItem(tr("Foot"), "foot");
77 comboProfile->addItem(tr("Horse"), "horse");
78 comboProfile->addItem(tr("Wheelchair"), "wheelchair");
79 comboProfile->addItem(tr("Bicycle"), "bicycle");
80 comboProfile->addItem(tr("Moped"), "moped");
81 comboProfile->addItem(tr("Motorcycle"), "motorcycle");
82 comboProfile->addItem(tr("Motorcar"), "motorcar");
83 comboProfile->addItem(tr("Goods"), "goods");
84
85 comboLanguage->addItem(tr("English"), "en");
86 comboLanguage->addItem(tr("German"), "de");
87 comboLanguage->addItem(tr("French"), "fr");
88 comboLanguage->addItem(tr("Hungarian"), "hu");
89 comboLanguage->addItem(tr("Dutch"), "nl");
90 comboLanguage->addItem(tr("Russian"), "ru");
91 comboLanguage->addItem(tr("Polish"), "pl");
92 comboLanguage->addItem(tr("Czech"), "cs");
93 comboLanguage->addItem(tr("Spanish"), "es");
94
95 connect(toolSetupPaths, &QToolButton::clicked, this, &CRouterRoutino::slotSetupPaths);
96
97 SETTINGS;
98 dbPaths = cfg.value("Route/routino/paths", dbPaths).toStringList();
99 buildDatabaseList();
100
101 comboProfile->setCurrentIndex(cfg.value("Route/routino/profile", 0).toInt());
102 comboLanguage->setCurrentIndex(cfg.value("Route/routino/language", 0).toInt());
103 comboMode->setCurrentIndex(cfg.value("Route/routino/mode", 0).toInt());
104 comboDatabase->setCurrentIndex(cfg.value("Route/routino/database", 0).toInt());
105
106 updateHelpText();
107 }
108
~CRouterRoutino()109 CRouterRoutino::~CRouterRoutino()
110 {
111 SETTINGS;
112 cfg.setValue("Route/routino/paths", dbPaths);
113 cfg.setValue("Route/routino/profile", comboProfile->currentIndex());
114 cfg.setValue("Route/routino/language", comboLanguage->currentIndex());
115 cfg.setValue("Route/routino/mode", comboMode->currentIndex());
116 cfg.setValue("Route/routino/database", comboDatabase->currentIndex());
117
118 freeDatabaseList();
119 Routino_FreeXMLProfiles();
120 Routino_FreeXMLTranslations();
121 }
122
xlateRoutinoError(int err)123 QString CRouterRoutino::xlateRoutinoError(int err)
124 {
125 switch(err)
126 {
127 case ROUTINO_ERROR_NO_DATABASE:
128 return tr("A function was called without the database variable set.");
129
130 case ROUTINO_ERROR_NO_PROFILE:
131 return tr("A function was called without the profile variable set.");
132
133 case ROUTINO_ERROR_NO_TRANSLATION:
134 return tr("A function was called without the translation variable set.");
135
136 case ROUTINO_ERROR_NO_DATABASE_FILES:
137 return tr("The specified database to load did not exist.");
138
139 case ROUTINO_ERROR_BAD_DATABASE_FILES:
140 return tr("The specified database could not be loaded.");
141
142 case ROUTINO_ERROR_NO_PROFILES_XML:
143 return tr("The specified profiles XML file did not exist.");
144
145 case ROUTINO_ERROR_BAD_PROFILES_XML:
146 return tr("The specified profiles XML file could not be loaded.");
147
148 case ROUTINO_ERROR_NO_TRANSLATIONS_XML:
149 return tr("The specified translations XML file did not exist.");
150
151 case ROUTINO_ERROR_BAD_TRANSLATIONS_XML:
152 return tr("The specified translations XML file could not be loaded.");
153
154 case ROUTINO_ERROR_NO_SUCH_PROFILE:
155 return tr("The requested profile name does not exist in the loaded XML file.");
156
157 case ROUTINO_ERROR_NO_SUCH_TRANSLATION:
158 return tr("The requested translation language does not exist in the loaded XML file.");
159
160 case ROUTINO_ERROR_NO_NEARBY_HIGHWAY:
161 return tr("In the routing database there is no highway near the coordinates to place a waypoint.");
162
163 case ROUTINO_ERROR_PROFILE_DATABASE_ERR:
164 return tr("The profile and database do not work together.");
165
166 case ROUTINO_ERROR_NOTVALID_PROFILE:
167 return tr("The profile being used has not been validated.");
168
169 case ROUTINO_ERROR_BAD_USER_PROFILE:
170 return tr("The user specified profile contained invalid data.");
171
172 case ROUTINO_ERROR_BAD_OPTIONS:
173 return tr("The routing options specified are not consistent with each other.");
174
175 case ROUTINO_ERROR_WRONG_API_VERSION:
176 return tr("There is a mismatch between the library and caller API version.");
177
178 case ROUTINO_ERROR_PROGRESS_ABORTED:
179 return tr("Route calculation was aborted by user.");
180 }
181
182 if(ROUTINO_ERROR_NO_ROUTE_1 <= err)
183 {
184 int n = err - 1000;
185 return tr("A route could not be found to waypoint %1.").arg(n);
186 }
187
188 return tr("Unknown error: %1").arg(err);
189 }
190
hasFastRouting()191 bool CRouterRoutino::hasFastRouting()
192 {
193 return IRouter::hasFastRouting() && (comboDatabase->count() != 0);
194 }
195
getOptions()196 QString CRouterRoutino::getOptions()
197 {
198 QString str;
199
200 str = tr("profile \"%1\"").arg(comboProfile->currentText());
201 str += tr(", mode \"%1\"").arg(comboMode->currentText());
202 return str;
203 }
204
setupPath(const QString & path)205 void CRouterRoutino::setupPath(const QString& path)
206 {
207 if(dbPaths.contains(path))
208 {
209 return;
210 }
211
212 dbPaths << path;
213 buildDatabaseList();
214 updateHelpText();
215 }
216
slotSetupPaths()217 void CRouterRoutino::slotSetupPaths()
218 {
219 CRouterRoutinoPathSetup dlg(dbPaths);
220 dlg.exec();
221
222 buildDatabaseList();
223 updateHelpText();
224 }
225
buildDatabaseList()226 void CRouterRoutino::buildDatabaseList()
227 {
228 QRegExp re("(.*)-segments.mem");
229 freeDatabaseList();
230
231 // initialise
232 currentProfilesPath = "";
233
234 IAppSetup* setup = IAppSetup::getPlatformInstance();
235
236 for(const QString& path : qAsConst(dbPaths))
237 {
238 QDir dir(path);
239 const QStringList& filenames = dir.entryList(QStringList("*segments.mem"), QDir::Files | QDir::Readable, QDir::Name);
240 for(const QString& filename : filenames)
241 {
242 QString prefix;
243 if(re.exactMatch(filename))
244 {
245 prefix = re.cap(1);
246 }
247 else
248 {
249 continue;
250 }
251
252 // qDebug() << "buildDatabase Prefix" << prefix;
253
254 #ifdef Q_OS_WIN
255 Routino_Database* data = Routino_LoadDatabase(dir.absolutePath().toLocal8Bit(), prefix.toLocal8Bit());
256 #else
257 Routino_Database* data = Routino_LoadDatabase(dir.absolutePath().toUtf8(), prefix.toUtf8());
258 #endif
259 qDebug() << "Loaded Routino DB" << dir.absolutePath().toUtf8().data() << " " << prefix.toUtf8().data();
260
261 if(data == nullptr)
262 {
263 QMessageBox::critical(this, "Routino ...", xlateRoutinoError(Routino_errno), QMessageBox::Abort);
264 continue;
265 }
266 /* determine the profile to use for each database*/
267 QVariantMap dmap;
268 dmap["db"] = QVariant ((qulonglong)data);
269
270 /* check possible profiles.xml locations and use the first available */
271 int pError = 0;
272 dmap["profilesPath"] = "";
273 QStringList profilesPaths = {
274 dir.filePath(prefix + "-profiles.xml"),
275 dir.filePath("profiles.xml"),
276 setup->routinoPath("profiles.xml").toUtf8()
277 };
278
279 for(const QString& profilePath : profilesPaths)
280 {
281 QFileInfo pinfo(profilePath);
282 if( pinfo.isReadable())
283 {
284 dmap["profilesPath"] = pinfo.filePath();
285 break;
286 }
287 }
288 if( dmap["profilesPath"].toString().isEmpty() )
289 {
290 QMessageBox::critical(this, "Routino...", tr("Could not find a profiles XML file in expected folders. Routino Routing will not function"), QMessageBox::Ok);
291 pError = 1;
292 }
293 else
294 {
295 /* ensure we always reload */
296 currentProfilesPath = "";
297 /* check if profile will load - will abort if not good */
298 pError = loadProfiles(dmap["profilesPath"].toString());
299 }
300 qDebug() << "Profile ... Using \n" << dmap["profilesPath"].toString();
301
302 if( pError == 0 )
303 {
304 comboDatabase->addItem(prefix.replace("_", " "), dmap);
305 }
306 else
307 {
308 const QString& msg = tr(
309 "%1\n"
310 "Error in '%2'\n"
311 "This needs to be fixed\n"
312 "The associated database '%3' is ignored"
313 ).arg(xlateRoutinoError(Routino_errno), dmap["profilesPath"].toString(), prefix);
314
315 QMessageBox::warning(this, "Routino...", msg, QMessageBox::Ok);
316 }
317 }
318 }
319 currentProfilesPath = "";
320 }
321
freeDatabaseList()322 void CRouterRoutino::freeDatabaseList()
323 {
324 for(int i = 0; i < comboDatabase->count(); i++)
325 {
326 QVariantMap map = comboDatabase->itemData(i, Qt::UserRole).toMap();
327 Routino_Database* data = (Routino_Database*)(map["db"].toULongLong());
328 Routino_UnloadDatabase(data);
329 }
330 comboDatabase->clear();
331 }
332
loadProfiles(const QString & profilesPath)333 int CRouterRoutino::loadProfiles(const QString& profilesPath)
334 {
335 int res = 0;
336 if( currentProfilesPath != profilesPath)
337 {
338 currentProfilesPath = profilesPath;
339 res = Routino_ParseXMLProfiles(profilesPath.toUtf8());
340 }
341 return res;
342 }
343
updateHelpText()344 void CRouterRoutino::updateHelpText()
345 {
346 bool haveDB = (comboDatabase->count() != 0);
347
348 frameHelp->setVisible(!haveDB);
349 comboDatabase->setEnabled(haveDB);
350 }
351
calcRoute(const IGisItem::key_t & key)352 void CRouterRoutino::calcRoute(const IGisItem::key_t& key)
353 {
354 if(!mutex.tryLock())
355 {
356 return;
357 }
358
359 try
360 {
361 QTime time;
362 time.start();
363
364 CGisItemRte* rte = dynamic_cast<CGisItemRte*>(CGisWorkspace::self().getItemByKey(key));
365 if(nullptr == rte)
366 {
367 throw QString();
368 }
369
370 QVariantMap map = comboDatabase->currentData(Qt::UserRole).toMap();
371 Routino_Database* data = (Routino_Database*)(map["db"].toULongLong());
372 if(nullptr == data)
373 {
374 throw QString();
375 }
376
377 loadProfiles(map["profilesPath"].toString());
378
379 rte->reset();
380
381 QString strProfile = comboProfile->currentData(Qt::UserRole).toString();
382 QString strLanguage = comboLanguage->currentData(Qt::UserRole).toString();
383
384 Routino_Profile* profile = Routino_GetProfile(strProfile.toUtf8());
385 if( profile == NULL )
386 {
387 throw tr("Required profile '%1' is not in the current profiles file.").arg(strProfile);
388 }
389 Routino_Translation* translation = Routino_GetTranslation(strLanguage.toUtf8());
390
391 int res = Routino_ValidateProfile(data, profile);
392 if(res != 0)
393 {
394 throw xlateRoutinoError(Routino_errno);
395 }
396
397 int options = ROUTINO_ROUTE_LIST_HTML_ALL;
398 if(comboMode->currentIndex() == 0)
399 {
400 options |= ROUTINO_ROUTE_SHORTEST;
401 }
402 if(comboMode->currentIndex() == 1)
403 {
404 options |= ROUTINO_ROUTE_QUICKEST;
405 }
406
407 SGisLine line;
408 rte->getPolylineFromData(line);
409
410 int idx = 0;
411 QVector<Routino_Waypoint*> waypoints(line.size(), nullptr);
412 for(const IGisLine::point_t& pt : qAsConst(line))
413 {
414 waypoints[idx] = Routino_FindWaypoint(data, profile, pt.coord.y() * RAD_TO_DEG, pt.coord.x() * RAD_TO_DEG);
415 if(waypoints[idx] == nullptr)
416 {
417 throw xlateRoutinoError(Routino_errno);
418 }
419 idx++;
420 }
421
422 progress = new CProgressDialog(tr("Calculate route with %1").arg(getOptions()), 0, NOINT, this);
423
424 Routino_Output* route = Routino_CalculateRoute(data, profile, translation, waypoints.data(), waypoints.size(), options, ProgressFunc);
425
426 delete progress;
427
428 if(nullptr != route)
429 {
430 rte->setResult(route, getOptions() + tr("<br/>Calculation time: %1s").arg(time.elapsed() / 1000.0, 0, 'f', 2));
431 Routino_DeleteRoute(route);
432 }
433 else
434 {
435 if(Routino_errno != ROUTINO_ERROR_PROGRESS_ABORTED)
436 {
437 throw xlateRoutinoError(Routino_errno);
438 }
439 }
440 }
441 catch(const QString& msg)
442 {
443 if(!msg.isEmpty())
444 {
445 QMessageBox::critical(this, "Routino...", msg, QMessageBox::Abort);
446 }
447 }
448
449 mutex.unlock();
450
451 CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
452 }
453
454
calcRoute(const QPointF & p1,const QPointF & p2,QPolygonF & coords,qreal * costs=nullptr)455 int CRouterRoutino::calcRoute(const QPointF& p1, const QPointF& p2, QPolygonF& coords, qreal* costs = nullptr)
456 {
457 if(!mutex.tryLock())
458 {
459 return -1;
460 }
461
462 try
463 {
464 QVariantMap map = comboDatabase->currentData(Qt::UserRole).toMap();
465 Routino_Database* data = (Routino_Database*)(map["db"].toULongLong());
466 if(nullptr == data)
467 {
468 throw QString();
469 }
470
471 loadProfiles(map["profilesPath"].toString());
472
473 QString strProfile = comboProfile->currentData(Qt::UserRole).toString();
474 QString strLanguage = comboLanguage->currentData(Qt::UserRole).toString();
475
476 Routino_Profile* profile = Routino_GetProfile(strProfile.toUtf8());
477 if( profile == NULL )
478 {
479 throw tr("Required profile '%1' is not in the current profiles file.").arg(strProfile);
480 }
481 Routino_Translation* translation = Routino_GetTranslation(strLanguage.toUtf8());
482
483
484 int res = Routino_ValidateProfile(data, profile);
485 if(res != 0)
486 {
487 throw xlateRoutinoError(Routino_errno);
488 }
489
490 int options = ROUTINO_ROUTE_LIST_HTML_ALL;
491 if(comboMode->currentIndex() == 0)
492 {
493 options |= ROUTINO_ROUTE_SHORTEST;
494 }
495 if(comboMode->currentIndex() == 1)
496 {
497 options |= ROUTINO_ROUTE_QUICKEST;
498 }
499
500 Routino_Waypoint* waypoints[2] = {0};
501 waypoints[0] = Routino_FindWaypoint(data, profile, p1.y() * RAD_TO_DEG, p1.x() * RAD_TO_DEG);
502 if(waypoints[0] == nullptr)
503 {
504 throw xlateRoutinoError(Routino_errno);
505 }
506
507 waypoints[1] = Routino_FindWaypoint(data, profile, p2.y() * RAD_TO_DEG, p2.x() * RAD_TO_DEG);
508 if(waypoints[1] == nullptr)
509 {
510 throw xlateRoutinoError(Routino_errno);
511 }
512
513 progress = new CProgressDialog(tr("Calculate route with %1").arg(getOptions()), 0, NOINT, this);
514
515 Routino_Output* route = Routino_CalculateRoute(data, profile, translation, waypoints, 2, options, ProgressFunc);
516
517 delete progress;
518
519 if(route != nullptr)
520 {
521 Routino_Output* next = route;
522 while(next)
523 {
524 if(next->type != ROUTINO_POINT_WAYPOINT)
525 {
526 coords << QPointF(next->lon, next->lat);
527 }
528 if(costs != nullptr)
529 {
530 if(comboMode->currentIndex() == 1)
531 {
532 // ROUTINO_ROUTE_QUICKEST
533 // This works, since CRouteOptimization adapts it's weights according to the data it gets
534 *costs = next->time;
535 }
536 else
537 {
538 // ROUTINO_ROUTE_SHORTEST
539 *costs = next->dist;
540 }
541 }
542 next = next->next;
543 }
544 Routino_DeleteRoute(route);
545 }
546 else
547 {
548 if(Routino_errno != ROUTINO_ERROR_PROGRESS_ABORTED)
549 {
550 throw xlateRoutinoError(Routino_errno);
551 }
552 else
553 {
554 throw QString();
555 }
556 }
557 }
558 catch(const QString& msg)
559 {
560 coords.clear();
561
562 if(!msg.isEmpty())
563 {
564 mutex.unlock();
565 throw msg;
566 }
567 }
568
569 mutex.unlock();
570 return coords.size();
571 }
572