Skip to content

Commit a44105e

Browse files
committed
KmlCourseExport: Simple course export for MapRunF
1 parent 109ea93 commit a44105e

8 files changed

+402
-0
lines changed

code-check-wrapper.sh

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ for I in \
6565
icon_engine \
6666
key_button_bar.cpp \
6767
key_value_container \
68+
kml_course_export \
6869
line_symbol.cpp \
6970
main.cpp \
7071
/map.cpp \

src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ set(Mapper_Common_SRCS
123123
fileformats/file_format.cpp
124124
fileformats/file_format_registry.cpp
125125
fileformats/file_import_export.cpp
126+
fileformats/kml_course_export.cpp
126127
fileformats/ocd_file_export.cpp
127128
fileformats/ocd_file_format.cpp
128129
fileformats/ocd_file_import.cpp

src/fileformats/kml_course_export.cpp

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2020 Kai Pastor
3+
*
4+
* This file is part of OpenOrienteering.
5+
*
6+
* OpenOrienteering is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* OpenOrienteering is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "kml_course_export.h"
21+
22+
#include <QByteArray>
23+
#include <QIODevice>
24+
#include <QSaveFile>
25+
26+
#include "mapper_config.h"
27+
#include "core/georeferencing.h"
28+
#include "core/latlon.h"
29+
#include "core/map.h"
30+
#include "core/map_coord.h"
31+
#include "core/map_part.h"
32+
#include "core/objects/object.h"
33+
34+
35+
namespace OpenOrienteering {
36+
37+
KmlCourseExport::~KmlCourseExport() = default;
38+
39+
KmlCourseExport::KmlCourseExport(const Map& map)
40+
: map(map)
41+
{}
42+
43+
44+
bool KmlCourseExport::canExport(const PathObject* object)
45+
{
46+
if (!object)
47+
{
48+
error_string = tr("For KML course export, a single path object must be selected.");
49+
return false;
50+
}
51+
return true;
52+
}
53+
54+
bool KmlCourseExport::canExport()
55+
{
56+
return canExport(findObjectForExport());
57+
}
58+
59+
60+
bool KmlCourseExport::doExport(const QString& filepath)
61+
{
62+
auto const* const object = findObjectForExport();
63+
if (!canExport(object))
64+
{
65+
return false;
66+
}
67+
68+
device = std::make_unique<QSaveFile>(filepath);
69+
if (!device->open(QIODevice::WriteOnly))
70+
{
71+
error_string = device->errorString();
72+
return false;
73+
}
74+
75+
writeKml(*object);
76+
if (!device->commit())
77+
{
78+
error_string = device->errorString();
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
86+
QString KmlCourseExport::errorString() const
87+
{
88+
return error_string;
89+
}
90+
91+
92+
const PathObject* KmlCourseExport::findObjectForExport() const
93+
{
94+
const PathObject* path_object = nullptr;
95+
if (map.getNumSelectedObjects() == 1
96+
&& map.getFirstSelectedObject()->getType() == Object::Path)
97+
{
98+
path_object = static_cast<PathObject const*>(map.getFirstSelectedObject());
99+
}
100+
else if (map.getNumParts() == 1
101+
&& map.getPart(0)->getNumObjects() == 1
102+
&& map.getPart(0)->getObject(0)->getType() == Object::Path)
103+
{
104+
path_object = static_cast<PathObject const*>(map.getPart(0)->getObject(0));
105+
}
106+
return (path_object && path_object->parts().size() == 1) ? path_object : nullptr;
107+
}
108+
109+
110+
void KmlCourseExport::writeKml(const PathObject& object)
111+
{
112+
device->write(
113+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
114+
"<!-- Generator: OpenOrienteering Mapper " APP_VERSION " -->\n"
115+
"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
116+
"<Document>\n"
117+
" <name>Course</name>\n"
118+
" <Folder>\n"
119+
" <name>Course 1</name>\n"
120+
" <open>1</open>\n"
121+
);
122+
writeKmlPlacemarks(object.getRawCoordinateVector());
123+
device->write(
124+
" </Folder>\n"
125+
"</Document>\n"
126+
"</kml>\n"
127+
);
128+
129+
}
130+
131+
void KmlCourseExport::writeKmlPlacemarks(const std::vector<MapCoord>& coords)
132+
{
133+
auto next = [](auto current) {
134+
return current + (current->isCurveStart() ? 3 : 1);
135+
};
136+
137+
writeKmlPlacemark(coords.front(), "S1", "Start");
138+
auto code_number = 1;
139+
for (auto current = next(coords.begin()); current != coords.end() - 1; current = next(current))
140+
{
141+
auto const name = QByteArray::number(code_number);
142+
writeKmlPlacemark(*current, name, QByteArray("Control " + name));
143+
++code_number;
144+
}
145+
writeKmlPlacemark(coords.back(), "F1", "Finish");
146+
}
147+
148+
void KmlCourseExport::writeKmlPlacemark(const MapCoord& coord, const char* name, const char* description)
149+
{
150+
device->write(
151+
" <Placemark>\n"
152+
" <name>"
153+
);
154+
device->write(name);
155+
device->write("</name>\n"
156+
" <description>"
157+
);
158+
device->write(description);
159+
device->write("</description>\n"
160+
" <Point>\n"
161+
" <coordinates>"
162+
);
163+
writeCoordinates(map.getGeoreferencing().toGeographicCoords(MapCoordF(coord)));
164+
device->write(
165+
"</coordinates>\n"
166+
" </Point>\n"
167+
" </Placemark>\n"
168+
);
169+
}
170+
171+
void KmlCourseExport::writeCoordinates(const LatLon& latlon)
172+
{
173+
device->write(QByteArray::number(latlon.longitude(), 'f', 7));
174+
device->write(",");
175+
device->write(QByteArray::number(latlon.latitude(), 'f', 7));
176+
device->write(",0");
177+
}
178+
179+
180+
} // namespace OpenOrienteering

src/fileformats/kml_course_export.h

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2020 Kai Pastor
3+
*
4+
* This file is part of OpenOrienteering.
5+
*
6+
* OpenOrienteering is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* OpenOrienteering is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
#ifndef OPENORIENTEERING_KML_EXPORT_EXPORT_H
21+
#define OPENORIENTEERING_KML_EXPORT_EXPORT_H
22+
23+
#include <memory>
24+
#include <vector>
25+
26+
#include <QCoreApplication>
27+
#include <QString>
28+
29+
class QSaveFile;
30+
31+
namespace OpenOrienteering {
32+
33+
class LatLon;
34+
class Map;
35+
class MapCoord;
36+
class PathObject;
37+
38+
39+
/**
40+
* This class generates KML course files for MapRunF.
41+
*
42+
* This export handles a single path object and outputs placemarks for start
43+
* (S1), finish (F1), and controls in between (starting from number 1). The path
44+
* is either the single selected path object, or the single path object
45+
* contained in the map's single map part.
46+
*
47+
* Due to its special mode of operation, this class is not implemented as a
48+
* subclass of Exporter. But the API is kept similar.
49+
*/
50+
class KmlCourseExport
51+
{
52+
Q_DECLARE_TR_FUNCTIONS(OpenOrienteering::KmlCourseExport)
53+
54+
public:
55+
~KmlCourseExport();
56+
57+
KmlCourseExport(const Map& map);
58+
59+
bool canExport(const PathObject* object);
60+
61+
bool canExport();
62+
63+
bool doExport(const QString& filepath);
64+
65+
QString errorString() const;
66+
67+
protected:
68+
const PathObject* findObjectForExport() const;
69+
70+
void writeKml(const PathObject& object);
71+
72+
void writeKmlPlacemarks(const std::vector<MapCoord>& coords);
73+
74+
void writeKmlPlacemark(const MapCoord& coord, const char* name, const char* description);
75+
76+
void writeCoordinates(const LatLon& latlon);
77+
78+
private:
79+
std::unique_ptr<QSaveFile> device;
80+
const Map& map;
81+
QString error_string;
82+
83+
};
84+
85+
86+
} // namespace OpenOrienteering
87+
88+
#endif // OPENORIENTEERING_KML_EXPORT_EXPORT_H

src/gui/map/map_editor.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
#include "fileformats/file_format.h"
113113
#include "fileformats/file_format_registry.h"
114114
#include "fileformats/file_import_export.h"
115+
#include "fileformats/kml_course_export.h"
115116
#include "gui/configure_grid_dialog.h"
116117
#include "gui/file_dialog.h"
117118
#include "gui/georeferencing_dialog.h"
@@ -955,6 +956,7 @@ void MapEditorController::createActions()
955956
#ifndef MAPPER_USE_GDAL
956957
export_kmz_act->setVisible(false);
957958
#endif
959+
export_kml_course_act = newAction("export-kml-course", tr("KML &course"), this, SLOT(exportKmlCourse()), nullptr, QString{}, "file_menu.html");
958960
export_pdf_act = newAction("export-pdf", tr("&PDF"), print_act_mapper, SLOT(map()), nullptr, QString{}, "file_menu.html");
959961
print_act_mapper->setMapping(export_pdf_act, PrintWidget::EXPORT_PDF_TASK);
960962
#ifdef MAPPER_USE_GDAL
@@ -967,6 +969,7 @@ void MapEditorController::createActions()
967969
print_act = nullptr;
968970
export_image_act = nullptr;
969971
export_kmz_act = nullptr;
972+
export_kml_course_act = nullptr;
970973
export_pdf_act = nullptr;
971974
#endif
972975

@@ -1138,6 +1141,7 @@ void MapEditorController::createMenuAndToolbars()
11381141
export_menu->menuAction()->setMenuRole(QAction::NoRole);
11391142
export_menu->addAction(export_image_act);
11401143
export_menu->addAction(export_kmz_act);
1144+
export_menu->addAction(export_kml_course_act);
11411145
export_menu->addAction(export_pdf_act);
11421146
if (export_vector_act)
11431147
export_menu->addAction(export_vector_act);
@@ -1742,6 +1746,45 @@ bool MapEditorController::keyReleaseEventFilter(QKeyEvent* event)
17421746

17431747

17441748

1749+
// slot
1750+
void MapEditorController::exportKmlCourse()
1751+
{
1752+
KmlCourseExport exporter{*map};
1753+
if (!exporter.canExport())
1754+
{
1755+
QMessageBox::warning(window, tr("Error"), exporter.errorString());
1756+
return;
1757+
}
1758+
1759+
QSettings settings;
1760+
auto const import_directory = settings.value(QString::fromLatin1("importFileDirectory"), QDir::homePath()).toString();
1761+
1762+
auto filepath = FileDialog::getSaveFileName(
1763+
window,
1764+
tr("Export"),
1765+
import_directory,
1766+
{tr("KML course") + QStringLiteral(" (*.kml)")});
1767+
if (filepath.isEmpty())
1768+
return;
1769+
1770+
if (!filepath.endsWith(QLatin1String(".kml", Qt::CaseInsensitive)))
1771+
filepath.append(QLatin1String(".kml"));
1772+
1773+
settings.setValue(QString::fromLatin1("importFileDirectory"), QFileInfo(filepath).canonicalPath());
1774+
1775+
if (exporter.doExport(filepath))
1776+
{
1777+
window->showStatusBarMessage(tr("Exported successfully to %1").arg(filepath), 4000);
1778+
}
1779+
else
1780+
{
1781+
QMessageBox::information(window, tr("Error"),
1782+
ImportExport::tr("Cannot save file\n%1:\n%2")
1783+
.arg(filepath, exporter.errorString()));
1784+
}
1785+
}
1786+
1787+
17451788
// slot
17461789
void MapEditorController::exportVector()
17471790
{

src/gui/map/map_editor.h

+6
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ Q_OBJECT
256256
bool keyReleaseEventFilter(QKeyEvent* event) override;
257257

258258
public slots:
259+
/**
260+
* Lets the user export a PathObject as MapRunF KML course.
261+
*/
262+
void exportKmlCourse();
263+
259264
/**
260265
* Lets the user export the map as geospatial vector data.
261266
*/
@@ -689,6 +694,7 @@ protected slots:
689694
QAction* print_act;
690695
QAction* export_image_act;
691696
QAction* export_kmz_act;
697+
QAction* export_kml_course_act;
692698
QAction* export_pdf_act;
693699
QAction* export_vector_act;
694700

0 commit comments

Comments
 (0)