KItinerary

locationutil.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
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
28using namespace KItinerary;
29
30KContacts::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{
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 }
52}
53
55{
57 return res.value<FlightReservation>().reservationFor().value<Flight>().arrivalAirport();
58 }
60 return res.value<TrainReservation>().reservationFor().value<TrainTrip>().arrivalStation();
61 }
63 return res.value<BusReservation>().reservationFor().value<BusTrip>().arrivalBusStop();
64 }
66 return res.value<RentalCarReservation>().dropoffLocation();
67 }
69 return res.value<BoatReservation>().reservationFor().value<BoatTrip>().arrivalBoatTerminal();
70 }
71 return {};
72}
73
75{
77 return res.value<FlightReservation>().reservationFor().value<Flight>().departureAirport();
78 }
80 return res.value<TrainReservation>().reservationFor().value<TrainTrip>().departureStation();
81 }
83 return res.value<BusReservation>().reservationFor().value<BusTrip>().departureBusStop();
84 }
86 return res.value<RentalCarReservation>().pickupLocation();
87 }
89 return res.value<TaxiReservation>().pickupLocation();
90 }
92 return res.value<BoatReservation>().reservationFor().value<BoatTrip>().departureBoatTerminal();
93 }
94 return {};
95}
96
98{
100 return res.value<LodgingReservation>().reservationFor();
101 }
103 return res.value<FoodEstablishmentReservation>().reservationFor();
104 }
106 return res.value<TouristAttractionVisit>().touristAttraction();
107 }
109 return res.value<EventReservation>().reservationFor().value<Event>().location();
110 }
112 return res.value<RentalCarReservation>().pickupLocation();
113 }
114
115 return {};
116}
117
119{
121 return JsonLd::convert<Place>(location).geo();
122 }
125 }
126
127 return {};
128}
129
131{
133 return JsonLd::convert<Place>(location).address();
134 }
136 return JsonLd::convert<Organization>(location).address();
137 }
138
139 return {};
140}
141
143{
145 const auto airport = location.value<Airport>();
146 return airport.name().isEmpty() ? airport.iataCode() : airport.name();
147 }
149 return JsonLd::convert<Place>(location).name();
150 }
153 }
154
155 return {};
156}
157
158int 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
164int 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
179static 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
193static 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
217static 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
231static 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}
void setStreet(const QString &street)
void setCountry(const QString &country)
void setRegion(const QString &region)
void setPostalCode(const QString &code)
void setLocality(const QString &locality)
A boat or ferry reservation.
A boat or ferry trip.
Definition boattrip.h:23
A bus reservation.
A bus trip.
Definition bustrip.h:22
An event reservation.
An event.
Definition event.h:21
A flight reservation.
Definition reservation.h:90
A flight.
Definition flight.h:25
Geographic coordinates.
Definition place.h:23
A hotel reservation.
Definition reservation.h:77
Base class for places.
Definition place.h:69
Postal address.
Definition place.h:46
QString addressCountry
The country this address is in, as ISO 3166-1 alpha 2 code.
Definition place.h:53
A Rental Car reservation.
A Taxi reservation.
A train reservation.
A train trip.
Definition traintrip.h:24
bool canConvert(const QVariant &value)
Checks if the given value can be up-cast to T.
Definition datatypes.h:31
GeoCoordinates geo(const QVariant &location)
Returns the geo coordinates of a given location.
QString name(const QVariant &location)
Returns a description of the location.
QVariant location(const QVariant &res)
Returns the location of a non-transport reservation.
bool isLocationChange(const QVariant &res)
Returns true if the given reservation is a location change.
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
Computes the distance between to geo coordinates in meters.
bool isSameLocation(const QVariant &lhs, const QVariant &rhs, Accuracy accuracy=Exact)
Returns true if the given locations are the same.
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.
PostalAddress address(const QVariant &location)
Returns the address of the given location.
Accuracy
Location comparison accuracy.
@ CityLevel
Locations are in the same city.
@ Exact
Locations match exactly.
@ WalkingDistance
Locations are close enough together to not need transportation.
QUrl geoUri(const QVariant &location)
Returns a geo: URI for the given location.
QString transliterate(QStringView s)
Transliterate diacritics or other special characters.
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
iterator begin()
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
iterator end()
bool isEmpty() const const
QString number(double n, char format, int precision)
void push_back(QChar ch)
void reserve(qsizetype size)
qsizetype size() const const
qsizetype size() const const
bool startsWith(QChar ch) const const
CaseInsensitive
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
void setScheme(const QString &scheme)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.