KItinerary

locationutil.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "locationutil.h"
8 
9 #include <KItinerary/BoatTrip>
10 #include <KItinerary/BusTrip>
11 #include <KItinerary/Event>
12 #include <KItinerary/Flight>
13 #include <KItinerary/Place>
14 #include <KItinerary/Reservation>
15 #include <KItinerary/TrainTrip>
16 #include <KItinerary/Visit>
17 
18 #include <QDebug>
19 
20 #include <cmath>
21 
22 using namespace KItinerary;
23 
25 {
26  if (JsonLd::isA<RentalCarReservation>(res)) {
27  const auto pickup = departureLocation(res);
28  const auto dropoff = arrivalLocation(res);
29  if (dropoff.value<Place>().name().isEmpty()) {
30  return false;
31  }
32  return !isSameLocation(pickup, dropoff);
33  }
34  return JsonLd::isA<FlightReservation>(res) || JsonLd::isA<TrainReservation>(res) || JsonLd::isA<BusReservation>(res) || JsonLd::isA<TaxiReservation>(res) || JsonLd::isA<BoatReservation>(res);
35 }
36 
38 {
39  if (JsonLd::isA<FlightReservation>(res)) {
40  return res.value<FlightReservation>().reservationFor().value<Flight>().arrivalAirport();
41  }
42  if (JsonLd::isA<TrainReservation>(res)) {
43  return res.value<TrainReservation>().reservationFor().value<TrainTrip>().arrivalStation();
44  }
45  if (JsonLd::isA<BusReservation>(res)) {
46  return res.value<BusReservation>().reservationFor().value<BusTrip>().arrivalBusStop();
47  }
48  if (JsonLd::isA<RentalCarReservation>(res)) {
49  return res.value<RentalCarReservation>().dropoffLocation();
50  }
51  if (JsonLd::isA<BoatReservation>(res)) {
52  return res.value<BoatReservation>().reservationFor().value<BoatTrip>().arrivalBoatTerminal();
53  }
54  return {};
55 }
56 
58 {
59  if (JsonLd::isA<FlightReservation>(res)) {
60  return res.value<FlightReservation>().reservationFor().value<Flight>().departureAirport();
61  }
62  if (JsonLd::isA<TrainReservation>(res)) {
63  return res.value<TrainReservation>().reservationFor().value<TrainTrip>().departureStation();
64  }
65  if (JsonLd::isA<BusReservation>(res)) {
66  return res.value<BusReservation>().reservationFor().value<BusTrip>().departureBusStop();
67  }
68  if (JsonLd::isA<RentalCarReservation>(res)) {
69  return res.value<RentalCarReservation>().pickupLocation();
70  }
71  if (JsonLd::isA<TaxiReservation>(res)) {
72  return res.value<TaxiReservation>().pickupLocation();
73  }
74  if (JsonLd::isA<BoatReservation>(res)) {
75  return res.value<BoatReservation>().reservationFor().value<BoatTrip>().departureBoatTerminal();
76  }
77  return {};
78 }
79 
81 {
82  if (JsonLd::isA<LodgingReservation>(res)) {
83  return res.value<LodgingReservation>().reservationFor();
84  }
85  if (JsonLd::isA<FoodEstablishmentReservation>(res)) {
86  return res.value<FoodEstablishmentReservation>().reservationFor();
87  }
88  if (JsonLd::isA<TouristAttractionVisit>(res)) {
89  return res.value<TouristAttractionVisit>().touristAttraction();
90  }
91  if (JsonLd::isA<EventReservation>(res)) {
92  return res.value<EventReservation>().reservationFor().value<Event>().location();
93  }
94  if (JsonLd::isA<RentalCarReservation>(res)) {
95  return res.value<RentalCarReservation>().pickupLocation();
96  }
97 
98  return {};
99 }
100 
102 {
103  if (JsonLd::canConvert<Place>(location)) {
104  return JsonLd::convert<Place>(location).geo();
105  }
106  if (JsonLd::canConvert<Organization>(location)) {
107  return JsonLd::convert<Organization>(location).geo();
108  }
109 
110  return {};
111 }
112 
114 {
115  if (JsonLd::canConvert<Place>(location)) {
116  return JsonLd::convert<Place>(location).address();
117  }
118  if (JsonLd::canConvert<Organization>(location)) {
119  return JsonLd::convert<Organization>(location).address();
120  }
121 
122  return {};
123 }
124 
126 {
127  if (JsonLd::isA<Airport>(location)) {
128  const auto airport = location.value<Airport>();
129  return airport.name().isEmpty() ? airport.iataCode() : airport.name();
130  }
131  if (JsonLd::canConvert<Place>(location)) {
132  return JsonLd::convert<Place>(location).name();
133  }
134  if (JsonLd::canConvert<Organization>(location)) {
135  return JsonLd::convert<Organization>(location).name();
136  }
137 
138  return {};
139 }
140 
141 int LocationUtil::distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
142 {
143  return distance(coord1.latitude(), coord1.longitude(), coord2.latitude(), coord2.longitude());
144 }
145 
146 // see https://en.wikipedia.org/wiki/Haversine_formula
147 int LocationUtil::distance(float lat1, float lon1, float lat2, float lon2)
148 {
149  const auto degToRad = M_PI / 180.0;
150  const auto earthRadius = 6371000.0; // in meters
151 
152  const auto d_lat = (lat1 - lat2) * degToRad;
153  const auto d_lon = (lon1 - lon2) * degToRad;
154 
155  const auto a = pow(sin(d_lat / 2.0), 2) + cos(lat1 * degToRad) * cos(lat2 * degToRad) * pow(sin(d_lon / 2.0), 2);
156  return 2.0 * earthRadius * atan2(sqrt(a), sqrt(1.0 - a));
157 }
158 
159 // if the character has a canonical decomposition use that and skip the combining diacritic markers following it
160 // see https://en.wikipedia.org/wiki/Unicode_equivalence
161 // see https://en.wikipedia.org/wiki/Combining_character
162 static QString stripDiacritics(const QString &s)
163 {
164  QString res;
165  res.reserve(s.size());
166  for (const auto &c : s) {
167  if (c.decompositionTag() == QChar::Canonical) {
168  res.push_back(c.decomposition().at(0));
169  } else {
170  res.push_back(c);
171  }
172  }
173  return res;
174 }
175 
176 // keep this ordered (see https://en.wikipedia.org/wiki/List_of_Unicode_characters)
177 struct {
178  ushort key;
179  const char* replacement;
180 } static const transliteration_map[] = {
181  { u'ä', "ae" },
182  { u'ö', "oe" },
183  { u'ø', "oe" },
184  { u'ü', "ue" }
185 };
186 
187 static QString applyTransliterations(const QString &s)
188 {
189  QString res;
190  res.reserve(s.size());
191 
192  for (const auto c : s) {
193  const auto it = std::lower_bound(std::begin(transliteration_map), std::end(transliteration_map), c, [](const auto &lhs, const auto rhs) {
194  return QChar(lhs.key) < rhs;
195  });
196  if (it != std::end(transliteration_map) && QChar((*it).key) == c) {
197  res += QString::fromUtf8((*it).replacement);
198  continue;
199  }
200 
201  if (c.decompositionTag() == QChar::Canonical) { // see above
202  res += c.decomposition().at(0);
203  } else {
204  res += c;
205  }
206  }
207 
208  return res;
209 }
210 
211 static bool isSameLocationName(const QString &lhs, const QString &rhs, LocationUtil::Accuracy accuracy)
212 {
213  Q_UNUSED(accuracy) // TODO for city level we can strip station or airport suffixes for example
214 
215  if (lhs.isEmpty() || rhs.isEmpty()) {
216  return false;
217  }
218 
219  // actually equal
220  if (lhs.compare(rhs, Qt::CaseInsensitive) == 0) {
221  return true;
222  }
223 
224  // check if any of the unicode normalization approaches helps
225  const auto lhsNormalized = stripDiacritics(lhs);
226  const auto rhsNormalized = stripDiacritics(rhs);
227  const auto lhsTransliterated = applyTransliterations(lhs);
228  const auto rhsTransliterated = applyTransliterations(rhs);
229  return lhsNormalized.compare(rhsNormalized, Qt::CaseInsensitive) == 0 || lhsNormalized.compare(rhsTransliterated, Qt::CaseInsensitive) == 0
230  || lhsTransliterated.compare(rhsNormalized, Qt::CaseInsensitive) == 0 || lhsTransliterated.compare(rhsTransliterated, Qt::CaseInsensitive) == 0;
231 }
232 
234 {
235  const auto lhsGeo = geo(lhs);
236  const auto rhsGeo = geo(rhs);
237  if (lhsGeo.isValid() && rhsGeo.isValid()) {
238  const auto d = distance(lhsGeo, rhsGeo);
239  switch (accuracy) {
240  case Exact:
241  return d < 100;
242  case WalkingDistance:
243  {
244  // airports are large but we have no local transport there, so the distance threshold needs to be higher there
245  const auto isAirport = JsonLd::isA<Airport>(lhs) || JsonLd::isA<Airport>(rhs);
246  return d < (isAirport ? 2000 : 1000);
247  }
248  case CityLevel:
249  return d < 50000;
250  break;
251  }
252  return false;
253  }
254 
255  const auto lhsAddr = address(lhs);
256  const auto rhsAddr = address(rhs);
257  switch (accuracy) {
258  case Exact:
259  case WalkingDistance:
260  if (!lhsAddr.streetAddress().isEmpty() && !lhsAddr.addressLocality().isEmpty()) {
261  return lhsAddr.streetAddress() == rhsAddr.streetAddress() && lhsAddr.addressLocality() == rhsAddr.addressLocality();
262  }
263  break;
264  case CityLevel:
265  if (!lhsAddr.addressLocality().isEmpty()) {
266  return lhsAddr.addressLocality() == rhsAddr.addressLocality();
267  }
268  break;
269  }
270 
271  return isSameLocationName(name(lhs), name(rhs), accuracy);
272 }
An event reservation.
Definition: reservation.h:128
QString name(const QVariant &location)
Returns a description of the location.
bool isSameLocation(const QVariant &lhs, const QVariant &rhs, Accuracy accuracy=Exact)
Returns true if the given locations are the same.
Classes for reservation/travel data models, data extraction and data augmentation.
A restaurant reservation.
Definition: reservation.h:116
A hotel reservation.
Definition: reservation.h:70
Locations match exactly.
Definition: locationutil.h:60
int size() const const
QVariant location(const QVariant &res)
Returns the location of a non-transport reservation.
T value() const const
A Taxi reservation.
Definition: reservation.h:148
A flight reservation.
Definition: reservation.h:81
Base class for places.
Definition: place.h:67
QVariant departureLocation(const QVariant &res)
Returns the departure location of the given reservation.
QVariant arrivalLocation(const QVariant &res)
Returns the arrival location of the given reservation.
A flight.
Definition: flight.h:24
QString fromUtf8(const char *str, int size)
PostalAddress address(const QVariant &location)
Returns the address of the given location.
A bus trip.
Definition: bustrip.h:21
GeoCoordinates geo(const QVariant &location)
Returns the geo coordinates of a given location.
CaseInsensitive
Locations are close enough together to not need transportation.
Definition: locationutil.h:62
bool isEmpty() const const
A boat or ferry reservation.
Definition: reservation.h:158
Geographic coordinates.
Definition: place.h:21
A Rental Car reservation.
Definition: reservation.h:136
Accuracy
Location comparison accuracy.
Definition: locationutil.h:59
A bus reservation.
Definition: reservation.h:107
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
Computes the distance between to geo coordinates in meters.
void push_back(QChar ch)
Postal address.
Definition: place.h:44
Airport.
Definition: place.h:94
A boat or ferry trip.
Definition: boattrip.h:22
const QChar at(int position) const const
Locations are in the same city.
Definition: locationutil.h:61
void reserve(int size)
An event.
Definition: event.h:20
A train reservation.
Definition: reservation.h:99
A train trip.
Definition: traintrip.h:23
int compare(const QString &other, Qt::CaseSensitivity cs) const const
bool isLocationChange(const QVariant &res)
Returns true if the given reservation is a location change.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Sep 27 2021 23:17:37 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.