6#include "AlternativeRoutesModel.h"
8#include "GeoDataDocument.h"
9#include "GeoDataExtendedData.h"
10#include "GeoDataFolder.h"
11#include "GeoDataLatLonAltBox.h"
12#include "GeoDataLineString.h"
13#include "GeoDataPlacemark.h"
15#include <QElapsedTimer>
22class Q_DECL_HIDDEN AlternativeRoutesModel::Private
30 bool filter(
const GeoDataDocument *document)
const;
39 static qreal similarity(
const GeoDataDocument *routeA,
const GeoDataDocument *routeB);
44 static qreal
distance(
const GeoDataLineString &wayPoints,
const GeoDataCoordinates &position);
50 static qreal bearing(
const GeoDataCoordinates &one,
const GeoDataCoordinates &two);
56 static qreal
distance(
const GeoDataCoordinates &satellite,
const GeoDataCoordinates &lineA,
const GeoDataCoordinates &lineB);
61 static GeoDataCoordinates coordinates(
const GeoDataCoordinates &
start, qreal distance, qreal bearing);
67 static qreal unidirectionalSimilarity(
const GeoDataDocument *routeA,
const GeoDataDocument *routeB);
72 static bool higherScore(
const GeoDataDocument *one,
const GeoDataDocument *two);
77 static qreal instructionScore(
const GeoDataDocument *document);
79 static const GeoDataLineString *waypoints(
const GeoDataDocument *document);
81 static int nonZero(
const QImage &image);
83 static QPolygonF polygon(
const GeoDataLineString &lineString, qreal x, qreal y, qreal sx, qreal sy);
97AlternativeRoutesModel::Private::Private()
103int AlternativeRoutesModel::Private::nonZero(
const QImage &image)
105 QRgb
const black = qRgb(0, 0, 0);
107 for (
int y = 0; y < image.
height(); ++y) {
108 QRgb *destLine = (QRgb *)image.
scanLine(y);
109 for (
int x = 0; x < image.
width(); ++x) {
110 count += destLine[x] ==
black ? 0 : 1;
116QPolygonF AlternativeRoutesModel::Private::polygon(
const GeoDataLineString &lineString, qreal x, qreal y, qreal sx, qreal sy)
119 for (
int i = 0; i < lineString.size(); ++i) {
120 poly <<
QPointF(qAbs((lineString)[i].longitude() - x) * sx, qAbs((lineString)[i].latitude() - y) * sy);
125bool AlternativeRoutesModel::Private::filter(
const GeoDataDocument *document)
const
127 for (
int i = 0; i < m_routes.size(); ++i) {
128 qreal similarity = Private::similarity(document, m_routes.at(i));
129 if (similarity > 0.8) {
137qreal AlternativeRoutesModel::Private::similarity(
const GeoDataDocument *routeA,
const GeoDataDocument *routeB)
139 return qMax<qreal>(unidirectionalSimilarity(routeA, routeB), unidirectionalSimilarity(routeB, routeA));
142qreal AlternativeRoutesModel::Private::distance(
const GeoDataLineString &wayPoints,
const GeoDataCoordinates &position)
144 Q_ASSERT(!wayPoints.isEmpty());
145 qreal minDistance = 0;
146 for (
int i = 1; i < wayPoints.size(); ++i) {
147 qreal dist =
distance(position, wayPoints.at(i - 1), wayPoints.at(i));
148 if (minDistance <= 0 || dist < minDistance) {
156qreal AlternativeRoutesModel::Private::bearing(
const GeoDataCoordinates &one,
const GeoDataCoordinates &two)
158 qreal delta = two.longitude() - one.longitude();
159 qreal lat1 = one.latitude();
160 qreal lat2 = two.latitude();
161 return fmod(atan2(sin(delta) * cos(lat2), cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(delta)), 2 * M_PI);
164GeoDataCoordinates AlternativeRoutesModel::Private::coordinates(
const GeoDataCoordinates &
start, qreal distance, qreal bearing)
166 qreal lat1 =
start.latitude();
167 qreal lon1 =
start.longitude();
168 qreal lat2 = asin(sin(lat1) * cos(distance) + cos(lat1) * sin(distance) * cos(bearing));
169 qreal lon2 = lon1 + atan2(sin(bearing) * sin(distance) * cos(lat1), cos(distance) - sin(lat1) * sin(lat2));
173qreal AlternativeRoutesModel::Private::distance(
const GeoDataCoordinates &satellite,
const GeoDataCoordinates &lineA,
const GeoDataCoordinates &lineB)
175 const qreal dist = lineA.sphericalDistanceTo(satellite);
176 qreal bearA = bearing(lineA, satellite);
177 qreal bearB = bearing(lineA, lineB);
178 qreal result = asin(sin(dist) * sin(bearB - bearA));
179 Q_ASSERT(qMax<qreal>(satellite.sphericalDistanceTo(lineA), satellite.sphericalDistanceTo(lineB)) >= qAbs<qreal>(result));
181 result = acos(cos(dist) / cos(result));
183 const qreal
final = qMin<qreal>(satellite.sphericalDistanceTo(lineA), satellite.sphericalDistanceTo(lineB));
184 if (result >= 0 && result <= lineA.sphericalDistanceTo(lineB)) {
185 GeoDataCoordinates nearest = coordinates(lineA, result, bearB);
186 return qMin<qreal>(
final, satellite.sphericalDistanceTo(nearest));
192qreal AlternativeRoutesModel::Private::unidirectionalSimilarity(
const GeoDataDocument *routeA,
const GeoDataDocument *routeB)
194 const GeoDataLineString *waypointsA = waypoints(routeA);
195 const GeoDataLineString *waypointsB = waypoints(routeB);
196 if (!waypointsA || !waypointsB) {
201 image.
fill(qRgb(0, 0, 0));
202 GeoDataLatLonBox box = GeoDataLatLonBox::fromLineString(*waypointsA);
203 box = box.united(GeoDataLatLonBox::fromLineString(*waypointsB));
204 if (!box.width() || !box.height()) {
208 qreal
const sw = image.
width() / box.width();
209 qreal
const sh = image.
height() / box.height();
214 painter.drawPoints(Private::polygon(*waypointsA, box.west(), box.north(), sw, sh));
215 int const countA = Private::nonZero(image);
217 painter.drawPoints(Private::polygon(*waypointsB, box.west(), box.north(), sw, sh));
218 int const countB = Private::nonZero(image);
219 Q_ASSERT(countA <= countB);
220 return countB ? 1.0 - qreal(countB - countA) / countB : 0;
223bool AlternativeRoutesModel::Private::higherScore(
const GeoDataDocument *one,
const GeoDataDocument *two)
225 qreal instructionScoreA = instructionScore(one);
226 qreal instructionScoreB = instructionScore(two);
227 if (instructionScoreA != instructionScoreB) {
228 return instructionScoreA > instructionScoreB;
231 qreal lengthA = waypoints(one)->length(EARTH_RADIUS);
232 qreal lengthB = waypoints(two)->length(EARTH_RADIUS);
234 return lengthA < lengthB;
237qreal AlternativeRoutesModel::Private::instructionScore(
const GeoDataDocument *document)
239 bool hasInstructions =
false;
243 for (
const GeoDataFolder *folder : std::as_const(folders)) {
244 for (
const GeoDataPlacemark *placemark : folder->placemarkList()) {
245 if (!blacklist.
contains(placemark->name())) {
246 hasInstructions =
true;
252 for (
const GeoDataPlacemark *placemark : document->placemarkList()) {
253 if (!blacklist.
contains(placemark->name())) {
254 hasInstructions =
true;
256 if (placemark->extendedData().contains(QStringLiteral(
"turnType"))) {
262 return hasInstructions ? 0.5 : 0.0;
265const GeoDataLineString *AlternativeRoutesModel::Private::waypoints(
const GeoDataDocument *document)
268 for (
const GeoDataFolder *folder : std::as_const(folders)) {
269 for (
const GeoDataPlacemark *placemark : folder->placemarkList()) {
270 const GeoDataGeometry *geometry = placemark->geometry();
271 const auto lineString =
dynamic_cast<const GeoDataLineString *
>(geometry);
278 for (
const GeoDataPlacemark *placemark : document->placemarkList()) {
279 const GeoDataGeometry *geometry = placemark->geometry();
280 const auto lineString =
dynamic_cast<const GeoDataLineString *
>(geometry);
289AlternativeRoutesModel::AlternativeRoutesModel(
QObject *parent)
296AlternativeRoutesModel::~AlternativeRoutesModel()
302int AlternativeRoutesModel::rowCount(
const QModelIndex &)
const
304 return d->m_routes.size();
322 return d->m_routes.at(index.
row())->name();
328const GeoDataDocument *AlternativeRoutesModel::route(
int index)
const
330 if (index >= 0 && index < d->m_routes.size()) {
331 return d->m_routes.at(index);
337void AlternativeRoutesModel::newRequest(RouteRequest *)
339 d->m_responseTime.start();
340 d->m_currentIndex = -1;
344void AlternativeRoutesModel::addRestrainedRoutes()
346 Q_ASSERT(d->m_routes.isEmpty());
347 std::sort(d->m_restrainedRoutes.begin(), d->m_restrainedRoutes.end(), Private::higherScore);
349 for (GeoDataDocument *route : std::as_const(d->m_restrainedRoutes)) {
350 if (!d->filter(route)) {
351 int affected = d->m_routes.size();
352 beginInsertRows(
QModelIndex(), affected, affected);
354 d->m_routes.push_back(route);
359 d->m_restrainedRoutes.clear();
360 Q_ASSERT(!d->m_routes.isEmpty());
364void AlternativeRoutesModel::addRoute(GeoDataDocument *document, WritePolicy policy)
366 if (policy != Instant) {
367 if (d->m_routes.isEmpty()) {
368 d->m_restrainedRoutes.push_back(document);
370 if (d->m_restrainedRoutes.isEmpty()) {
372 const int responseTime = d->m_responseTime.elapsed();
373 const int timeout = qMin<int>(500, qMax<int>(50, responseTime * 2));
380 for (
int i = 0; i < d->m_routes.size(); ++i) {
381 qreal similarity = Private::similarity(document, d->m_routes.at(i));
382 if (similarity > 0.8) {
383 if (Private::higherScore(document, d->m_routes.at(i))) {
384 d->m_routes[i] = document;
386 Q_EMIT dataChanged(changed, changed);
394 const int affected = d->m_routes.size();
395 beginInsertRows(
QModelIndex(), affected, affected);
396 d->m_routes.push_back(document);
400const GeoDataLineString *AlternativeRoutesModel::waypoints(
const GeoDataDocument *document)
402 return Private::waypoints(document);
405void AlternativeRoutesModel::setCurrentRoute(
int index)
407 if (index >= 0 && index < rowCount() && d->m_currentIndex != index) {
408 d->m_currentIndex = index;
409 Q_EMIT currentRouteChanged(currentRoute());
410 Q_EMIT currentRouteChanged(d->m_currentIndex);
414const GeoDataDocument *AlternativeRoutesModel::currentRoute()
const
416 const GeoDataDocument *result =
nullptr;
417 if (d->m_currentIndex >= 0 && d->m_currentIndex < rowCount()) {
418 result = d->m_routes[d->m_currentIndex];
424void AlternativeRoutesModel::clear()
428 d->m_currentIndex = -1;
436#include "moc_AlternativeRoutesModel.cpp"
Q_SCRIPTABLE Q_NOREPLY void start()
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
void fill(Qt::GlobalColor color)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)