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 #include "locationutil_p.h"
9 #include "stringutil.h"
10 
11 #include <KItinerary/BoatTrip>
12 #include <KItinerary/BusTrip>
13 #include <KItinerary/Event>
14 #include <KItinerary/Flight>
15 #include <KItinerary/Place>
16 #include <KItinerary/Reservation>
17 #include <KItinerary/TrainTrip>
18 #include <KItinerary/Visit>
19 
20 #include <KContacts/Address>
21 
22 #include <QDebug>
23 #include <QUrl>
24 #include <QUrlQuery>
25 
26 #include <cmath>
27 
28 using namespace KItinerary;
29 
30 KContacts::Address LocationUtil::toAddress(const PostalAddress &addr)
31 {
33  a.setStreet(addr.streetAddress());
34  a.setPostalCode(addr.postalCode());
35  a.setLocality(addr.addressLocality());
36  a.setRegion(addr.addressRegion());
37  a.setCountry(addr.addressCountry());
38  return a;
39 }
40 
42 {
43  if (JsonLd::isA<RentalCarReservation>(res)) {
44  const auto pickup = departureLocation(res);
45  const auto dropoff = arrivalLocation(res);
46  if (dropoff.value<Place>().name().isEmpty()) {
47  return false;
48  }
49  return !isSameLocation(pickup, dropoff);
50  }
51  return JsonLd::isA<FlightReservation>(res) || JsonLd::isA<TrainReservation>(res) || JsonLd::isA<BusReservation>(res) || JsonLd::isA<TaxiReservation>(res) || JsonLd::isA<BoatReservation>(res);
52 }
53 
55 {
56  if (JsonLd::isA<FlightReservation>(res)) {
57  return res.value<FlightReservation>().reservationFor().value<Flight>().arrivalAirport();
58  }
59  if (JsonLd::isA<TrainReservation>(res)) {
60  return res.value<TrainReservation>().reservationFor().value<TrainTrip>().arrivalStation();
61  }
62  if (JsonLd::isA<BusReservation>(res)) {
63  return res.value<BusReservation>().reservationFor().value<BusTrip>().arrivalBusStop();
64  }
65  if (JsonLd::isA<RentalCarReservation>(res)) {
66  return res.value<RentalCarReservation>().dropoffLocation();
67  }
68  if (JsonLd::isA<BoatReservation>(res)) {
69  return res.value<BoatReservation>().reservationFor().value<BoatTrip>().arrivalBoatTerminal();
70  }
71  return {};
72 }
73 
75 {
76  if (JsonLd::isA<FlightReservation>(res)) {
77  return res.value<FlightReservation>().reservationFor().value<Flight>().departureAirport();
78  }
79  if (JsonLd::isA<TrainReservation>(res)) {
80  return res.value<TrainReservation>().reservationFor().value<TrainTrip>().departureStation();
81  }
82  if (JsonLd::isA<BusReservation>(res)) {
83  return res.value<BusReservation>().reservationFor().value<BusTrip>().departureBusStop();
84  }
85  if (JsonLd::isA<RentalCarReservation>(res)) {
86  return res.value<RentalCarReservation>().pickupLocation();
87  }
88  if (JsonLd::isA<TaxiReservation>(res)) {
89  return res.value<TaxiReservation>().pickupLocation();
90  }
91  if (JsonLd::isA<BoatReservation>(res)) {
92  return res.value<BoatReservation>().reservationFor().value<BoatTrip>().departureBoatTerminal();
93  }
94  return {};
95 }
96 
98 {
99  if (JsonLd::isA<LodgingReservation>(res)) {
100  return res.value<LodgingReservation>().reservationFor();
101  }
102  if (JsonLd::isA<FoodEstablishmentReservation>(res)) {
103  return res.value<FoodEstablishmentReservation>().reservationFor();
104  }
105  if (JsonLd::isA<TouristAttractionVisit>(res)) {
106  return res.value<TouristAttractionVisit>().touristAttraction();
107  }
108  if (JsonLd::isA<EventReservation>(res)) {
109  return res.value<EventReservation>().reservationFor().value<Event>().location();
110  }
111  if (JsonLd::isA<RentalCarReservation>(res)) {
112  return res.value<RentalCarReservation>().pickupLocation();
113  }
114 
115  return {};
116 }
117 
119 {
120  if (JsonLd::canConvert<Place>(location)) {
121  return JsonLd::convert<Place>(location).geo();
122  }
123  if (JsonLd::canConvert<Organization>(location)) {
124  return JsonLd::convert<Organization>(location).geo();
125  }
126 
127  return {};
128 }
129 
131 {
132  if (JsonLd::canConvert<Place>(location)) {
133  return JsonLd::convert<Place>(location).address();
134  }
135  if (JsonLd::canConvert<Organization>(location)) {
136  return JsonLd::convert<Organization>(location).address();
137  }
138 
139  return {};
140 }
141 
142 QString LocationUtil::name(const QVariant &location)
143 {
144  if (JsonLd::isA<Airport>(location)) {
145  const auto airport = location.value<Airport>();
146  return airport.name().isEmpty() ? airport.iataCode() : airport.name();
147  }
148  if (JsonLd::canConvert<Place>(location)) {
149  return JsonLd::convert<Place>(location).name();
150  }
151  if (JsonLd::canConvert<Organization>(location)) {
152  return JsonLd::convert<Organization>(location).name();
153  }
154 
155  return {};
156 }
157 
158 int LocationUtil::distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
159 {
160  return distance(coord1.latitude(), coord1.longitude(), coord2.latitude(), coord2.longitude());
161 }
162 
163 // see https://en.wikipedia.org/wiki/Haversine_formula
164 int LocationUtil::distance(float lat1, float lon1, float lat2, float lon2)
165 {
166  const auto degToRad = M_PI / 180.0;
167  const auto earthRadius = 6371000.0; // in meters
168 
169  const auto d_lat = (lat1 - lat2) * degToRad;
170  const auto d_lon = (lon1 - lon2) * degToRad;
171 
172  const auto a = pow(sin(d_lat / 2.0), 2) + cos(lat1 * degToRad) * cos(lat2 * degToRad) * pow(sin(d_lon / 2.0), 2);
173  return 2.0 * earthRadius * atan2(sqrt(a), sqrt(1.0 - a));
174 }
175 
176 // if the character has a canonical decomposition use that and skip the combining diacritic markers following it
177 // see https://en.wikipedia.org/wiki/Unicode_equivalence
178 // see https://en.wikipedia.org/wiki/Combining_character
179 static QString stripDiacritics(const QString &s)
180 {
181  QString res;
182  res.reserve(s.size());
183  for (const auto &c : s) {
184  if (c.decompositionTag() == QChar::Canonical) {
185  res.push_back(c.decomposition().at(0));
186  } else {
187  res.push_back(c);
188  }
189  }
190  return res;
191 }
192 
193 static bool compareSpaceCaseInsenstive(const QString &lhs, const QString &rhs)
194 {
195  auto lit = lhs.begin();
196  auto rit = rhs.begin();
197  while (true) {
198  while ((*lit).isSpace() && lit != lhs.end()) {
199  ++lit;
200  }
201  while ((*rit).isSpace() && rit != rhs.end()) {
202  ++rit;
203  }
204  if (lit == lhs.end() || rit == rhs.end()) {
205  break;
206  }
207  if ((*lit).toCaseFolded() != (*rit).toCaseFolded()) {
208  return false;
209  }
210  ++lit;
211  ++rit;
212  }
213 
214  return lit == lhs.end() && rit == rhs.end();
215 }
216 
217 static bool hasCommonPrefix(QStringView lhs, QStringView rhs)
218 {
219  // check for a common prefix
220  bool foundSeparator = false;
221  for (auto i = 0; i < std::min(lhs.size(), rhs.size()); ++i) {
222  if (lhs[i].toCaseFolded() != rhs[i].toCaseFolded()) {
223  return foundSeparator;
224  }
225  foundSeparator |= !lhs[i].isLetter();
226  }
227 
228  return lhs.startsWith(rhs, Qt::CaseInsensitive) || rhs.startsWith(lhs, Qt::CaseInsensitive);
229 }
230 
231 static bool isSameLocationName(const QString &lhs, const QString &rhs, LocationUtil::Accuracy accuracy)
232 {
233  if (lhs.isEmpty() || rhs.isEmpty()) {
234  return false;
235  }
236 
237  // actually equal
238  if (lhs.compare(rhs, Qt::CaseInsensitive) == 0) {
239  return true;
240  }
241 
242  // check if any of the Unicode normalization approaches helps
243  const auto lhsNormalized = stripDiacritics(lhs);
244  const auto rhsNormalized = stripDiacritics(rhs);
245  const auto lhsTransliterated = StringUtil::transliterate(lhs);
246  const auto rhsTransliterated = StringUtil::transliterate(rhs);
247  if (compareSpaceCaseInsenstive(lhsNormalized, rhsNormalized) || compareSpaceCaseInsenstive(lhsNormalized, rhsTransliterated)
248  || compareSpaceCaseInsenstive(lhsTransliterated, rhsNormalized) || compareSpaceCaseInsenstive(lhsTransliterated, rhsTransliterated)) {
249  return true;
250  }
251 
252  if (accuracy == LocationUtil::CityLevel) {
253  // check for a common prefix
254  return hasCommonPrefix(lhsNormalized, rhsNormalized) || hasCommonPrefix(lhsTransliterated, rhsTransliterated);
255  }
256 
257  return false;
258 }
259 
261 {
262  const auto lhsGeo = geo(lhs);
263  const auto rhsGeo = geo(rhs);
264  if (lhsGeo.isValid() && rhsGeo.isValid()) {
265  const auto d = distance(lhsGeo, rhsGeo);
266  switch (accuracy) {
267  case Exact:
268  return d < 100;
269  case WalkingDistance:
270  {
271  // airports are large but we have no local transport there, so the distance threshold needs to be higher there
272  const auto isAirport = JsonLd::isA<Airport>(lhs) || JsonLd::isA<Airport>(rhs);
273  return d < (isAirport ? 2000 : 1000);
274  }
275  case CityLevel:
276  if (d >= 50000) {
277  return false;
278  }
279  if (d < 2000) {
280  return true;
281  }
282  if (d < 50000 && address(lhs).addressLocality().isEmpty() && name(lhs).isEmpty()) {
283  return true;
284  }
285  break;
286  }
287  }
288 
289  const auto lhsAddr = address(lhs);
290  const auto rhsAddr = address(rhs);
291  switch (accuracy) {
292  case Exact:
293  case WalkingDistance:
294  if (!lhsAddr.streetAddress().isEmpty() && !lhsAddr.addressLocality().isEmpty()) {
295  return lhsAddr.streetAddress() == rhsAddr.streetAddress() && lhsAddr.addressLocality() == rhsAddr.addressLocality();
296  }
297  break;
298  case CityLevel:
299  if (!lhsAddr.addressLocality().isEmpty()) {
300  return isSameLocationName(lhsAddr.addressLocality(), rhsAddr.addressLocality(), LocationUtil::Exact);
301  }
302  break;
303  }
304 
305  return isSameLocationName(name(lhs), name(rhs), accuracy);
306 }
307 
309 {
310  QUrl url;
311  url.setScheme(QStringLiteral("geo"));
312 
313  const auto geo = LocationUtil::geo(location);
314  if (geo.isValid()) {
315  url.setPath(QString::number(geo.latitude()) + QLatin1Char(',') + QString::number(geo.longitude()));
316  return url;
317  }
318 
319  const auto addr = LocationUtil::address(location);
320  if (!addr.isEmpty()) {
321  url.setPath(QStringLiteral("0,0"));
322  QUrlQuery query;
323  query.addQueryItem(QStringLiteral("q"), toAddress(addr).formatted(KContacts::AddressFormatStyle::GeoUriQuery));
324  url.setQuery(query);
325  return url;
326  }
327 
328  return {};
329 }
QString::iterator begin()
A train reservation.
Definition: reservation.h:105
void setLocality(const QString &locality)
QString number(int n, int base)
QVariant location(const QVariant &res)
Returns the location of a non-transport reservation.
void setStreet(const QString &street)
A Taxi reservation.
Definition: reservation.h:164
int size() const const
CaseInsensitive
T value() const const
A boat or ferry reservation.
Definition: reservation.h:176
@ CityLevel
Locations are in the same city.
Definition: locationutil.h:62
An event reservation.
Definition: reservation.h:140
QUrl geoUri(const QVariant &location)
Returns a geo: URI for the given location.
void reserve(int size)
QString::iterator end()
qsizetype size() const const
void setScheme(const QString &scheme)
A bus trip.
Definition: bustrip.h:21
A hotel reservation.
Definition: reservation.h:72
Base class for places.
Definition: place.h:68
void setRegion(const QString &region)
Accuracy
Location comparison accuracy.
Definition: locationutil.h:60
A bus reservation.
Definition: reservation.h:115
bool isEmpty() const const
A boat or ferry trip.
Definition: boattrip.h:22
QVariant arrivalLocation(const QVariant &res)
Returns the arrival location of the given reservation.
void push_back(QChar ch)
QVariant departureLocation(const QVariant &res)
Returns the departure location of the given reservation.
An event.
Definition: event.h:20
PostalAddress address(const QVariant &location)
Returns the address of the given location.
GeoCoordinates geo(const QVariant &location)
Returns the geo coordinates of a given location.
Postal address.
Definition: place.h:45
bool isSameLocation(const QVariant &lhs, const QVariant &rhs, Accuracy accuracy=Exact)
Returns true if the given locations are the same.
Geographic coordinates.
Definition: place.h:22
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
Computes the distance between to geo coordinates in meters.
QString transliterate(QStringView s)
Transliterate diacritics or other special characters.
Definition: stringutil.cpp:168
void setQuery(const QString &query, QUrl::ParsingMode mode)
void setPostalCode(const QString &code)
Airport.
Definition: place.h:104
A flight.
Definition: flight.h:24
void setPath(const QString &path, QUrl::ParsingMode mode)
void setCountry(const QString &country)
A Rental Car reservation.
Definition: reservation.h:150
A train trip.
Definition: traintrip.h:23
int compare(const QString &other, Qt::CaseSensitivity cs) const const
@ Exact
Locations match exactly.
Definition: locationutil.h:61
bool isEmpty
The country this address is in, as ISO 3166-1 alpha 2 code.
Definition: place.h:55
@ WalkingDistance
Locations are close enough together to not need transportation.
Definition: locationutil.h:63
bool startsWith(QStringView str, Qt::CaseSensitivity cs) const const
A flight reservation.
Definition: reservation.h:85
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-2023 The KDE developers.
Generated on Mon May 8 2023 04:02:09 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.