1 /*
2  *    Copyright 2016-2019 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 "ogr_file_format.h"
21 #include "ogr_file_format_p.h"  // IWYU pragma: associated
22 
23 #include <algorithm>
24 #include <cmath>
25 #include <cstddef>
26 #include <functional>
27 #include <iterator>
28 #include <memory>
29 #include <vector>
30 #include <utility>
31 
32 #include <cpl_conv.h>
33 #include <gdal.h>
34 #include <ogr_api.h>
35 #include <ogr_srs_api.h>
36 // IWYU pragma: no_include <cpl_error.h>
37 // IWYU pragma: no_include <gdal_version.h>
38 
39 #include <QtGlobal>
40 #include <QtMath>
41 #include <QByteArray>
42 #include <QColor>
43 #include <QFileInfo>
44 #include <QFlags>
45 #include <QHash>
46 #include <QLatin1Char>
47 #include <QLatin1String>
48 #include <QPointF>
49 #include <QRegularExpression>
50 #include <QRegularExpressionMatch>
51 #include <QScopedValueRollback>
52 #include <QString>
53 #include <QStringRef>
54 #include <QVariant>
55 
56 #include "core/georeferencing.h"
57 #include "core/latlon.h"
58 #include "core/map.h"
59 #include "core/map_color.h"
60 #include "core/map_coord.h"
61 #include "core/map_part.h"
62 #include "core/path_coord.h"
63 #include "core/virtual_path.h"
64 #include "core/objects/object.h"
65 #include "core/objects/text_object.h"
66 #include "core/symbols/area_symbol.h"
67 #include "core/symbols/combined_symbol.h"
68 #include "core/symbols/line_symbol.h"
69 #include "core/symbols/point_symbol.h"
70 #include "core/symbols/symbol.h"
71 #include "core/symbols/text_symbol.h"
72 #include "fileformats/file_import_export.h"
73 #include "gdal/gdal_manager.h"
74 
75 // IWYU pragma: no_forward_declare QFile
76 
77 namespace OpenOrienteering {
78 
79 namespace {
80 
applyPenWidth(OGRStyleToolH tool,LineSymbol * line_symbol)81 	void applyPenWidth(OGRStyleToolH tool, LineSymbol* line_symbol)
82 	{
83 		int is_null;
84 		auto pen_width = OGR_ST_GetParamDbl(tool, OGRSTPenWidth, &is_null);
85 		if (!is_null)
86 		{
87 			Q_ASSERT(OGR_ST_GetUnit(tool) == OGRSTUMM);
88 
89 			if (pen_width <= 0.01)
90 				pen_width = 0.1;
91 
92 			line_symbol->setLineWidth(pen_width);
93 		}
94 	}
95 
applyPenCap(OGRStyleToolH tool,LineSymbol * line_symbol)96 	void applyPenCap(OGRStyleToolH tool, LineSymbol* line_symbol)
97 	{
98 		int is_null;
99 		auto pen_cap = OGR_ST_GetParamStr(tool, OGRSTPenCap, &is_null);
100 		if (!is_null)
101 		{
102 			switch (pen_cap[0])
103 			{
104 			case 'p':
105 				line_symbol->setCapStyle(LineSymbol::SquareCap);
106 				break;
107 			case 'r':
108 				line_symbol->setCapStyle(LineSymbol::RoundCap);
109 				break;
110 			default:
111 				;
112 			}
113 		}
114 	}
115 
applyPenJoin(OGRStyleToolH tool,LineSymbol * line_symbol)116 	void applyPenJoin(OGRStyleToolH tool, LineSymbol* line_symbol)
117 	{
118 		int is_null;
119 		auto pen_join = OGR_ST_GetParamStr(tool, OGRSTPenJoin, &is_null);
120 		if (!is_null)
121 		{
122 			switch (pen_join[0])
123 			{
124 			case 'b':
125 				line_symbol->setJoinStyle(LineSymbol::BevelJoin);
126 				break;
127 			case 'r':
128 				line_symbol->setJoinStyle(LineSymbol::RoundJoin);
129 				break;
130 			default:
131 				;
132 			}
133 		}
134 	}
135 
applyPenPattern(OGRStyleToolH tool,LineSymbol * line_symbol)136 	void applyPenPattern(OGRStyleToolH tool, LineSymbol* line_symbol)
137 	{
138 		int is_null;
139 		auto raw_pattern = OGR_ST_GetParamStr(tool, OGRSTPenPattern, &is_null);
140 		if (!is_null)
141 		{
142 			auto pattern = QString::fromLatin1(raw_pattern);
143 			auto sub_pattern_re = QRegularExpression(QString::fromLatin1("([0-9.]+)([a-z]*) *([0-9.]+)([a-z]*)"));
144 			auto match = sub_pattern_re.match(pattern);
145 			double length_0{}, length_1{};
146 			bool ok = match.hasMatch();
147 			if (ok)
148 				length_0 = match.capturedRef(1).toDouble(&ok);
149 			if (ok)
150 				length_1 = match.capturedRef(3).toDouble(&ok);
151 			if (ok)
152 			{
153 				/// \todo Apply units from capture 2 and 4
154 				line_symbol->setDashed(true);
155 				line_symbol->setDashLength(qMax(100, qRound(length_0 * 1000)));
156 				line_symbol->setBreakLength(qMax(100, qRound(length_1 * 1000)));
157 			}
158 			else
159 			{
160 				qDebug("OgrFileImportFormat: Failed to parse dash pattern '%s'", raw_pattern);
161 			}
162 		}
163 	}
164 
165 #if 0
166 	int getFontSize(const char* font_size_string)
167 	{
168 		auto pattern = QString::fromLatin1(font_size_string);
169 		auto sub_pattern_re = QRegularExpression(QString::fromLatin1("([0-9.]+)([a-z]*)"));
170 		auto match = sub_pattern_re.match(pattern);
171 		double font_size;
172 		bool ok = match.hasMatch();
173 		if (ok)
174 			font_size = match.capturedRef(1).toDouble(&ok);
175 		if (ok)
176 		{
177 			auto unit = match.capturedRef(2).toUtf8();
178 			if (!unit.isEmpty())
179 			{
180 				if (unit == "pt")
181 				{
182 
183 				}
184 				else if (unit == "px")
185 				{
186 
187 				}
188 				else
189 				{
190 					qDebug("OgrFileImportFormat: Unsupported font size unit '%s'", unit.constData());
191 				}
192 			}
193 		}
194 		else
195 		{
196 			qDebug("OgrFileImportFormat: Failed to parse font size '%s'", font_size_string);
197 			font_size = 0;
198 		}
199 		return font_size;
200 	}
201 #endif
202 
applyLabelAnchor(int anchor,TextObject * text_object)203 	void applyLabelAnchor(int anchor, TextObject* text_object)
204 	{
205 		auto v_align = (anchor - 1) / 3;
206 		switch (v_align)
207 		{
208 		case 0:
209 			text_object->setVerticalAlignment(TextObject::AlignBaseline);
210 			break;
211 		case 1:
212 			text_object->setVerticalAlignment(TextObject::AlignVCenter);
213 			break;
214 		case 2:
215 			text_object->setVerticalAlignment(TextObject::AlignTop);
216 			break;
217 		case 3:
218 			text_object->setVerticalAlignment(TextObject::AlignBottom);
219 			break;
220 		default:
221 			Q_UNREACHABLE();
222 		}
223 		auto h_align = (anchor - 1) % 3;
224 		switch (h_align)
225 		{
226 		case 0:
227 			text_object->setHorizontalAlignment(TextObject::AlignLeft);
228 			break;
229 		case 1:
230 			text_object->setHorizontalAlignment(TextObject::AlignHCenter);
231 			break;
232 		case 2:
233 			text_object->setHorizontalAlignment(TextObject::AlignRight);
234 			break;
235 		default:
236 			Q_UNREACHABLE();
237 		}
238 	}
239 
toPrettyWkt(OGRSpatialReferenceH spatial_reference)240 	QString toPrettyWkt(OGRSpatialReferenceH spatial_reference)
241 	{
242 		char* srs_wkt_raw = nullptr;
243 		OSRExportToPrettyWkt(spatial_reference, &srs_wkt_raw, 0);
244 		auto srs_wkt = QString::fromLocal8Bit(srs_wkt_raw);
245 		CPLFree(srs_wkt_raw);
246 		return srs_wkt;
247 	}
248 
249 
250 
toRgbString(const MapColor * color)251 	QByteArray toRgbString(const MapColor* color)
252 	{
253 		auto rgb = QColor(color->getRgb()).name(QColor::HexArgb).toLatin1();
254 		std::rotate(rgb.begin()+1, rgb.begin()+3, rgb.end()); // Move alpha to end
255 		return rgb;
256 	}
257 
makeStyleString(const PointSymbol * point_symbol)258 	QByteArray makeStyleString(const PointSymbol* point_symbol)
259 	{
260 		QByteArray style;
261 		if (auto main_color = point_symbol->guessDominantColor())
262 		{
263 			style.reserve(40);
264 			style += "SYMBOL(id:\"ogr-sym-0\"";
265 			style += ",c:" + toRgbString(main_color);
266 			style += ",l:" + QByteArray::number(-main_color->getPriority());
267 			style += ")";
268 		}
269 		return style;
270 	}
271 
makeStyleString(const LineSymbol * line_symbol)272 	QByteArray makeStyleString(const LineSymbol* line_symbol)
273 	{
274 		QByteArray style;
275 		style.reserve(200);
276 		auto main_color = line_symbol->getColor();
277 		if (main_color && line_symbol->getLineWidth())
278 		{
279 			style += "PEN(c:" + toRgbString(main_color);
280 			style += ",w:" + QByteArray::number(line_symbol->getLineWidth()/1000.0) + "mm";
281 			if (line_symbol->isDashed())
282 				style += ",p:\"2mm 1mm\""; // TODO
283 			style += ",l:" + QByteArray::number(-main_color->getPriority());
284 			style += ");";
285 		}
286 		if (line_symbol->hasBorder())
287 		{
288 			const auto& left_border = line_symbol->getBorder();
289 			if (left_border.isVisible())
290 			{
291 				// left border
292 				style += "PEN(c:" + toRgbString(left_border.color);
293 				style += ",w:" + QByteArray::number(left_border.width/1000.0) + "mm";
294 				style += ",dp:" + QByteArray::number(-left_border.shift/1000.0) + "mm";
295 				style += ",l:" + QByteArray::number(-left_border.color->getPriority());
296 				if (left_border.dashed)
297 					style += ",p:\"2mm 1mm\""; // TODO
298 				style += ");";
299 			}
300 			const auto& right_border = line_symbol->getBorder();
301 			if (right_border.isVisible())
302 			{
303 				// left border
304 				style += "PEN(c:" + toRgbString(right_border.color);
305 				style += ",w:" + QByteArray::number(right_border.width/1000.0) + "mm";
306 				style += ",dp:" + QByteArray::number(right_border.shift/1000.0) + "mm";
307 				style += ",l:" + QByteArray::number(-right_border.color->getPriority());
308 				if (right_border.dashed)
309 					style += ",p:\"2mm 1mm\""; // TODO
310 				style += ");";
311 			}
312 		}
313 		if (style.isEmpty())
314 		{
315 			if (auto main_color = line_symbol->guessDominantColor())
316 			{
317 				style += "PEN(c:" + toRgbString(main_color);
318 				style += ",w:1pt";
319 				style += ",l:" + QByteArray::number(-main_color->getPriority());
320 				style += ')';
321 			}
322 		}
323 		if (style.endsWith(';'))
324 		{
325 			style.chop(1);
326 		}
327 		return style;
328 	}
329 
makeStyleString(const AreaSymbol * area_symbol)330 	QByteArray makeStyleString(const AreaSymbol* area_symbol)
331 	{
332 		QByteArray style;
333 		style.reserve(200);
334 		if (auto color = area_symbol->getColor())
335 		{
336 			style += "BRUSH(fc:" + toRgbString(color);
337 			style += ",l:" + QByteArray::number(-color->getPriority());
338 			style += ");";
339 		}
340 
341 		auto num_fill_patterns = area_symbol->getNumFillPatterns();
342 		for (int i = 0; i < num_fill_patterns; ++i)
343 		{
344 			auto part = area_symbol->getFillPattern(i);
345 			switch (part.type)
346 			{
347 			case AreaSymbol::FillPattern::LinePattern:
348 				if (!part.line_color)
349 					continue;
350 				style += "BRUSH(fc:" + toRgbString(part.line_color);
351 				style += ",id:\"ogr-brush-2\"";  // parallel horizontal lines
352 				style += ",a:" + QByteArray::number(qRadiansToDegrees(part.angle));
353 				style += ",l:" + QByteArray::number(-part.line_color->getPriority());
354 				style += ");";
355 				break;
356 			case AreaSymbol::FillPattern::PointPattern:
357 				// TODO
358 				qWarning("Cannot handle point pattern in area symbol %s",
359 				         qPrintable(area_symbol->getName()));
360 			}
361 		}
362 		if (style.endsWith(';'))
363 			style.chop(1);
364 		return style;
365 	}
366 
makeStyleString(const TextSymbol * text_symbol)367 	QByteArray makeStyleString(const TextSymbol* text_symbol)
368 	{
369 		QByteArray style;
370 		style.reserve(200);
371 		style += "LABEL(c:" + toRgbString(text_symbol->getColor());
372 		style += ",f:\"" + text_symbol->getFontFamily().toUtf8() + "\"";
373 		style += ",s:"+QByteArray::number(text_symbol->getFontSize())+ "mm";
374 		style += ",t:\"{Name}\"";
375 		style += ')';
376 		return style;
377 	}
378 
379 
makeStyleString(const CombinedSymbol * combined_symbol)380 	QByteArray makeStyleString(const CombinedSymbol* combined_symbol)
381 	{
382 		QByteArray style;
383 		style.reserve(200);
384 		for (auto i = combined_symbol->getNumParts() - 1; i >= 0; i--)
385 		{
386 			const auto* subsymbol = combined_symbol->getPart(i);
387 			if (subsymbol)
388 			{
389 				switch (subsymbol->getType())
390 				{
391 				case Symbol::Line:
392 					style += makeStyleString(subsymbol->asLine()) + ';';
393 					break;
394 				case Symbol::Area:
395 					style += makeStyleString(subsymbol->asArea()) + ';';
396 					break;
397 				case Symbol::Combined:
398 					style += makeStyleString(subsymbol->asCombined()) + ';';
399 					break;
400 				case Symbol::Point:
401 				case Symbol::Text:
402 					qWarning("Cannot handle point or text symbol in combined symbol %s",
403 					         qPrintable(combined_symbol->getName()));
404 					break;
405 				case Symbol::NoSymbol:
406 				case Symbol::AllSymbols:
407 					Q_UNREACHABLE();
408 				}
409 			}
410 		}
411 		if (style.endsWith(';'))
412 			style.chop(1);
413 		return style;
414 	}
415 
416 
417 	class AverageCoords
418 	{
419 	private:
420 		double x = 0;
421 		double y = 0;
422 		unsigned num_coords = 0u;
423 
handleGeometry(OGRGeometryH geometry)424 		void handleGeometry(OGRGeometryH geometry)
425 		{
426 			auto const geometry_type = wkbFlatten(OGR_G_GetGeometryType(geometry));
427 			switch (geometry_type)
428 			{
429 			case OGRwkbGeometryType::wkbPoint:
430 			case OGRwkbGeometryType::wkbLineString:
431 				for (auto num_points = OGR_G_GetPointCount(geometry), i = 0; i < num_points; ++i)
432 				{
433 					x += OGR_G_GetX(geometry, i);
434 					y += OGR_G_GetY(geometry, i);
435 					++num_coords;
436 				}
437 				break;
438 
439 			case OGRwkbGeometryType::wkbPolygon:
440 			case OGRwkbGeometryType::wkbMultiPoint:
441 			case OGRwkbGeometryType::wkbMultiLineString:
442 			case OGRwkbGeometryType::wkbMultiPolygon:
443 			case OGRwkbGeometryType::wkbGeometryCollection:
444 				for (auto num_geometries = OGR_G_GetGeometryCount(geometry), i = 0; i < num_geometries; ++i)
445 				{
446 					handleGeometry(OGR_G_GetGeometryRef(geometry, i));
447 				}
448 				break;
449 
450 			default:
451 				;  // unsupported type, will be reported in importGeometry
452 			}
453 		}
454 
455 	public:
AverageCoords(OGRDataSourceH data_source,OGRDataSourceH srs)456 		AverageCoords(OGRDataSourceH data_source, OGRDataSourceH srs)
457 		{
458 			auto num_layers = OGR_DS_GetLayerCount(data_source);
459 			for (int i = 0; i < num_layers; ++i)
460 			{
461 				if (auto layer = OGR_DS_GetLayer(data_source, i))
462 				{
463 					auto spatial_reference = OGR_L_GetSpatialRef(layer);
464 					if (!spatial_reference)
465 						continue;
466 
467 					auto transformation = ogr::unique_transformation{ OCTNewCoordinateTransformation(spatial_reference, srs) };
468 					if (!transformation)
469 						continue;
470 
471 					OGR_L_ResetReading(layer);
472 					while (auto feature = ogr::unique_feature(OGR_L_GetNextFeature(layer)))
473 					{
474 						auto geometry = OGR_F_GetGeometryRef(feature.get());
475 						if (!geometry || OGR_G_IsEmpty(geometry))
476 							continue;
477 
478 						auto error = OGR_G_Transform(geometry, transformation.get());
479 						if (error)
480 							continue;
481 
482 						handleGeometry(geometry);
483 					}
484 				}
485 			}
486 		}
487 
operator QPointF() const488 		operator QPointF() const
489 		{
490 			return num_coords ? QPointF{ x / num_coords, y / num_coords } : QPointF{};
491 		}
492 
493 	};
494 
495 
496 }  // namespace
497 
498 
499 
500 // ### OgrFileImportFormat ###
501 
OgrFileImportFormat()502 OgrFileImportFormat::OgrFileImportFormat()
503  : FileFormat(OgrFile, "OGR",
504               ::OpenOrienteering::ImportExport::tr("Geospatial vector data"),
505               QString{},
506               Feature::FileOpen | Feature::FileImport | Feature::ReadingLossy )
507 {
508 	for (const auto& extension : GdalManager().supportedVectorImportExtensions())
509 		addExtension(QString::fromLatin1(extension));
510 }
511 
512 
makeImporter(const QString & path,Map * map,MapView * view) const513 std::unique_ptr<Importer> OgrFileImportFormat::makeImporter(const QString& path, Map* map, MapView* view) const
514 {
515 	return std::make_unique<OgrFileImport>(path, map, view);
516 }
517 
518 
519 // ### OgrFileExportFormat ###
520 
OgrFileExportFormat()521 OgrFileExportFormat::OgrFileExportFormat()
522  : FileFormat(OgrFile, "OGR-export",
523               ::OpenOrienteering::ImportExport::tr("Geospatial vector data"),
524               QString{},
525               Feature::FileExport | Feature::WritingLossy )
526 {
527 	for (const auto& extension : GdalManager().supportedVectorExportExtensions())
528 		addExtension(QString::fromLatin1(extension));
529 }
530 
makeExporter(const QString & path,const Map * map,const MapView * view) const531 std::unique_ptr<Exporter> OgrFileExportFormat::makeExporter(const QString& path, const Map* map, const MapView* view) const
532 {
533 	return std::make_unique<OgrFileExport>(path, map, view);
534 }
535 
536 
537 
538 // ### OgrFileImport ###
539 
OgrFileImport(const QString & path,Map * map,MapView * view,UnitType unit_type)540 OgrFileImport::OgrFileImport(const QString& path, Map* map, MapView* view, UnitType unit_type)
541  : Importer(path, map, view)
542  , manager{ OGR_SM_Create(nullptr) }
543  , unit_type{ unit_type }
544 {
545 	GdalManager().configure();
546 
547 	setOption(QLatin1String{ "Separate layers" }, QVariant{ false });
548 
549 	// OGR feature style defaults
550 	default_pen_color = new MapColor(QLatin1String{"Purple"}, 0);
551 	default_pen_color->setSpotColorName(QLatin1String{"PURPLE"});
552 	default_pen_color->setCmyk({0.35f, 0.85f, 0.0, 0.0});
553 	default_pen_color->setRgbFromCmyk();
554 	map->addColor(default_pen_color, 0);
555 
556 	// 50% opacity of 80% Purple should result in app. 40% Purple (on white) in
557 	// normal view and in an opaque Purple slightly lighter than lines and
558 	// points in overprinting simulation mode.
559 	auto default_brush_color = new MapColor(default_pen_color->getName() + QLatin1String(" 40%"), 0);
560 	default_brush_color->setSpotColorComposition({ {default_pen_color, 0.8f} });
561 	default_brush_color->setCmykFromSpotColors();
562 	default_brush_color->setRgbFromSpotColors();
563 	default_brush_color->setOpacity(0.5f);
564 	map->addColor(default_brush_color, 1);
565 
566 	default_point_symbol = new PointSymbol();
567 	default_point_symbol->setName(tr("Point"));
568 	default_point_symbol->setNumberComponent(0, 1);
569 	default_point_symbol->setInnerColor(default_pen_color);
570 	default_point_symbol->setInnerRadius(500); // (um)
571 	map->addSymbol(default_point_symbol, 0);
572 
573 	default_line_symbol = new LineSymbol();
574 	default_line_symbol->setName(tr("Line"));
575 	default_line_symbol->setNumberComponent(0, 2);
576 	default_line_symbol->setColor(default_pen_color);
577 	default_line_symbol->setLineWidth(0.1); // (0.1 mm, nearly cosmetic)
578 	default_line_symbol->setCapStyle(LineSymbol::FlatCap);
579 	default_line_symbol->setJoinStyle(LineSymbol::MiterJoin);
580 	map->addSymbol(default_line_symbol, 1);
581 
582 	default_area_symbol = new AreaSymbol();
583 	default_area_symbol->setName(tr("Area"));
584 	default_area_symbol->setNumberComponent(0, 3);
585 	default_area_symbol->setColor(default_brush_color);
586 	map->addSymbol(default_area_symbol, 2);
587 
588 	default_text_symbol = new TextSymbol();
589 	default_text_symbol->setName(tr("Text"));
590 	default_text_symbol->setNumberComponent(0, 4);
591 	default_text_symbol->setColor(default_pen_color);
592 	map->addSymbol(default_text_symbol, 3);
593 }
594 
595 
596 OgrFileImport::~OgrFileImport() = default;  // not inlined
597 
598 
599 
supportsQIODevice() const600 bool OgrFileImport::supportsQIODevice() const noexcept
601 {
602 	return false;
603 }
604 
605 
606 
setGeoreferencingImportEnabled(bool enabled)607 void OgrFileImport::setGeoreferencingImportEnabled(bool enabled)
608 {
609 	georeferencing_import_enabled = enabled;
610 }
611 
612 
613 
srsFromMap()614 ogr::unique_srs OgrFileImport::srsFromMap()
615 {
616 	auto srs = ogr::unique_srs(OSRNewSpatialReference(nullptr));
617 	auto& georef = map->getGeoreferencing();
618 	if (georef.isValid() && !georef.isLocal())
619 	{
620 		OSRSetProjCS(srs.get(), "Projected map SRS");
621 		OSRSetWellKnownGeogCS(srs.get(), "WGS84");
622 		auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
623 #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
624 		// Cf. https://github.com/OSGeo/PROJ/pull/1573
625 		spec.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb");
626 #endif
627 		auto error = OSRImportFromProj4(srs.get(), spec);
628 		if (!error)
629 			return srs;
630 
631 		addWarning(tr("Unable to setup \"%1\" SRS for GDAL: %2")
632 		           .arg(QString::fromLatin1(spec), QString::number(error)));
633 		srs.reset(OSRNewSpatialReference(nullptr));
634 	}
635 
636 	OSRSetLocalCS(srs.get(), "Local SRS");
637 	return srs;
638 }
639 
640 
641 
importImplementation()642 bool OgrFileImport::importImplementation()
643 {
644 	// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
645 	auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
646 	if (!data_source)
647 	{
648 		addWarning(Importer::tr("Cannot open file\n%1:\n%2").arg(path, QString::fromLatin1(CPLGetLastErrorMsg())));
649 		return false;
650 	}
651 
652 	if (auto driver = OGR_DS_GetDriver(data_source.get()))
653 	{
654 		if (auto driver_name = OGR_Dr_GetName(driver))
655 		{
656 			map->setSymbolSetId(QString::fromLatin1(driver_name));
657 		}
658 	}
659 
660 	empty_geometries = 0;
661 	no_transformation = 0;
662 	failed_transformation = 0;
663 	unsupported_geometry_type = 0;
664 	too_few_coordinates = 0;
665 
666 	if (georeferencing_import_enabled)
667 		map_srs = importGeoreferencing(data_source.get());
668 	else
669 		map_srs = srsFromMap();
670 
671 	importStyles(data_source.get());
672 
673 	if (!loadSymbolsOnly())
674 	{
675 		QScopedValueRollback<MapCoord::BoundsOffset> rollback { MapCoord::boundsOffset() };
676 		MapCoord::boundsOffset().reset(true);
677 
678 		auto num_layers = OGR_DS_GetLayerCount(data_source.get());
679 		for (int i = 0; i < num_layers; ++i)
680 		{
681 			auto layer = OGR_DS_GetLayer(data_source.get(), i);
682 			if (!layer)
683 			{
684 				addWarning(tr("Unable to load layer %1.").arg(i));
685 				continue;
686 			}
687 
688 			if (qstrcmp(OGR_L_GetName(layer), "track_points") == 0)
689 			{
690 				// Skip GPX track points as points. Track line is separate.
691 				/// \todo Use hooks and delegates per file format
692 				continue;
693 			}
694 
695 			auto part = map->getCurrentPart();
696 			if (option(QLatin1String("Separate layers")).toBool())
697 			{
698 				if (num_layers > 0)
699 				{
700 					if (part->getNumObjects() == 0)
701 					{
702 						part->setName(QString::fromUtf8(OGR_L_GetName(layer)));
703 					}
704 					else
705 					{
706 						part = new MapPart(QString::fromUtf8(OGR_L_GetName(layer)), map);
707 						auto index = std::size_t(map->getNumParts());
708 						map->addPart(part, index);
709 						map->setCurrentPartIndex(index);
710 					}
711 				}
712 			}
713 
714 			importLayer(part, layer);
715 		}
716 
717 		const auto& offset = MapCoord::boundsOffset();
718 		if (!offset.isZero())
719 		{
720 			// We need to adjust the georeferencing.
721 			auto offset_f  = MapCoordF { offset.x / 1000.0, offset.y / 1000.0 };
722 			auto georef = map->getGeoreferencing();
723 			auto ref_point = MapCoordF { georef.getMapRefPoint() };
724 			auto new_projected = georef.toProjectedCoords(ref_point + offset_f);
725 			georef.setProjectedRefPoint(new_projected, false);
726 			map->setGeoreferencing(georef);
727 		}
728 	}
729 
730 	if (empty_geometries)
731 	{
732 		addWarning(tr("Unable to load %n objects, reason: %1", nullptr, empty_geometries)
733 		           .arg(tr("Empty geometry.")));
734 	}
735 	if (no_transformation)
736 	{
737 		addWarning(tr("Unable to load %n objects, reason: %1", nullptr, no_transformation)
738 		           .arg(tr("Can't determine the coordinate transformation: %1").arg(QString::fromUtf8(CPLGetLastErrorMsg()))));
739 	}
740 	if (failed_transformation)
741 	{
742 		addWarning(tr("Unable to load %n objects, reason: %1", nullptr, failed_transformation)
743 		           .arg(tr("Failed to transform the coordinates.")));
744 	}
745 	if (unsupported_geometry_type)
746 	{
747 		addWarning(tr("Unable to load %n objects, reason: %1", nullptr, unsupported_geometry_type)
748 		           .arg(tr("Unknown or unsupported geometry type.")));
749 	}
750 	if (too_few_coordinates)
751 	{
752 		addWarning(tr("Unable to load %n objects, reason: %1", nullptr, too_few_coordinates)
753 		           .arg(tr("Not enough coordinates.")));
754 	}
755 
756 	return true;
757 }
758 
759 
760 
importGeoreferencing(OGRDataSourceH data_source)761 ogr::unique_srs OgrFileImport::importGeoreferencing(OGRDataSourceH data_source)
762 {
763 	auto no_srs = true;
764 	auto local_srs = ogr::unique_srs { nullptr };
765 	auto suitable_srs = ogr::unique_srs { nullptr };
766 	char* projected_srs_spec =  { nullptr };
767 
768 	auto orthographic = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
769 	OSRSetProjCS(orthographic.get(), "Orthographic SRS");
770 	OSRSetWellKnownGeogCS(orthographic.get(), "WGS84");
771 	OSRSetOrthographic(orthographic.get(), 0.0, 0.0, 0.0, 0.0);
772 
773 	// Find any SRS which can be transformed to our orthographic SRS,
774 	// but prefer a projected SRS.
775 	auto num_layers = OGR_DS_GetLayerCount(data_source);
776 	for (int i = 0; i < num_layers; ++i)
777 	{
778 		auto layer = OGR_DS_GetLayer(data_source, i);
779 		if (!layer)
780 		    continue;
781 
782 		auto spatial_reference = OGR_L_GetSpatialRef(layer);
783 		if (!spatial_reference)
784 			continue;
785 
786 		no_srs = false;
787 
788 		if (OSRIsLocal(spatial_reference))
789 		{
790 			if (!local_srs)
791 				local_srs.reset(OSRClone(spatial_reference));
792 			continue;
793 		}
794 
795 		auto transformation = OCTNewCoordinateTransformation(spatial_reference, orthographic.get());
796 		if (!transformation)
797 		{
798 			addWarning(tr("Cannot use this spatial reference:\n%1").arg(toPrettyWkt(spatial_reference)));
799 			continue;
800 		}
801 		OCTDestroyCoordinateTransformation(transformation);
802 
803 		if (OSRIsProjected(spatial_reference))
804 		{
805 			char *srs_spec = nullptr;
806 			auto error = OSRExportToProj4(spatial_reference, &srs_spec);
807 			if (!error)
808 			{
809 				projected_srs_spec = srs_spec;  // transfer ownership
810 				suitable_srs.reset(OSRClone(spatial_reference));
811 				break;
812 			}
813 			CPLFree(srs_spec);
814 		}
815 
816 		if (!suitable_srs)
817 			suitable_srs.reset(OSRClone(spatial_reference));
818 	}
819 
820 	if (projected_srs_spec)
821 	{
822 		// Found a suitable projected SRS
823 		auto center = calcAverageCoords(data_source, suitable_srs.get());
824 		auto georef = map->getGeoreferencing();  // copy
825 		georef.setProjectedCRS(QStringLiteral("PROJ.4"), QString::fromLatin1(projected_srs_spec));
826 		georef.setProjectedRefPoint({std::round(center.x()), std::round(center.y())});
827 		map->setGeoreferencing(georef);
828 		CPLFree(projected_srs_spec);
829 		return suitable_srs;
830 	}
831 
832 	if (suitable_srs)
833 	{
834 		// Found a suitable SRS but it is not projected.
835 		// Setting up a local orthographic projection.
836 		auto center = calcAverageLatLon(data_source);
837 		auto latitude = 0.001 * qRound(1000 * center.latitude());
838 		auto longitude = 0.001 * qRound(1000 * center.longitude());
839 		auto ortho_georef = Georeferencing();
840 		ortho_georef.setScaleDenominator(int(map->getScaleDenominator()));
841 		ortho_georef.setProjectedCRS(QString{},
842 		                             QString::fromLatin1("+proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs")
843 		                             .arg(latitude, 0, 'f')
844 		                             .arg(longitude, 0, 'f') );
845 		ortho_georef.setProjectedRefPoint({}, false);
846 		ortho_georef.setDeclination(map->getGeoreferencing().getDeclination());
847 		map->setGeoreferencing(ortho_georef);
848 		return srsFromMap();
849 	}
850 
851 	if (local_srs || no_srs)
852 	{
853 		auto georef = Georeferencing();
854 		georef.setScaleDenominator(int(map->getScaleDenominator()));
855 		georef.setDeclination(map->getGeoreferencing().getDeclination());
856 		map->setGeoreferencing(georef);
857 		return local_srs ? std::move(local_srs) : srsFromMap();
858 	}
859 
860 	throw FileFormatException(tr("The geospatial data has no suitable spatial reference."));
861 }
862 
863 
864 
importStyles(OGRDataSourceH data_source)865 void OgrFileImport::importStyles(OGRDataSourceH data_source)
866 {
867 	//auto style_table = OGR_DS_GetStyleTable(data_source);
868 	Q_UNUSED(data_source)
869 }
870 
importLayer(MapPart * map_part,OGRLayerH layer)871 void OgrFileImport::importLayer(MapPart* map_part, OGRLayerH layer)
872 {
873 	Q_ASSERT(map_part);
874 
875 	auto feature_definition = OGR_L_GetLayerDefn(layer);
876 
877 	OGR_L_ResetReading(layer);
878 	while (auto feature = ogr::unique_feature(OGR_L_GetNextFeature(layer)))
879 	{
880 		auto geometry = OGR_F_GetGeometryRef(feature.get());
881 		if (!geometry || OGR_G_IsEmpty(geometry))
882 		{
883 			++empty_geometries;
884 			continue;
885 		}
886 
887 		importFeature(map_part, feature_definition, feature.get(), geometry);
888 	}
889 }
890 
importFeature(MapPart * map_part,OGRFeatureDefnH feature_definition,OGRFeatureH feature,OGRGeometryH geometry)891 void OgrFileImport::importFeature(MapPart* map_part, OGRFeatureDefnH feature_definition, OGRFeatureH feature, OGRGeometryH geometry)
892 {
893 	to_map_coord = &OgrFileImport::fromProjected;
894 	auto new_srs = OGR_G_GetSpatialReference(geometry);
895 	if (new_srs && data_srs != new_srs)
896 	{
897 		// New SRS, indeed.
898 		auto transformation = ogr::unique_transformation{ OCTNewCoordinateTransformation(new_srs, map_srs.get()) };
899 		if (!transformation)
900 		{
901 			++no_transformation;
902 			return;
903 		}
904 
905 		// Commit change to data srs and coordinate transformation
906 		data_srs = new_srs;
907 		data_transform = std::move(transformation);
908 	}
909 
910 	if (new_srs)
911 	{
912 		auto error = OGR_G_Transform(geometry, data_transform.get());
913 		if (error)
914 		{
915 			++failed_transformation;
916 			return;
917 		}
918 	}
919 	else if (unit_type == UnitOnPaper)
920 	{
921 		to_map_coord = &OgrFileImport::fromDrawing;
922 	}
923 
924 	auto objects = importGeometry(feature, geometry);
925 	for (auto object : objects)
926 	{
927 		map_part->addObject(object);
928 		if (!feature_definition)
929 			continue;
930 
931 		auto num_fields = OGR_FD_GetFieldCount(feature_definition);
932 		for (int i = 0; i < num_fields; ++i)
933 		{
934 			auto value = OGR_F_GetFieldAsString(feature, i);
935 			if (value && qstrlen(value) > 0)
936 			{
937 				auto field_definition = OGR_FD_GetFieldDefn(feature_definition, i);
938 				object->setTag(QString::fromUtf8(OGR_Fld_GetNameRef(field_definition)), QString::fromUtf8(value));
939 			}
940 		}
941 	}
942 }
943 
importGeometry(OGRFeatureH feature,OGRGeometryH geometry)944 OgrFileImport::ObjectList OgrFileImport::importGeometry(OGRFeatureH feature, OGRGeometryH geometry)
945 {
946 	ObjectList result;
947 	auto geometry_type = wkbFlatten(OGR_G_GetGeometryType(geometry));
948 	switch (geometry_type)
949 	{
950 	case OGRwkbGeometryType::wkbPoint:
951 		if (auto object = importPointGeometry(feature, geometry))
952 			result = { object };
953 		break;
954 
955 	case OGRwkbGeometryType::wkbLineString:
956 		if (auto object = importLineStringGeometry(feature, geometry))
957 			result = { object };
958 		break;
959 
960 	case OGRwkbGeometryType::wkbPolygon:
961 		if (auto object = importPolygonGeometry(feature, geometry))
962 			result = { object };
963 		break;
964 
965 	case OGRwkbGeometryType::wkbGeometryCollection:
966 	case OGRwkbGeometryType::wkbMultiLineString:
967 	case OGRwkbGeometryType::wkbMultiPoint:
968 	case OGRwkbGeometryType::wkbMultiPolygon:
969 		result = importGeometryCollection(feature, geometry);
970 		break;
971 
972 	default:
973 		qDebug("OgrFileImport: Unknown or unsupported geometry type: %d", geometry_type);
974 		++unsupported_geometry_type;
975 	}
976 	return result;
977 }
978 
importGeometryCollection(OGRFeatureH feature,OGRGeometryH geometry)979 OgrFileImport::ObjectList OgrFileImport::importGeometryCollection(OGRFeatureH feature, OGRGeometryH geometry)
980 {
981 	ObjectList result;
982 	auto num_geometries = OGR_G_GetGeometryCount(geometry);
983 	result.reserve(std::size_t(num_geometries));
984 	for (int i = 0; i < num_geometries; ++i)
985 	{
986 		auto tmp = importGeometry(feature, OGR_G_GetGeometryRef(geometry, i));
987 		result.insert(result.end(), begin(tmp), end(tmp));
988 	}
989 	return result;
990 }
991 
importPointGeometry(OGRFeatureH feature,OGRGeometryH geometry)992 Object* OgrFileImport::importPointGeometry(OGRFeatureH feature, OGRGeometryH geometry)
993 {
994 	auto style = OGR_F_GetStyleString(feature);
995 	auto symbol = getSymbol(Symbol::Point, style);
996 	if (symbol->getType() == Symbol::Point)
997 	{
998 		auto object = new PointObject(symbol);
999 		object->setPosition(toMapCoord(OGR_G_GetX(geometry, 0), OGR_G_GetY(geometry, 0)));
1000 		return object;
1001 	}
1002 
1003 	if (symbol->getType() == Symbol::Text)
1004 	{
1005 		const auto& description = symbol->getDescription();
1006 		auto length = description.length();
1007 		auto split = description.indexOf(QLatin1Char(' '));
1008 		Q_ASSERT(split > 0);
1009 		Q_ASSERT(split < length);
1010 
1011 		auto label = description.right(length - split - 1);
1012 		if (label.startsWith(QLatin1Char{'{'}) && label.endsWith(QLatin1Char{'}'}))
1013 		{
1014 			label.remove(0, 1);
1015 			label.chop(1);
1016 			int index = OGR_F_GetFieldIndex(feature, label.toLatin1().constData());
1017 			if (index >= 0)
1018 			{
1019 				label = QString::fromUtf8(OGR_F_GetFieldAsString(feature, index));
1020 			}
1021 		}
1022 		if (!label.isEmpty())
1023 		{
1024 			auto object = new TextObject(symbol);
1025 			object->setAnchorPosition(toMapCoord(OGR_G_GetX(geometry, 0), OGR_G_GetY(geometry, 0)));
1026 			// DXF observation
1027 			label.replace(QRegularExpression(QString::fromLatin1("(\\\\[^;]*;)*"), QRegularExpression::MultilineOption), QString{});
1028 			label.replace(QLatin1String("^I"), QLatin1String("\t"));
1029 			object->setText(label);
1030 
1031 			bool ok;
1032 			auto anchor = QStringRef(&description, 1, 2).toInt(&ok);
1033 			if (ok)
1034 			{
1035 				applyLabelAnchor(anchor, object);
1036 			}
1037 
1038 			auto angle = QStringRef(&description, 3, split-3).toDouble(&ok);
1039 			if (ok)
1040 			{
1041 				object->setRotation(qDegreesToRadians(angle));
1042 			}
1043 
1044 			return object;
1045 		}
1046 	}
1047 
1048 	return nullptr;
1049 }
1050 
importLineStringGeometry(OGRFeatureH feature,OGRGeometryH geometry)1051 PathObject* OgrFileImport::importLineStringGeometry(OGRFeatureH feature, OGRGeometryH geometry)
1052 {
1053 	auto managed_geometry = ogr::unique_geometry(nullptr);
1054 	if (OGR_G_GetGeometryType(geometry) != wkbLineString)
1055 	{
1056 		geometry = OGR_G_ForceToLineString(OGR_G_Clone(geometry));
1057 		managed_geometry.reset(geometry);
1058 	}
1059 
1060 	auto num_points = OGR_G_GetPointCount(geometry);
1061 	if (num_points < 2)
1062 	{
1063 		++too_few_coordinates;
1064 		return nullptr;
1065 	}
1066 
1067 	auto style = OGR_F_GetStyleString(feature);
1068 	auto object = new PathObject(getSymbol(Symbol::Line, style));
1069 	for (int i = 0; i < num_points; ++i)
1070 	{
1071 		object->addCoordinate(toMapCoord(OGR_G_GetX(geometry, i), OGR_G_GetY(geometry, i)));
1072 	}
1073 	return object;
1074 }
1075 
importPolygonGeometry(OGRFeatureH feature,OGRGeometryH geometry)1076 PathObject* OgrFileImport::importPolygonGeometry(OGRFeatureH feature, OGRGeometryH geometry)
1077 {
1078 	auto num_geometries = OGR_G_GetGeometryCount(geometry);
1079 	if (num_geometries < 1)
1080 	{
1081 		++too_few_coordinates;
1082 		return nullptr;
1083 	}
1084 
1085 	auto outline = OGR_G_GetGeometryRef(geometry, 0);
1086 	auto managed_outline = ogr::unique_geometry(nullptr);
1087 	if (OGR_G_GetGeometryType(outline) != wkbLineString)
1088 	{
1089 		outline = OGR_G_ForceToLineString(OGR_G_Clone(outline));
1090 		managed_outline.reset(outline);
1091 	}
1092 	auto num_points = OGR_G_GetPointCount(outline);
1093 	if (num_points < 3)
1094 	{
1095 		++too_few_coordinates;
1096 		return nullptr;
1097 	}
1098 
1099 	auto style = OGR_F_GetStyleString(feature);
1100 	auto object = new PathObject(getSymbol(Symbol::Area, style));
1101 	for (int i = 0; i < num_points; ++i)
1102 	{
1103 		object->addCoordinate(toMapCoord(OGR_G_GetX(outline, i), OGR_G_GetY(outline, i)));
1104 	}
1105 
1106 	for (int g = 1; g < num_geometries; ++g)
1107 	{
1108 		bool start_new_part = true;
1109 		auto hole = /*OGR_G_ForceToLineString*/(OGR_G_GetGeometryRef(geometry, g));
1110 		auto num_points = OGR_G_GetPointCount(hole);
1111 		for (int i = 0; i < num_points; ++i)
1112 		{
1113 			object->addCoordinate(toMapCoord(OGR_G_GetX(hole, i), OGR_G_GetY(hole, i)), start_new_part);
1114 			start_new_part = false;
1115 		}
1116 	}
1117 
1118 	object->closeAllParts();
1119 	return object;
1120 }
1121 
getSymbol(Symbol::Type type,const char * raw_style_string)1122 Symbol* OgrFileImport::getSymbol(Symbol::Type type, const char* raw_style_string)
1123 {
1124 	auto style_string = QByteArray::fromRawData(raw_style_string, int(qstrlen(raw_style_string)));
1125 	Symbol* symbol = nullptr;
1126 	switch (type)
1127 	{
1128 	case Symbol::Point:
1129 	case Symbol::Text:
1130 		symbol = point_symbols.value(style_string);
1131 		if (!symbol)
1132 			symbol = getSymbolForPointGeometry(style_string);
1133 		if (!symbol)
1134 			symbol = default_point_symbol;
1135 		break;
1136 
1137 	case Symbol::Combined:
1138 		/// \todo
1139 		Q_FALLTHROUGH();
1140 	case Symbol::Line:
1141 		symbol = line_symbols.value(style_string);
1142 		if (!symbol)
1143 			symbol = getLineSymbol(style_string);
1144 		if (!symbol)
1145 			symbol = default_line_symbol;
1146 		break;
1147 
1148 	case Symbol::Area:
1149 		symbol = area_symbols.value(style_string);
1150 		if (!symbol)
1151 			symbol = getAreaSymbol(style_string);
1152 		if (!symbol)
1153 			symbol = default_area_symbol;
1154 		break;
1155 
1156 	case Symbol::NoSymbol:
1157 	case Symbol::AllSymbols:
1158 		Q_UNREACHABLE();
1159 	}
1160 
1161 	Q_ASSERT(symbol);
1162 	return symbol;
1163 }
1164 
makeColor(OGRStyleToolH tool,const char * color_string)1165 MapColor* OgrFileImport::makeColor(OGRStyleToolH tool, const char* color_string)
1166 {
1167 	auto key = QByteArray::fromRawData(color_string, int(qstrlen(color_string)));
1168 	auto color = colors.value(key);
1169 	if (!color)
1170 	{
1171 		int r, g, b, a;
1172 		auto success = OGR_ST_GetRGBFromString(tool, color_string, &r, &g, &b, &a);
1173 		if (!success)
1174 		{
1175 			color = default_pen_color;
1176 		}
1177 		else if (a > 0)
1178 		{
1179 			color = new MapColor(QString::fromUtf8(color_string), map->getNumColors());
1180 			color->setRgb(QColor{ r, g, b });
1181 			color->setCmykFromRgb();
1182 			map->addColor(color, map->getNumColors());
1183 		}
1184 
1185 		key.detach();
1186 		colors.insert(key, color);
1187 	}
1188 
1189 	return color;
1190 }
1191 
applyPenColor(OGRStyleToolH tool,LineSymbol * line_symbol)1192 void OgrFileImport::applyPenColor(OGRStyleToolH tool, LineSymbol* line_symbol)
1193 {
1194 	int is_null;
1195 	auto color_string = OGR_ST_GetParamStr(tool, OGRSTPenColor, &is_null);
1196 	if (!is_null)
1197 	{
1198 		auto color = makeColor(tool, color_string);
1199 		if (color)
1200 			line_symbol->setColor(color);
1201 		else
1202 			line_symbol->setHidden(true);
1203 	}
1204 }
1205 
applyBrushColor(OGRStyleToolH tool,AreaSymbol * area_symbol)1206 void OgrFileImport::applyBrushColor(OGRStyleToolH tool, AreaSymbol* area_symbol)
1207 {
1208 	int is_null;
1209 	auto color_string = OGR_ST_GetParamStr(tool, OGRSTBrushFColor, &is_null);
1210 	if (!is_null)
1211 	{
1212 		auto color = makeColor(tool, color_string);
1213 		if (color)
1214 			area_symbol->setColor(color);
1215 		else
1216 			area_symbol->setHidden(true);
1217 	}
1218 }
1219 
1220 
getSymbolForPointGeometry(const QByteArray & style_string)1221 Symbol* OgrFileImport::getSymbolForPointGeometry(const QByteArray& style_string)
1222 {
1223 	if (style_string.isEmpty())
1224 		return nullptr;
1225 
1226 	auto manager = this->manager.get();
1227 
1228 	auto data = style_string.constData();
1229 	if (!OGR_SM_InitStyleString(manager, data))
1230 		return nullptr;
1231 
1232 	auto num_parts = OGR_SM_GetPartCount(manager, data);
1233 	if (!num_parts)
1234 		return nullptr;
1235 
1236 	Symbol* symbol = nullptr;
1237 	for (int i = 0; !symbol && i < num_parts; ++i)
1238 	{
1239 		auto tool = OGR_SM_GetPart(manager, i, nullptr);
1240 		if (!tool)
1241 			continue;
1242 
1243 		OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
1244 
1245 		auto type = OGR_ST_GetType(tool);
1246 		switch (type)
1247 		{
1248 		case OGRSTCBrush:
1249 		case OGRSTCPen:
1250 		case OGRSTCSymbol:
1251 			symbol = getSymbolForOgrSymbol(tool, style_string);
1252 			break;
1253 
1254 		case OGRSTCLabel:
1255 			symbol = getSymbolForLabel(tool, style_string);
1256 			break;
1257 
1258 		default:
1259 			;
1260 		}
1261 
1262 		OGR_ST_Destroy(tool);
1263 	}
1264 
1265 	return symbol;
1266 }
1267 
getLineSymbol(const QByteArray & style_string)1268 LineSymbol* OgrFileImport::getLineSymbol(const QByteArray& style_string)
1269 {
1270 	if (style_string.isEmpty())
1271 		return nullptr;
1272 
1273 	auto manager = this->manager.get();
1274 
1275 	auto data = style_string.constData();
1276 	if (!OGR_SM_InitStyleString(manager, data))
1277 		return nullptr;
1278 
1279 	auto num_parts = OGR_SM_GetPartCount(manager, data);
1280 	if (!num_parts)
1281 		return nullptr;
1282 
1283 	LineSymbol* symbol = nullptr;
1284 	for (int i = 0; !symbol && i < num_parts; ++i)
1285 	{
1286 		auto tool = OGR_SM_GetPart(manager, i, nullptr);
1287 		if (!tool)
1288 			continue;
1289 
1290 		OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
1291 
1292 		auto type = OGR_ST_GetType(tool);
1293 		switch (type)
1294 		{
1295 		case OGRSTCPen:
1296 			symbol = getSymbolForPen(tool, style_string);
1297 			break;
1298 
1299 		default:
1300 			;
1301 		}
1302 
1303 		OGR_ST_Destroy(tool);
1304 	}
1305 
1306 	return symbol;
1307 }
1308 
getAreaSymbol(const QByteArray & style_string)1309 AreaSymbol* OgrFileImport::getAreaSymbol(const QByteArray& style_string)
1310 {
1311 	if (style_string.isEmpty())
1312 		return nullptr;
1313 
1314 	auto manager = this->manager.get();
1315 
1316 	auto data = style_string.constData();
1317 	if (!OGR_SM_InitStyleString(manager, data))
1318 		return nullptr;
1319 
1320 	auto num_parts = OGR_SM_GetPartCount(manager, data);
1321 	if (!num_parts)
1322 		return nullptr;
1323 
1324 	AreaSymbol* symbol = nullptr;
1325 	for (int i = 0; !symbol && i < num_parts; ++i)
1326 	{
1327 		auto tool = OGR_SM_GetPart(manager, i, nullptr);
1328 		if (!tool)
1329 			continue;
1330 
1331 		OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
1332 
1333 		auto type = OGR_ST_GetType(tool);
1334 		switch (type)
1335 		{
1336 		case OGRSTCBrush:
1337 			symbol = getSymbolForBrush(tool, style_string);
1338 			break;
1339 
1340 		default:
1341 			;
1342 		}
1343 
1344 		OGR_ST_Destroy(tool);
1345 	}
1346 
1347 	return symbol;
1348 }
1349 
getSymbolForOgrSymbol(OGRStyleToolH tool,const QByteArray & style_string)1350 PointSymbol* OgrFileImport::getSymbolForOgrSymbol(OGRStyleToolH tool, const QByteArray& style_string)
1351 {
1352 	auto raw_tool_key = OGR_ST_GetStyleString(tool);
1353 	auto tool_key = QByteArray::fromRawData(raw_tool_key, qstrlen(raw_tool_key));
1354 	auto symbol = point_symbols.value(tool_key);
1355 	if (symbol && symbol->getType() == Symbol::Point)
1356 		return static_cast<PointSymbol*>(symbol);
1357 
1358 	int color_key;
1359 	switch (OGR_ST_GetType(tool))
1360 	{
1361 	case OGRSTCBrush:
1362 		color_key = OGRSTBrushFColor;
1363 		break;
1364 	case OGRSTCPen:
1365 		color_key = OGRSTPenColor;
1366 		break;
1367 	case OGRSTCSymbol:
1368 		color_key = OGRSTSymbolColor;
1369 		break;
1370 	default:
1371 		return nullptr;
1372 	}
1373 
1374 	int is_null;
1375 	auto color_string = OGR_ST_GetParamStr(tool, color_key, &is_null);
1376 	if (is_null)
1377 		return nullptr;
1378 
1379 	auto point_symbol = duplicate<PointSymbol>(*default_point_symbol);
1380 	auto color = makeColor(tool, color_string);
1381 	if (color)
1382 		point_symbol->setInnerColor(color);
1383 	else
1384 		point_symbol->setHidden(true);
1385 
1386 	auto key = style_string;
1387 	key.detach();
1388 	point_symbols.insert(key, point_symbol.get());
1389 
1390 	if (key != tool_key)
1391 	{
1392 		tool_key.detach();
1393 		point_symbols.insert(tool_key, point_symbol.get());
1394 	}
1395 
1396 	auto ret = point_symbol.get();
1397 	map->addSymbol(point_symbol.release(), map->getNumSymbols());
1398 	return ret;
1399 }
1400 
getSymbolForLabel(OGRStyleToolH tool,const QByteArray &)1401 TextSymbol* OgrFileImport::getSymbolForLabel(OGRStyleToolH tool, const QByteArray& /*style_string*/)
1402 {
1403 	Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCLabel);
1404 
1405 	int is_null;
1406 	auto label_string = OGR_ST_GetParamStr(tool, OGRSTLabelTextString, &is_null);
1407 	if (is_null)
1408 		return nullptr;
1409 
1410 	auto color_string = OGR_ST_GetParamStr(tool, OGRSTLabelFColor, &is_null);
1411 	auto font_size_string = OGR_ST_GetParamStr(tool, OGRSTLabelSize, &is_null);
1412 
1413 	// Don't use the style string as a key: The style contains the label.
1414 	QByteArray key;
1415 	key.reserve(int(qstrlen(color_string) + qstrlen(font_size_string) + 1));
1416 	key.append(color_string);
1417 	key.append(font_size_string);
1418 	auto text_symbol = static_cast<TextSymbol*>(text_symbols.value(key));
1419 	if (!text_symbol)
1420 	{
1421 		auto copy = duplicate<TextSymbol>(*default_text_symbol);
1422 		text_symbol = copy.get();
1423 
1424 		auto color = makeColor(tool, color_string);
1425 		if (color)
1426 			text_symbol->setColor(color);
1427 		else
1428 			text_symbol->setHidden(true);
1429 
1430 		auto font_size = OGR_ST_GetParamDbl(tool, OGRSTLabelSize, &is_null);
1431 		if (!is_null && font_size > 0.0)
1432 			text_symbol->scale(font_size / text_symbol->getFontSize());
1433 
1434 		key.detach();
1435 		text_symbols.insert(key, text_symbol);
1436 
1437 		map->addSymbol(copy.release(), map->getNumSymbols());
1438 	}
1439 
1440 	auto anchor = qBound(1, OGR_ST_GetParamNum(tool, OGRSTLabelAnchor, &is_null), 12);
1441 	if (is_null)
1442 		anchor = 1;
1443 
1444 	auto angle = OGR_ST_GetParamDbl(tool, OGRSTLabelAngle, &is_null);
1445 	if (is_null)
1446 		angle = 0.0;
1447 
1448 	QString description;
1449 	description.reserve(int(qstrlen(label_string) + 100));
1450 	description.append(QString::number(100 + anchor));
1451 	description.append(QString::number(angle, 'g', 1));
1452 	description.append(QLatin1Char(' '));
1453 	description.append(QString::fromUtf8(label_string));
1454 	text_symbol->setDescription(description);
1455 
1456 	return text_symbol;
1457 }
1458 
getSymbolForPen(OGRStyleToolH tool,const QByteArray & style_string)1459 LineSymbol* OgrFileImport::getSymbolForPen(OGRStyleToolH tool, const QByteArray& style_string)
1460 {
1461 	Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCPen);
1462 
1463 	auto raw_tool_key = OGR_ST_GetStyleString(tool);
1464 	auto tool_key = QByteArray::fromRawData(raw_tool_key, int(qstrlen(raw_tool_key)));
1465 	auto symbol = line_symbols.value(tool_key);
1466 	if (symbol && symbol->getType() == Symbol::Line)
1467 		return static_cast<LineSymbol*>(symbol);
1468 
1469 	auto line_symbol = duplicate<LineSymbol>(*default_line_symbol);
1470 	applyPenColor(tool, line_symbol.get());
1471 	applyPenWidth(tool, line_symbol.get());
1472 	applyPenCap(tool, line_symbol.get());
1473 	applyPenJoin(tool, line_symbol.get());
1474 	applyPenPattern(tool, line_symbol.get());
1475 
1476 	auto key = style_string;
1477 	key.detach();
1478 	line_symbols.insert(key, line_symbol.get());
1479 
1480 	if (key != tool_key)
1481 	{
1482 		tool_key.detach();
1483 		line_symbols.insert(tool_key, line_symbol.get());
1484 	}
1485 
1486 	auto ret = line_symbol.get();
1487 	map->addSymbol(line_symbol.release(), map->getNumSymbols());
1488 	return ret;
1489 }
1490 
getSymbolForBrush(OGRStyleToolH tool,const QByteArray & style_string)1491 AreaSymbol* OgrFileImport::getSymbolForBrush(OGRStyleToolH tool, const QByteArray& style_string)
1492 {
1493 	Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCBrush);
1494 
1495 	auto raw_tool_key = OGR_ST_GetStyleString(tool);
1496 	auto tool_key = QByteArray::fromRawData(raw_tool_key, int(qstrlen(raw_tool_key)));
1497 	auto symbol = area_symbols.value(tool_key);
1498 	if (symbol && symbol->getType() == Symbol::Area)
1499 		return static_cast<AreaSymbol*>(symbol);
1500 
1501 	auto area_symbol = duplicate<AreaSymbol>(*default_area_symbol);
1502 	applyBrushColor(tool, area_symbol.get());
1503 
1504 	auto key = style_string;
1505 	key.detach();
1506 	area_symbols.insert(key, area_symbol.get());
1507 
1508 	if (key != tool_key)
1509 	{
1510 		tool_key.detach();
1511 		area_symbols.insert(tool_key, area_symbol.get());
1512 	}
1513 
1514 	auto s = area_symbol.get();
1515 	map->addSymbol(area_symbol.release(), map->getNumSymbols());
1516 	return s;
1517 }
1518 
1519 
fromDrawing(double x,double y) const1520 MapCoord OgrFileImport::fromDrawing(double x, double y) const
1521 {
1522 	return MapCoord::load(x, -y, MapCoord::Flags{});
1523 }
1524 
fromProjected(double x,double y) const1525 MapCoord OgrFileImport::fromProjected(double x, double y) const
1526 {
1527 	return MapCoord::load(map->getGeoreferencing().toMapCoordF(QPointF{ x, y }), MapCoord::Flags{});
1528 }
1529 
1530 
1531 // static
checkGeoreferencing(const QString & path,const Georeferencing & georef)1532 bool OgrFileImport::checkGeoreferencing(const QString& path, const Georeferencing& georef)
1533 {
1534 	if (georef.isLocal() || !georef.isValid())
1535 		return false;
1536 
1537 	GdalManager();
1538 
1539 	// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
1540 	auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
1541 	if (!data_source)
1542 	{
1543 		throw FileFormatException(QString::fromLatin1(CPLGetLastErrorMsg()));
1544 	}
1545 
1546 	return checkGeoreferencing(data_source.get(), georef);
1547 }
1548 
1549 
1550 // static
checkGeoreferencing(OGRDataSourceH data_source,const Georeferencing & georef)1551 bool OgrFileImport::checkGeoreferencing(OGRDataSourceH data_source, const Georeferencing& georef)
1552 {
1553 	auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
1554 #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
1555 	// Cf. https://github.com/OSGeo/PROJ/pull/1573
1556 	spec.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb");
1557 #endif
1558 	auto map_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
1559 	OSRSetProjCS(map_srs.get(), "Projected map SRS");
1560 	OSRSetWellKnownGeogCS(map_srs.get(), "WGS84");
1561 	OSRImportFromProj4(map_srs.get(), spec.constData());
1562 
1563 	bool suitable_srs_found = false;
1564 	auto num_layers = OGR_DS_GetLayerCount(data_source);
1565 	for (int i = 0; i < num_layers; ++i)
1566 	{
1567 		if (auto layer = OGR_DS_GetLayer(data_source, i))
1568 		{
1569 			if (auto spatial_reference = OGR_L_GetSpatialRef(layer))
1570 			{
1571 				auto transformation = OCTNewCoordinateTransformation(spatial_reference, map_srs.get());
1572 				if (!transformation)
1573 				{
1574 					qDebug("Failed to transform this SRS:\n%s", qPrintable(toPrettyWkt(spatial_reference)));
1575 					return false;
1576 				}
1577 
1578 				OCTDestroyCoordinateTransformation(transformation);
1579 				suitable_srs_found = true;
1580 			}
1581 		}
1582 	}
1583 
1584 	return suitable_srs_found;
1585 }
1586 
1587 
1588 
1589 // static
calcAverageLatLon(const QString & path)1590 LatLon OgrFileImport::calcAverageLatLon(const QString& path)
1591 {
1592 	GdalManager();
1593 
1594 	// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
1595 	auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
1596 	if (!data_source)
1597 	{
1598 		throw FileFormatException(QString::fromLatin1(CPLGetLastErrorMsg()));
1599 	}
1600 
1601 	return calcAverageLatLon(data_source.get());
1602 }
1603 
1604 
1605 // static
calcAverageLatLon(OGRDataSourceH data_source)1606 LatLon OgrFileImport::calcAverageLatLon(OGRDataSourceH data_source)
1607 {
1608 	auto geo_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
1609 	OSRSetWellKnownGeogCS(geo_srs.get(), "WGS84");
1610 #if GDAL_VERSION_MAJOR >= 3
1611 	OSRSetAxisMappingStrategy(geo_srs.get(), OAMS_TRADITIONAL_GIS_ORDER);
1612 #endif
1613 
1614 	auto const average = calcAverageCoords(data_source, geo_srs.get());
1615 	return {average.y(), average.x()};
1616 }
1617 
1618 
1619 // static
calcAverageCoords(OGRDataSourceH data_source,OGRDataSourceH srs)1620 QPointF OgrFileImport::calcAverageCoords(OGRDataSourceH data_source, OGRDataSourceH srs)
1621 {
1622 	return QPointF{AverageCoords(data_source, srs)};
1623 }
1624 
1625 
1626 // ### OgrFileExport ###
1627 
OgrFileExport(const QString & path,const Map * map,const MapView * view)1628 OgrFileExport::OgrFileExport(const QString& path, const Map* map, const MapView* view)
1629 : Exporter(path, map, view)
1630 {
1631 	GdalManager manager;
1632 	bool one_layer_per_symbol = manager.isExportOptionEnabled(GdalManager::OneLayerPerSymbol);
1633 	setOption(QString::fromLatin1("Per Symbol Layers"), one_layer_per_symbol);
1634 }
1635 
1636 OgrFileExport::~OgrFileExport() = default;
1637 
supportsQIODevice() const1638 bool OgrFileExport::supportsQIODevice() const noexcept
1639 {
1640 	return false;
1641 }
1642 
exportImplementation()1643 bool OgrFileExport::exportImplementation()
1644 {
1645 	// Choose driver and setup format-specific features
1646 	QFileInfo info(path);
1647 	QString file_extn = info.completeSuffix();
1648 	GDALDriverH po_driver = nullptr;
1649 
1650 	auto count = GDALGetDriverCount();
1651 	for (auto i = 0; i < count; ++i)
1652 	{
1653 		auto driver_data = GDALGetDriver(i);
1654 
1655 		auto type = GDALGetMetadataItem(driver_data, GDAL_DCAP_VECTOR, nullptr);
1656 		if (qstrcmp(type, "YES") != 0)
1657 			continue;
1658 
1659 		auto cap_create = GDALGetMetadataItem(driver_data, GDAL_DCAP_CREATE, nullptr);
1660 		if (qstrcmp(cap_create, "YES") != 0)
1661 			continue;
1662 
1663 		auto extensions_raw = GDALGetMetadataItem(driver_data, GDAL_DMD_EXTENSIONS, nullptr);
1664 		auto extensions = QByteArray::fromRawData(extensions_raw, int(qstrlen(extensions_raw)));
1665 		for (auto pos = 0; pos >= 0; )
1666 		{
1667 			auto start = pos ? pos + 1 : 0;
1668 			pos = extensions.indexOf(' ', start);
1669 			auto extension = extensions.mid(start, pos - start);
1670 			if (file_extn == QString::fromLatin1(extension))
1671 			{
1672 				po_driver = driver_data;
1673 				break;
1674 			}
1675 		}
1676 	}
1677 
1678 	if (!po_driver)
1679 		throw FileFormatException(tr("Couldn't find a driver for file extension %1").arg(file_extn));
1680 
1681 	setupQuirks(po_driver);
1682 
1683 	setupGeoreferencing(po_driver);
1684 
1685 	// Create output dataset
1686 	po_ds = ogr::unique_datasource(OGR_Dr_CreateDataSource(
1687 	                                   po_driver,
1688 	                                   path.toLatin1(),
1689 	                                   nullptr));
1690 	if (!po_ds)
1691 		throw FileFormatException(tr("Failed to create dataset: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
1692 
1693 	// Name field definition
1694 	if (quirks.testFlag(UseLayerField))
1695 	{
1696 		symbol_field = "Layer";
1697 		o_name_field = nullptr;
1698 	}
1699 	else
1700 	{
1701 		symbol_field = "Name";
1702 		o_name_field = ogr::unique_fielddefn(OGR_Fld_Create(symbol_field, OFTString));
1703 		OGR_Fld_SetWidth(o_name_field.get(), 32);
1704 	}
1705 
1706 	auto symbols = symbolsForExport();
1707 
1708 	// Setup style table
1709 	populateStyleTable(symbols);
1710 
1711 	auto is_point_object = [](const Object* object) {
1712 		const auto* symbol = object->getSymbol();
1713 		return symbol && symbol->getContainedTypes() & Symbol::Point;
1714 	};
1715 
1716 	auto is_text_object = [](const Object* object) {
1717 		const auto* symbol = object->getSymbol();
1718 		return symbol && symbol->getContainedTypes() & Symbol::Text;
1719 	};
1720 
1721 	auto is_line_object = [](const Object* object) {
1722 		const auto* symbol = object->getSymbol();
1723 		return symbol && (symbol->getType() == Symbol::Line
1724 		                  || (symbol->getType() == Symbol::Combined && !(symbol->getContainedTypes() & Symbol::Area)));
1725 	};
1726 
1727 	auto is_area_object = [](const Object* object) {
1728 		const auto* symbol = object->getSymbol();
1729 		return symbol && symbol->getContainedTypes() & Symbol::Area;
1730 	};
1731 
1732 	if (quirks & SingleLayer)
1733 	{
1734 		auto layer = createLayer("Layer", wkbUnknown);
1735 		if (layer == nullptr)
1736 			throw FileFormatException(tr("Failed to create layer: %2").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
1737 
1738 		for (auto symbol : symbols)
1739 		{
1740 			auto match_symbol = [symbol](auto object) { return object->getSymbol() == symbol; };
1741 			switch (symbol->getType())
1742 			{
1743 			case Symbol::Point:
1744 				addPointsToLayer(layer, match_symbol);
1745 				break;
1746 			case Symbol::Text:
1747 				addTextToLayer(layer, match_symbol);
1748 				break;
1749 			case Symbol::Line:
1750 				addLinesToLayer(layer, match_symbol);
1751 				break;
1752 			case Symbol::Combined:
1753 				if (!(symbol->getContainedTypes() & Symbol::Area))
1754 				{
1755 					addLinesToLayer(layer, match_symbol);
1756 					break;
1757 				}
1758 				Q_FALLTHROUGH();
1759 			case Symbol::Area:
1760 				addAreasToLayer(layer, match_symbol);
1761 				break;
1762 			case Symbol::NoSymbol:
1763 			case Symbol::AllSymbols:
1764 				Q_UNREACHABLE();
1765 			}
1766 		}
1767 	}
1768 	else if (option(QString::fromLatin1("Per Symbol Layers")).toBool())
1769 	{
1770 		// Add points, lines, areas in this order for driver compatibility (esp GPX)
1771 		for (auto symbol : symbols)
1772 		{
1773 			if (symbol->getType() == Symbol::Point)
1774 			{
1775 				auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPoint);
1776 				if (layer != nullptr)
1777 					addPointsToLayer(layer, [&symbol](const Object* object) {
1778 						const auto* sym = object->getSymbol();
1779 						return sym == symbol;
1780 					});
1781 			}
1782 			else if (symbol->getType() == Symbol::Text)
1783 			{
1784 				auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPoint);
1785 				if (layer != nullptr)
1786 					addTextToLayer(layer, [&symbol](const Object* object) {
1787 						const auto* sym = object->getSymbol();
1788 						return sym == symbol;
1789 					});
1790 			}
1791 		}
1792 
1793 		// Line symbols
1794 		for (auto symbol : symbols)
1795 		{
1796 			if (symbol->getType() == Symbol::Line
1797 			    || (symbol->getType() == Symbol::Combined && !(symbol->getContainedTypes() & Symbol::Area)))
1798 			{
1799 				auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbLineString);
1800 				if (layer != nullptr)
1801 					addLinesToLayer(layer, [&symbol](const Object* object) {
1802 						const auto* sym = object->getSymbol();
1803 						return sym == symbol;
1804 					});
1805 			}
1806 		}
1807 
1808 		// Area symbols
1809 		for (auto symbol : symbols)
1810 		{
1811 			if (symbol->getContainedTypes() & Symbol::Area)
1812 			{
1813 				auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPolygon);
1814 				if (layer != nullptr)
1815 					addAreasToLayer(layer, [&symbol](const Object* object) {
1816 						const auto* sym = object->getSymbol();
1817 						return sym == symbol;
1818 					});
1819 			}
1820 		}
1821 	}
1822 	else
1823 	{
1824 		// Add points, lines, areas in this order for driver compatibility (esp GPX)
1825 		auto point_layer = createLayer(QString::fromLatin1("%1_points").arg(info.baseName()).toLatin1(), wkbPoint);
1826 		if (point_layer != nullptr)
1827 		{
1828 			addPointsToLayer(point_layer, is_point_object);
1829 			addTextToLayer(point_layer, is_text_object);
1830 		}
1831 
1832 		auto line_layer = createLayer(QString::fromLatin1("%1_lines").arg(info.baseName()).toLatin1(), wkbLineString);
1833 		if (line_layer != nullptr)
1834 			addLinesToLayer(line_layer, is_line_object);
1835 
1836 		auto area_layer = createLayer(QString::fromLatin1("%1_areas").arg(info.baseName()).toLatin1(), wkbPolygon);
1837 		if (area_layer != nullptr)
1838 			addAreasToLayer(area_layer, is_area_object);
1839 	}
1840 
1841 	return true;
1842 }
1843 
1844 
symbolsForExport() const1845 std::vector<const Symbol*> OgrFileExport::symbolsForExport() const
1846 {
1847 	std::vector<bool> symbols_in_use;
1848 	map->determineSymbolsInUse(symbols_in_use);
1849 
1850 	const auto num_symbols = map->getNumSymbols();
1851 	std::vector<const Symbol*> symbols;
1852 	symbols.reserve(std::size_t(num_symbols));
1853 	for (auto i = 0; i < num_symbols; ++i)
1854 	{
1855 		auto symbol = map->getSymbol(i);
1856 		if (symbols_in_use[std::size_t(i)]
1857 		    && !symbol->isHidden()
1858 		    && !symbol->isHelperSymbol())
1859 			symbols.push_back(symbol);
1860 	}
1861 	std::sort(begin(symbols), end(symbols), Symbol::lessByColorPriority);
1862 	return symbols;
1863 }
1864 
1865 
setupGeoreferencing(GDALDriverH po_driver)1866 void OgrFileExport::setupGeoreferencing(GDALDriverH po_driver)
1867 {
1868 	// Check if map is georeferenced
1869 	const auto& georef = map->getGeoreferencing();
1870 	bool local_only = false;
1871 	if (georef.getState() == Georeferencing::Local)
1872 	{
1873 		local_only = true;
1874 		addWarning(tr("The map is not georeferenced. Local georeferencing only."));
1875 	}
1876 
1877 	// Make sure GDAL can work with the georeferencing info
1878 	map_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
1879 	if (!local_only)
1880 	{
1881 		OSRSetProjCS(map_srs.get(), "Projected map SRS");
1882 		OSRSetWellKnownGeogCS(map_srs.get(), "WGS84");
1883 		auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
1884 		if (OSRImportFromProj4(map_srs.get(), spec) != OGRERR_NONE)
1885 		{
1886 			local_only = true;
1887 			addWarning(tr("Failed to properly export the georeferencing info. Local georeferencing only."));
1888 		}
1889 	}
1890 
1891 	/**
1892 	 * Only certain drivers work without georeferencing info.
1893 	 * GeorefOptional based on http://www.gdal.org/ogr_formats.html as of March 5, 2018
1894 	 */
1895 	if (local_only && !quirks.testFlag(GeorefOptional))
1896 	{
1897 		throw FileFormatException(tr("The %1 driver requires valid georefencing info.")
1898 		                          .arg(QString::fromLatin1(GDALGetDriverShortName(po_driver))));
1899 	}
1900 
1901 	if (quirks & NeedsWgs84)
1902 	{
1903 		// Formats with NeedsWgs84 quirk need coords in EPSG:4326/WGS 1984
1904 		auto geo_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
1905 		OSRSetWellKnownGeogCS(geo_srs.get(), "WGS84");
1906 #if GDAL_VERSION_MAJOR >= 3
1907 		OSRSetAxisMappingStrategy(geo_srs.get(), OAMS_TRADITIONAL_GIS_ORDER);
1908 #endif
1909 		transformation = ogr::unique_transformation { OCTNewCoordinateTransformation(map_srs.get(), geo_srs.get()) };
1910 	}
1911 }
1912 
setupQuirks(GDALDriverH po_driver)1913 void OgrFileExport::setupQuirks(GDALDriverH po_driver)
1914 {
1915 	static struct {
1916 		const char* name;
1917 		OgrQuirks quirks;
1918 	} driver_quirks[] = {
1919 	    { "ARCGEN",        GeorefOptional },
1920 	    { "BNA",           GeorefOptional },
1921 	    { "CSV",           GeorefOptional },
1922 	    { "DGN",           GeorefOptional },
1923 	    { "DGNv8",         GeorefOptional },
1924 	    { "DWG",           GeorefOptional },
1925 	    { "DXF",           OgrQuirks() | GeorefOptional | SingleLayer | UseLayerField },
1926 	    { "Geomedia",      GeorefOptional },
1927 	    { "GPX",           NeedsWgs84 },
1928 	    { "INGRES",        GeorefOptional },
1929 	    { "LIBKML",        NeedsWgs84 },
1930 	    { "ODS",           GeorefOptional },
1931 	    { "OpenJUMP .jml", GeorefOptional },
1932 	    { "REC",           GeorefOptional },
1933 	    { "SEGY",          GeorefOptional },
1934 	    { "XLS",           GeorefOptional },
1935 	    { "XLSX",          GeorefOptional },
1936 	};
1937 	using std::begin;
1938 	using std::end;
1939 	auto driver_name = GDALGetDriverShortName(po_driver);
1940 	auto driver_info = std::find_if(begin(driver_quirks), end(driver_quirks), [driver_name](auto entry) {
1941 		return qstrcmp(driver_name, entry.name) == 0;
1942 	});
1943 	if (driver_info != end(driver_quirks))
1944 		quirks |= driver_info->quirks;
1945 }
1946 
addPointsToLayer(OGRLayerH layer,const std::function<bool (const Object *)> & condition)1947 void OgrFileExport::addPointsToLayer(OGRLayerH layer, const std::function<bool (const Object*)>& condition)
1948 {
1949 	const auto& georef = map->getGeoreferencing();
1950 
1951 	auto add_feature = [&](const Object* object) {
1952 		auto symbol = object->getSymbol();
1953 		auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
1954 
1955 		QString sym_name = symbol->getPlainTextName();
1956 		sym_name.truncate(32);
1957 		OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
1958 
1959 		auto pt = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPoint));
1960 		QPointF proj_cord = georef.toProjectedCoords(object->asPoint()->getCoordF());
1961 
1962 		OGR_G_SetPoint_2D(pt.get(), 0, proj_cord.x(), proj_cord.y());
1963 
1964 		if (quirks & NeedsWgs84)
1965 			OGR_G_Transform(pt.get(), transformation.get());
1966 
1967 		OGR_F_SetGeometry(po_feature.get(), pt.get());
1968 
1969 		OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
1970 
1971 		if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
1972 			throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
1973 	};
1974 
1975 	map->applyOnMatchingObjects(add_feature, condition);
1976 }
1977 
addTextToLayer(OGRLayerH layer,const std::function<bool (const Object *)> & condition)1978 void OgrFileExport::addTextToLayer(OGRLayerH layer, const std::function<bool (const Object*)>& condition)
1979 {
1980 	const auto& georef = map->getGeoreferencing();
1981 
1982 	auto add_feature = [&](const Object* object) {
1983 		auto symbol = object->getSymbol();
1984 		auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
1985 
1986 		QString sym_name = symbol->getPlainTextName();
1987 		sym_name.truncate(32);
1988 		OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
1989 
1990 		auto text = object->asText()->getText();
1991 		if (o_name_field)
1992 		{
1993 			// Use the name field for the text (useful e.g. for KML).
1994 			// This may overwrite the symbol name, and
1995 			// it may be too short for the full text.
1996 			auto index = OGR_F_GetFieldIndex(po_feature.get(), OGR_Fld_GetNameRef(o_name_field.get()));
1997 			OGR_F_SetFieldString(po_feature.get(), index, text.leftRef(32).toUtf8().constData());
1998 		}
1999 
2000 		auto pt = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPoint));
2001 		QPointF proj_cord = georef.toProjectedCoords(object->asText()->getAnchorCoordF());
2002 
2003 		OGR_G_SetPoint_2D(pt.get(), 0, proj_cord.x(), proj_cord.y());
2004 
2005 		if (quirks & NeedsWgs84)
2006 			OGR_G_Transform(pt.get(), transformation.get());
2007 
2008 		OGR_F_SetGeometry(po_feature.get(), pt.get());
2009 
2010 		QByteArray style = OGR_STBL_Find(table.get(), symbolId(symbol));
2011 		if (!o_name_field || text.length() > 32)
2012 		{
2013 			// There is no label field, or the text is too long.
2014 			// Put the text directly in the label.
2015 			text.replace(QRegularExpression(QLatin1String(R"((["\\]))"), QRegularExpression::MultilineOption), QLatin1String("\\\\1"));
2016 			style.replace("{Name}", text.toUtf8());
2017 		}
2018 		OGR_F_SetStyleString(po_feature.get(), style);
2019 
2020 		if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
2021 			throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
2022 	};
2023 
2024 	map->applyOnMatchingObjects(add_feature, condition);
2025 }
2026 
2027 void OgrFileExport::addLinesToLayer(OGRLayerH layer, const std::function<bool (const Object*)>& condition)
2028 {
2029 	const auto& georef = map->getGeoreferencing();
2030 
2031 	auto add_feature = [&](const Object* object) {
2032 		const auto* symbol = object->getSymbol();
2033 		const auto* path = object->asPath();
2034 		if (path->parts().empty())
2035 			return;
2036 
2037 		QString sym_name = symbol->getPlainTextName();
2038 		sym_name.truncate(32);
2039 
2040 		auto line_string = ogr::unique_geometry(OGR_G_CreateGeometry(wkbLineString));
2041 		const auto& parts = path->parts();
2042 		for (const auto& part : parts)
2043 		{
2044 			auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
2045 			OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
2046 
2047 			for (const auto& coord : part.path_coords)
2048 			{
2049 				QPointF proj_cord = georef.toProjectedCoords(coord.pos);
2050 				OGR_G_AddPoint_2D(line_string.get(), proj_cord.x(), proj_cord.y());
2051 			}
2052 
2053 			if (quirks & NeedsWgs84)
2054 				OGR_G_Transform(line_string.get(), transformation.get());
2055 
2056 			OGR_F_SetGeometry(po_feature.get(), line_string.get());
2057 
2058 			OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
2059 
2060 			if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
2061 				throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
2062 		}
2063 	};
2064 
2065 	map->applyOnMatchingObjects(add_feature, condition);
2066 }
2067 
2068 void OgrFileExport::addAreasToLayer(OGRLayerH layer, const std::function<bool (const Object*)>& condition)
2069 {
2070 	const auto& georef = map->getGeoreferencing();
2071 
2072 	auto add_feature = [&](const Object* object) {
2073 		const auto* symbol = object->getSymbol();
2074 		const auto* path = object->asPath();
2075 		if (path->parts().empty())
2076 			return;
2077 
2078 		auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
2079 
2080 		QString sym_name = symbol->getPlainTextName();
2081 		sym_name.truncate(32);
2082 		OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
2083 
2084 		auto polygon = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPolygon));
2085 		auto cur_ring = ogr::unique_geometry(OGR_G_CreateGeometry(wkbLinearRing));
2086 
2087 		const auto& parts = path->parts();
2088 		for (const auto& part : parts)
2089 		{
2090 			for (const auto& coord : part.path_coords)
2091 			{
2092 				QPointF proj_cord = georef.toProjectedCoords(coord.pos);
2093 				OGR_G_AddPoint_2D(cur_ring.get(), proj_cord.x(), proj_cord.y());
2094 			}
2095 			OGR_G_CloseRings(cur_ring.get());
2096 			if (quirks & NeedsWgs84)
2097 				OGR_G_Transform(cur_ring.get(), transformation.get());
2098 			OGR_G_AddGeometry(polygon.get(), cur_ring.get());
2099 			cur_ring.reset(OGR_G_CreateGeometry(wkbLinearRing));
2100 		}
2101 
2102 		OGR_F_SetGeometry(po_feature.get(), polygon.get());
2103 
2104 		OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
2105 
2106 		if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
2107 			throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
2108 	};
2109 
2110 	map->applyOnMatchingObjects(add_feature, condition);
2111 }
2112 
2113 OGRLayerH OgrFileExport::createLayer(const char* layer_name, OGRwkbGeometryType type)
2114 {
2115 	auto po_layer = GDALDatasetCreateLayer(po_ds.get(), layer_name, map_srs.get(), type, nullptr);
2116 	if (!po_layer) {
2117 		addWarning(tr("Failed to create layer %1: %2").arg(QString::fromUtf8(layer_name), QString::fromLatin1(CPLGetLastErrorMsg())));
2118 		return nullptr;
2119 	}
2120 
2121 	if (!quirks.testFlag(UseLayerField)
2122 	    && OGR_L_CreateField(po_layer, o_name_field.get(), 1) != OGRERR_NONE)
2123 	{
2124 		addWarning(tr("Failed to create name field: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
2125 	}
2126 
2127 	return po_layer;
2128 }
2129 
2130 void OgrFileExport::populateStyleTable(const std::vector<const Symbol*>& symbols)
2131 {
2132 	table = ogr::unique_styletable(OGR_STBL_Create());
2133 	auto manager = ogr::unique_stylemanager(OGR_SM_Create(table.get()));
2134 
2135 	// Go through all used symbols and create a style table
2136 	for (auto symbol : symbols)
2137 	{
2138 		QByteArray style_string;
2139 		switch (symbol->getType())
2140 		{
2141 		case Symbol::Text:
2142 			style_string = makeStyleString(symbol->asText());
2143 			break;
2144 		case Symbol::Point:
2145 			style_string = makeStyleString(symbol->asPoint());
2146 			break;
2147 		case Symbol::Line:
2148 			style_string = makeStyleString(symbol->asLine());
2149 			break;
2150 		case Symbol::Area:
2151 			style_string = makeStyleString(symbol->asArea());
2152 			break;
2153 		case Symbol::Combined:
2154 			style_string = makeStyleString(symbol->asCombined());
2155 			break;
2156 		case Symbol::NoSymbol:
2157 		case Symbol::AllSymbols:
2158 			Q_UNREACHABLE();
2159 		}
2160 
2161 #ifdef MAPPER_DEVELOPMENT_BUILD
2162 		if (qEnvironmentVariableIsSet("MAPPER_DEBUG_OGR"))
2163 			qDebug("%s:\t \"%s\"", qPrintable(symbol->getPlainTextName()), style_string.constData());
2164 #endif
2165 		OGR_SM_AddStyle(manager.get(), symbolId(symbol), style_string);
2166 	}
2167 }
2168 
2169 }  // namespace OpenOrienteering
2170