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