Commit 8b35b406 authored by Volker Krause's avatar Volker Krause
Browse files

Add trip group GPX export

Useful for importing into Nextcloud Maps.
parent 5bd4ba44
......@@ -8,6 +8,7 @@ set(itinerary_srcs
applicationcontroller.cpp
documentmanager.cpp
favoritelocationmodel.cpp
gpxexport.cpp
importexport.cpp
json.cpp
livedata.cpp
......@@ -44,6 +45,7 @@ add_library(itinerary STATIC ${itinerary_srcs})
target_link_libraries(itinerary PUBLIC
itinerary-weather
SolidExtras
GpxIo
KPublicTransport
KPim::Itinerary
KPim::PkPass
......
......@@ -7,6 +7,7 @@
import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.1 as QQC2
import Qt.labs.platform 1.1 as Platform
import org.kde.kirigami 2.12 as Kirigami
import org.kde.itinerary 1.0
import "." as App
......@@ -146,6 +147,16 @@ Kirigami.ScrollablePage {
}
}
Platform.FileDialog {
id: tripGroupGpxExportDialog
property string tripGroupId
fileMode: Platform.FileDialog.SaveFile
title: i18n("Export Trip")
folder: Platform.StandardPaths.writableLocation(StandardPaths.DocumentsLocation)
nameFilters: [i18n("GPX Files (*.gpx)")]
onAccepted: ApplicationController.exportTripToGpx(tripGroupId, file)
}
Kirigami.CardsListView {
id: listView
model: TripGroupProxyModel
......
......@@ -110,6 +110,13 @@ Kirigami.AbstractCard {
Row {
visible: root.rangeType == TimelineElement.RangeBegin
anchors.right: parent.right
QQC2.ToolButton {
icon.name: "export-symbolic"
onClicked: {
tripGroupGpxExportDialog.tripGroupId = root.tripGroupId
tripGroupGpxExportDialog.open()
}
}
QQC2.ToolButton {
icon.name: "edit-delete"
onClicked: {
......
......@@ -7,11 +7,15 @@
#include "applicationcontroller.h"
#include "documentmanager.h"
#include "favoritelocationmodel.h"
#include "gpxexport.h"
#include "importexport.h"
#include "livedatamanager.h"
#include "logging.h"
#include "pkpassmanager.h"
#include "reservationmanager.h"
#include "transfermanager.h"
#include "tripgroup.h"
#include "tripgroupmanager.h"
#include <itinerary_version_detailed.h>
#include <KItinerary/CreativeWork>
......@@ -167,6 +171,11 @@ void ApplicationController::setLiveDataManager(LiveDataManager *liveDataMgr)
m_liveDataMgr = liveDataMgr;
}
void ApplicationController::setTripGroupManager(TripGroupManager *tripGroupMgr)
{
m_tripGroupMgr = tripGroupMgr;
}
#ifdef Q_OS_ANDROID
void ApplicationController::importFromIntent(const KAndroidExtras::Intent &intent)
{
......@@ -407,6 +416,41 @@ void ApplicationController::exportToFile(const QString &filePath)
exporter.exportSettings();
}
void ApplicationController::exportTripToGpx(const QString &tripGroupId, const QString &filePath)
{
if (filePath.isEmpty()) {
return;
}
QFile f(QUrl(filePath).isLocalFile() ? QUrl(filePath).toLocalFile() : filePath);
if (!f.open(QFile::WriteOnly)) {
qCWarning(Log) << f.errorString() << f.fileName();
return;
}
GpxExport exporter(&f);
const auto tg = m_tripGroupMgr->tripGroup(tripGroupId);
const auto batches = tg.elements();
for (const auto &batchId : batches) {
exporter.writeStartRoute();
auto transfer = m_transferMgr->transfer(batchId, Transfer::Before);
if (transfer.state() == Transfer::Selected) {
exporter.writeTransfer(transfer);
}
const auto res = m_resMgr->reservation(batchId);
exporter.writeReservation(res, m_liveDataMgr->journey(batchId));
transfer = m_transferMgr->transfer(batchId, Transfer::After);
if (transfer.state() == Transfer::Selected) {
exporter.writeTransfer(transfer);
}
exporter.writeEndRoute();
}
}
void ApplicationController::importBundle(const QUrl &url)
{
KItinerary::File f(url.isLocalFile() ? url.toLocalFile() : url.toString());
......
......@@ -15,6 +15,7 @@ class LiveDataManager;
class PkPassManager;
class ReservationManager;
class TransferManager;
class TripGroupManager;
namespace KItinerary {
class File;
......@@ -42,6 +43,7 @@ public:
void setTransferManager(TransferManager *transferMgr);
void setFavoriteLocationModel(FavoriteLocationModel *favLocModel);
void setLiveDataManager(LiveDataManager *liveDataMgr);
void setTripGroupManager(TripGroupManager *tripGroupMgr);
// data import
void importFromIntent(const KAndroidExtras::Intent &intent);
......@@ -60,6 +62,7 @@ public:
// data export
Q_INVOKABLE void exportToFile(const QString &filePath);
Q_INVOKABLE void exportTripToGpx(const QString &tripGroupId, const QString &filePath);
// document attaching
Q_INVOKABLE void addDocument(const QString &batchId, const QUrl &url);
......@@ -85,6 +88,7 @@ private:
TransferManager *m_transferMgr = nullptr;
FavoriteLocationModel *m_favLocModel = nullptr;
LiveDataManager *m_liveDataMgr = nullptr;
TripGroupManager *m_tripGroupMgr = nullptr;
QNetworkAccessManager *m_nam = nullptr;
};
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "gpxexport.h"
#include "transfer.h"
#include <KItinerary/Event>
#include <KItinerary/LocationUtil>
#include <KItinerary/Reservation>
#include <KItinerary/SortUtil>
#include <KPublicTransport/Stopover>
#include <KLocalizedString>
using namespace KItinerary;
using namespace KPublicTransport;
GpxExport::GpxExport(QIODevice* out)
: m_writer(out)
{
m_writer.writeStartMetadata();
m_writer.writeLink(QStringLiteral("https://apps.kde.org/itinerary"), i18n("KDE Itinerary"));
m_writer.writeEndMetadata();
}
GpxExport::~GpxExport() = default;
void GpxExport::writeReservation(const QVariant &res, const KPublicTransport::JourneySection &journey)
{
if (LocationUtil::isLocationChange(res)) {
if (!journey.from().isEmpty() && !journey.to().isEmpty()) {
writeJourneySection(journey);
} else {
const auto dep = LocationUtil::departureLocation(res);
auto coord = LocationUtil::geo(dep);
m_writer.writeStartRoutePoint(coord.latitude(), coord.longitude());
m_writer.writeName(LocationUtil::name(dep));
m_writer.writeTime(SortUtil::startDateTime(res));
m_writer.writeEndRoutePoint();
const auto arr = LocationUtil::arrivalLocation(res);
coord = LocationUtil::geo(arr);
m_writer.writeStartRoutePoint(coord.latitude(), coord.longitude());
m_writer.writeName(LocationUtil::name(arr));
m_writer.writeTime(SortUtil::endDateTime(res));
m_writer.writeEndRoutePoint();
}
} else {
if (JsonLd::isA<LodgingReservation>(res)) {
m_writer.writeName(res.value<LodgingReservation>().reservationFor().value<LodgingBusiness>().name());
} else if (JsonLd::isA<EventReservation>(res)) {
m_writer.writeName(res.value<EventReservation>().reservationFor().value<Event>().name());
} else if (JsonLd::isA<FoodEstablishmentReservation>(res)) {
m_writer.writeName(res.value<FoodEstablishmentReservation>().reservationFor().value<FoodEstablishment>().name());
}
const auto loc = LocationUtil::location(res);
const auto coord = LocationUtil::geo(loc);
m_writer.writeStartRoutePoint(coord.latitude(), coord.longitude());
m_writer.writeEndRoutePoint();
m_writer.writeStartRoutePoint(coord.latitude(), coord.longitude());
m_writer.writeEndRoutePoint();
}
}
void GpxExport::writeTransfer(const Transfer &transfer)
{
const auto journey = transfer.journey();
for (const auto &section : journey.sections()) {
writeJourneySection(section);
}
}
void GpxExport::writeJourneySection(const KPublicTransport::JourneySection &section)
{
if (section.mode() == JourneySection::Waiting) {
return;
}
if (section.path().isEmpty()) {
m_writer.writeStartRoutePoint(section.from().latitude(), section.from().longitude());
m_writer.writeTime(section.hasExpectedDepartureTime() ? section.expectedDepartureTime() : section.scheduledDepartureTime());
m_writer.writeEndRoutePoint();
for (const auto &stop : section.intermediateStops()) {
m_writer.writeStartRoutePoint(stop.stopPoint().latitude(), stop.stopPoint().longitude());
m_writer.writeName(stop.stopPoint().name());
m_writer.writeTime(stop.hasExpectedDepartureTime() ? stop.expectedDepartureTime() : stop.scheduledDepartureTime());
m_writer.writeEndRoutePoint();
}
m_writer.writeStartRoutePoint(section.to().latitude(), section.to().longitude());
m_writer.writeTime(section.hasExpectedArrivalTime() ? section.expectedArrivalTime() : section.scheduledArrivalTime());
m_writer.writeEndRoutePoint();
} else {
for (const auto &pathSec : section.path().sections()) {
// TODO name?
for (const auto &pt : pathSec.path()) {
m_writer.writeStartRoutePoint(pt.y(), pt.x());
m_writer.writeEndRoutePoint();
}
}
}
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef GPXEXPORT_H
#define GPXEXPORT_H
#include <KPublicTransport/Journey>
#include <gpx/gpxwriter.h>
class Transfer;
/** Trip group to GPX export. */
class GpxExport
{
public:
explicit GpxExport(QIODevice *out);
~GpxExport();
void writeReservation(const QVariant &res, const KPublicTransport::JourneySection &journey = {});
void writeTransfer(const Transfer &transfer);
inline void writeStartRoute() { m_writer.writeStartRoute(); }
inline void writeEndRoute() { m_writer.writeEndRoute(); }
private:
void writeJourneySection(const KPublicTransport::JourneySection &section);
Gpx::Writer m_writer;
};
#endif // GPXEXPORT_H
......@@ -305,6 +305,7 @@ int main(int argc, char **argv)
appController.setFavoriteLocationModel(&favLocModel);
appController.setTransferManager(&transferManager);
appController.setLiveDataManager(&liveDataMgr);
appController.setTripGroupManager(&tripGroupMgr);
#ifndef Q_OS_ANDROID
QObject::connect(&service, &KDBusService::activateRequested, [&parser, &appController](const QStringList &args, const QString &workingDir) {
qCDebug(Log) << "remote activation" << args << workingDir;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment