/*
* Copyright 2016-2019 Kai Pastor
*
* This file is part of OpenOrienteering.
*
* OpenOrienteering is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenOrienteering is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenOrienteering. If not, see .
*/
#include "ogr_file_format.h"
#include "ogr_file_format_p.h" // IWYU pragma: associated
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// IWYU pragma: no_include
// IWYU pragma: no_include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core/georeferencing.h"
#include "core/latlon.h"
#include "core/map.h"
#include "core/map_color.h"
#include "core/map_coord.h"
#include "core/map_part.h"
#include "core/path_coord.h"
#include "core/virtual_path.h"
#include "core/objects/object.h"
#include "core/objects/text_object.h"
#include "core/symbols/area_symbol.h"
#include "core/symbols/combined_symbol.h"
#include "core/symbols/line_symbol.h"
#include "core/symbols/point_symbol.h"
#include "core/symbols/symbol.h"
#include "core/symbols/text_symbol.h"
#include "fileformats/file_import_export.h"
#include "gdal/gdal_manager.h"
// IWYU pragma: no_forward_declare QFile
namespace OpenOrienteering {
namespace {
void applyPenWidth(OGRStyleToolH tool, LineSymbol* line_symbol)
{
int is_null;
auto pen_width = OGR_ST_GetParamDbl(tool, OGRSTPenWidth, &is_null);
if (!is_null)
{
Q_ASSERT(OGR_ST_GetUnit(tool) == OGRSTUMM);
if (pen_width <= 0.01)
pen_width = 0.1;
line_symbol->setLineWidth(pen_width);
}
}
void applyPenCap(OGRStyleToolH tool, LineSymbol* line_symbol)
{
int is_null;
auto pen_cap = OGR_ST_GetParamStr(tool, OGRSTPenCap, &is_null);
if (!is_null)
{
switch (pen_cap[0])
{
case 'p':
line_symbol->setCapStyle(LineSymbol::SquareCap);
break;
case 'r':
line_symbol->setCapStyle(LineSymbol::RoundCap);
break;
default:
;
}
}
}
void applyPenJoin(OGRStyleToolH tool, LineSymbol* line_symbol)
{
int is_null;
auto pen_join = OGR_ST_GetParamStr(tool, OGRSTPenJoin, &is_null);
if (!is_null)
{
switch (pen_join[0])
{
case 'b':
line_symbol->setJoinStyle(LineSymbol::BevelJoin);
break;
case 'r':
line_symbol->setJoinStyle(LineSymbol::RoundJoin);
break;
default:
;
}
}
}
void applyPenPattern(OGRStyleToolH tool, LineSymbol* line_symbol)
{
int is_null;
auto raw_pattern = OGR_ST_GetParamStr(tool, OGRSTPenPattern, &is_null);
if (!is_null)
{
auto pattern = QString::fromLatin1(raw_pattern);
auto sub_pattern_re = QRegularExpression(QString::fromLatin1("([0-9.]+)([a-z]*) *([0-9.]+)([a-z]*)"));
auto match = sub_pattern_re.match(pattern);
double length_0{}, length_1{};
bool ok = match.hasMatch();
if (ok)
length_0 = match.capturedRef(1).toDouble(&ok);
if (ok)
length_1 = match.capturedRef(3).toDouble(&ok);
if (ok)
{
/// \todo Apply units from capture 2 and 4
line_symbol->setDashed(true);
line_symbol->setDashLength(qMax(100, qRound(length_0 * 1000)));
line_symbol->setBreakLength(qMax(100, qRound(length_1 * 1000)));
}
else
{
qDebug("OgrFileImportFormat: Failed to parse dash pattern '%s'", raw_pattern);
}
}
}
#if 0
int getFontSize(const char* font_size_string)
{
auto pattern = QString::fromLatin1(font_size_string);
auto sub_pattern_re = QRegularExpression(QString::fromLatin1("([0-9.]+)([a-z]*)"));
auto match = sub_pattern_re.match(pattern);
double font_size;
bool ok = match.hasMatch();
if (ok)
font_size = match.capturedRef(1).toDouble(&ok);
if (ok)
{
auto unit = match.capturedRef(2).toUtf8();
if (!unit.isEmpty())
{
if (unit == "pt")
{
}
else if (unit == "px")
{
}
else
{
qDebug("OgrFileImportFormat: Unsupported font size unit '%s'", unit.constData());
}
}
}
else
{
qDebug("OgrFileImportFormat: Failed to parse font size '%s'", font_size_string);
font_size = 0;
}
return font_size;
}
#endif
void applyLabelAnchor(int anchor, TextObject* text_object)
{
auto v_align = (anchor - 1) / 3;
switch (v_align)
{
case 0:
text_object->setVerticalAlignment(TextObject::AlignBaseline);
break;
case 1:
text_object->setVerticalAlignment(TextObject::AlignVCenter);
break;
case 2:
text_object->setVerticalAlignment(TextObject::AlignTop);
break;
case 3:
text_object->setVerticalAlignment(TextObject::AlignBottom);
break;
default:
Q_UNREACHABLE();
}
auto h_align = (anchor - 1) % 3;
switch (h_align)
{
case 0:
text_object->setHorizontalAlignment(TextObject::AlignLeft);
break;
case 1:
text_object->setHorizontalAlignment(TextObject::AlignHCenter);
break;
case 2:
text_object->setHorizontalAlignment(TextObject::AlignRight);
break;
default:
Q_UNREACHABLE();
}
}
QString toPrettyWkt(OGRSpatialReferenceH spatial_reference)
{
char* srs_wkt_raw = nullptr;
OSRExportToPrettyWkt(spatial_reference, &srs_wkt_raw, 0);
auto srs_wkt = QString::fromLocal8Bit(srs_wkt_raw);
CPLFree(srs_wkt_raw);
return srs_wkt;
}
QByteArray toRgbString(const MapColor* color)
{
auto rgb = QColor(color->getRgb()).name(QColor::HexArgb).toLatin1();
std::rotate(rgb.begin()+1, rgb.begin()+3, rgb.end()); // Move alpha to end
return rgb;
}
QByteArray makeStyleString(const PointSymbol* point_symbol)
{
QByteArray style;
if (auto main_color = point_symbol->guessDominantColor())
{
style.reserve(40);
style += "SYMBOL(id:\"ogr-sym-0\"";
style += ",c:" + toRgbString(main_color);
style += ",l:" + QByteArray::number(-main_color->getPriority());
style += ")";
}
return style;
}
QByteArray makeStyleString(const LineSymbol* line_symbol)
{
QByteArray style;
style.reserve(200);
auto main_color = line_symbol->getColor();
if (main_color && line_symbol->getLineWidth())
{
style += "PEN(c:" + toRgbString(main_color);
style += ",w:" + QByteArray::number(line_symbol->getLineWidth()/1000.0) + "mm";
if (line_symbol->isDashed())
style += ",p:\"2mm 1mm\""; // TODO
style += ",l:" + QByteArray::number(-main_color->getPriority());
style += ");";
}
if (line_symbol->hasBorder())
{
const auto& left_border = line_symbol->getBorder();
if (left_border.isVisible())
{
// left border
style += "PEN(c:" + toRgbString(left_border.color);
style += ",w:" + QByteArray::number(left_border.width/1000.0) + "mm";
style += ",dp:" + QByteArray::number(-left_border.shift/1000.0) + "mm";
style += ",l:" + QByteArray::number(-left_border.color->getPriority());
if (left_border.dashed)
style += ",p:\"2mm 1mm\""; // TODO
style += ");";
}
const auto& right_border = line_symbol->getBorder();
if (right_border.isVisible())
{
// left border
style += "PEN(c:" + toRgbString(right_border.color);
style += ",w:" + QByteArray::number(right_border.width/1000.0) + "mm";
style += ",dp:" + QByteArray::number(right_border.shift/1000.0) + "mm";
style += ",l:" + QByteArray::number(-right_border.color->getPriority());
if (right_border.dashed)
style += ",p:\"2mm 1mm\""; // TODO
style += ");";
}
}
if (style.isEmpty())
{
if (auto main_color = line_symbol->guessDominantColor())
{
style += "PEN(c:" + toRgbString(main_color);
style += ",w:1pt";
style += ",l:" + QByteArray::number(-main_color->getPriority());
style += ')';
}
}
if (style.endsWith(';'))
{
style.chop(1);
}
return style;
}
QByteArray makeStyleString(const AreaSymbol* area_symbol)
{
QByteArray style;
style.reserve(200);
if (auto color = area_symbol->getColor())
{
style += "BRUSH(fc:" + toRgbString(color);
style += ",l:" + QByteArray::number(-color->getPriority());
style += ");";
}
auto num_fill_patterns = area_symbol->getNumFillPatterns();
for (int i = 0; i < num_fill_patterns; ++i)
{
auto part = area_symbol->getFillPattern(i);
switch (part.type)
{
case AreaSymbol::FillPattern::LinePattern:
if (!part.line_color)
continue;
style += "BRUSH(fc:" + toRgbString(part.line_color);
style += ",id:\"ogr-brush-2\""; // parallel horizontal lines
style += ",a:" + QByteArray::number(qRadiansToDegrees(part.angle));
style += ",l:" + QByteArray::number(-part.line_color->getPriority());
style += ");";
break;
case AreaSymbol::FillPattern::PointPattern:
// TODO
qWarning("Cannot handle point pattern in area symbol %s",
qPrintable(area_symbol->getName()));
}
}
if (style.endsWith(';'))
style.chop(1);
return style;
}
QByteArray makeStyleString(const TextSymbol* text_symbol)
{
QByteArray style;
style.reserve(200);
style += "LABEL(c:" + toRgbString(text_symbol->getColor());
style += ",f:\"" + text_symbol->getFontFamily().toUtf8() + "\"";
style += ",s:"+QByteArray::number(text_symbol->getFontSize())+ "mm";
style += ",t:\"{Name}\"";
style += ')';
return style;
}
QByteArray makeStyleString(const CombinedSymbol* combined_symbol)
{
QByteArray style;
style.reserve(200);
for (auto i = combined_symbol->getNumParts() - 1; i >= 0; i--)
{
const auto* subsymbol = combined_symbol->getPart(i);
if (subsymbol)
{
switch (subsymbol->getType())
{
case Symbol::Line:
style += makeStyleString(subsymbol->asLine()) + ';';
break;
case Symbol::Area:
style += makeStyleString(subsymbol->asArea()) + ';';
break;
case Symbol::Combined:
style += makeStyleString(subsymbol->asCombined()) + ';';
break;
case Symbol::Point:
case Symbol::Text:
qWarning("Cannot handle point or text symbol in combined symbol %s",
qPrintable(combined_symbol->getName()));
break;
case Symbol::NoSymbol:
case Symbol::AllSymbols:
Q_UNREACHABLE();
}
}
}
if (style.endsWith(';'))
style.chop(1);
return style;
}
class AverageCoords
{
private:
double x = 0;
double y = 0;
unsigned num_coords = 0u;
void handleGeometry(OGRGeometryH geometry)
{
auto const geometry_type = wkbFlatten(OGR_G_GetGeometryType(geometry));
switch (geometry_type)
{
case OGRwkbGeometryType::wkbPoint:
case OGRwkbGeometryType::wkbLineString:
for (auto num_points = OGR_G_GetPointCount(geometry), i = 0; i < num_points; ++i)
{
x += OGR_G_GetX(geometry, i);
y += OGR_G_GetY(geometry, i);
++num_coords;
}
break;
case OGRwkbGeometryType::wkbPolygon:
case OGRwkbGeometryType::wkbMultiPoint:
case OGRwkbGeometryType::wkbMultiLineString:
case OGRwkbGeometryType::wkbMultiPolygon:
case OGRwkbGeometryType::wkbGeometryCollection:
for (auto num_geometries = OGR_G_GetGeometryCount(geometry), i = 0; i < num_geometries; ++i)
{
handleGeometry(OGR_G_GetGeometryRef(geometry, i));
}
break;
default:
; // unsupported type, will be reported in importGeometry
}
}
public:
AverageCoords(OGRDataSourceH data_source, OGRDataSourceH srs)
{
auto num_layers = OGR_DS_GetLayerCount(data_source);
for (int i = 0; i < num_layers; ++i)
{
if (auto layer = OGR_DS_GetLayer(data_source, i))
{
auto spatial_reference = OGR_L_GetSpatialRef(layer);
if (!spatial_reference)
continue;
auto transformation = ogr::unique_transformation{ OCTNewCoordinateTransformation(spatial_reference, srs) };
if (!transformation)
continue;
OGR_L_ResetReading(layer);
while (auto feature = ogr::unique_feature(OGR_L_GetNextFeature(layer)))
{
auto geometry = OGR_F_GetGeometryRef(feature.get());
if (!geometry || OGR_G_IsEmpty(geometry))
continue;
auto error = OGR_G_Transform(geometry, transformation.get());
if (error)
continue;
handleGeometry(geometry);
}
}
}
}
operator QPointF() const
{
return num_coords ? QPointF{ x / num_coords, y / num_coords } : QPointF{};
}
};
} // namespace
// ### OgrFileImportFormat ###
OgrFileImportFormat::OgrFileImportFormat()
: FileFormat(OgrFile, "OGR",
::OpenOrienteering::ImportExport::tr("Geospatial vector data"),
QString{},
Feature::FileOpen | Feature::FileImport | Feature::ReadingLossy )
{
for (const auto& extension : GdalManager().supportedVectorImportExtensions())
addExtension(QString::fromLatin1(extension));
}
std::unique_ptr OgrFileImportFormat::makeImporter(const QString& path, Map* map, MapView* view) const
{
return std::make_unique(path, map, view);
}
// ### OgrFileExportFormat ###
OgrFileExportFormat::OgrFileExportFormat()
: FileFormat(OgrFile, "OGR-export",
::OpenOrienteering::ImportExport::tr("Geospatial vector data"),
QString{},
Feature::FileExport | Feature::WritingLossy )
{
for (const auto& extension : GdalManager().supportedVectorExportExtensions())
addExtension(QString::fromLatin1(extension));
}
std::unique_ptr OgrFileExportFormat::makeExporter(const QString& path, const Map* map, const MapView* view) const
{
return std::make_unique(path, map, view);
}
// ### OgrFileImport ###
OgrFileImport::OgrFileImport(const QString& path, Map* map, MapView* view, UnitType unit_type)
: Importer(path, map, view)
, manager{ OGR_SM_Create(nullptr) }
, unit_type{ unit_type }
{
GdalManager().configure();
setOption(QLatin1String{ "Separate layers" }, QVariant{ false });
// OGR feature style defaults
default_pen_color = new MapColor(QLatin1String{"Purple"}, 0);
default_pen_color->setSpotColorName(QLatin1String{"PURPLE"});
default_pen_color->setCmyk({0.35f, 0.85f, 0.0, 0.0});
default_pen_color->setRgbFromCmyk();
map->addColor(default_pen_color, 0);
// 50% opacity of 80% Purple should result in app. 40% Purple (on white) in
// normal view and in an opaque Purple slightly lighter than lines and
// points in overprinting simulation mode.
auto default_brush_color = new MapColor(default_pen_color->getName() + QLatin1String(" 40%"), 0);
default_brush_color->setSpotColorComposition({ {default_pen_color, 0.8f} });
default_brush_color->setCmykFromSpotColors();
default_brush_color->setRgbFromSpotColors();
default_brush_color->setOpacity(0.5f);
map->addColor(default_brush_color, 1);
default_point_symbol = new PointSymbol();
default_point_symbol->setName(tr("Point"));
default_point_symbol->setNumberComponent(0, 1);
default_point_symbol->setInnerColor(default_pen_color);
default_point_symbol->setInnerRadius(500); // (um)
map->addSymbol(default_point_symbol, 0);
default_line_symbol = new LineSymbol();
default_line_symbol->setName(tr("Line"));
default_line_symbol->setNumberComponent(0, 2);
default_line_symbol->setColor(default_pen_color);
default_line_symbol->setLineWidth(0.1); // (0.1 mm, nearly cosmetic)
default_line_symbol->setCapStyle(LineSymbol::FlatCap);
default_line_symbol->setJoinStyle(LineSymbol::MiterJoin);
map->addSymbol(default_line_symbol, 1);
default_area_symbol = new AreaSymbol();
default_area_symbol->setName(tr("Area"));
default_area_symbol->setNumberComponent(0, 3);
default_area_symbol->setColor(default_brush_color);
map->addSymbol(default_area_symbol, 2);
default_text_symbol = new TextSymbol();
default_text_symbol->setName(tr("Text"));
default_text_symbol->setNumberComponent(0, 4);
default_text_symbol->setColor(default_pen_color);
map->addSymbol(default_text_symbol, 3);
}
OgrFileImport::~OgrFileImport() = default; // not inlined
bool OgrFileImport::supportsQIODevice() const noexcept
{
return false;
}
void OgrFileImport::setGeoreferencingImportEnabled(bool enabled)
{
georeferencing_import_enabled = enabled;
}
ogr::unique_srs OgrFileImport::srsFromMap()
{
auto srs = ogr::unique_srs(OSRNewSpatialReference(nullptr));
auto& georef = map->getGeoreferencing();
if (georef.isValid() && !georef.isLocal())
{
OSRSetProjCS(srs.get(), "Projected map SRS");
OSRSetWellKnownGeogCS(srs.get(), "WGS84");
auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
// Cf. https://github.com/OSGeo/PROJ/pull/1573
spec.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb");
#endif
auto error = OSRImportFromProj4(srs.get(), spec);
if (!error)
return srs;
addWarning(tr("Unable to setup \"%1\" SRS for GDAL: %2")
.arg(QString::fromLatin1(spec), QString::number(error)));
srs.reset(OSRNewSpatialReference(nullptr));
}
OSRSetLocalCS(srs.get(), "Local SRS");
return srs;
}
bool OgrFileImport::importImplementation()
{
// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
if (!data_source)
{
addWarning(Importer::tr("Cannot open file\n%1:\n%2").arg(path, QString::fromLatin1(CPLGetLastErrorMsg())));
return false;
}
if (auto driver = OGR_DS_GetDriver(data_source.get()))
{
if (auto driver_name = OGR_Dr_GetName(driver))
{
map->setSymbolSetId(QString::fromLatin1(driver_name));
}
}
empty_geometries = 0;
no_transformation = 0;
failed_transformation = 0;
unsupported_geometry_type = 0;
too_few_coordinates = 0;
if (georeferencing_import_enabled)
map_srs = importGeoreferencing(data_source.get());
else
map_srs = srsFromMap();
importStyles(data_source.get());
if (!loadSymbolsOnly())
{
QScopedValueRollback rollback { MapCoord::boundsOffset() };
MapCoord::boundsOffset().reset(true);
auto num_layers = OGR_DS_GetLayerCount(data_source.get());
for (int i = 0; i < num_layers; ++i)
{
auto layer = OGR_DS_GetLayer(data_source.get(), i);
if (!layer)
{
addWarning(tr("Unable to load layer %1.").arg(i));
continue;
}
if (qstrcmp(OGR_L_GetName(layer), "track_points") == 0)
{
// Skip GPX track points as points. Track line is separate.
/// \todo Use hooks and delegates per file format
continue;
}
auto part = map->getCurrentPart();
if (option(QLatin1String("Separate layers")).toBool())
{
if (num_layers > 0)
{
if (part->getNumObjects() == 0)
{
part->setName(QString::fromUtf8(OGR_L_GetName(layer)));
}
else
{
part = new MapPart(QString::fromUtf8(OGR_L_GetName(layer)), map);
auto index = std::size_t(map->getNumParts());
map->addPart(part, index);
map->setCurrentPartIndex(index);
}
}
}
importLayer(part, layer);
}
const auto& offset = MapCoord::boundsOffset();
if (!offset.isZero())
{
// We need to adjust the georeferencing.
auto offset_f = MapCoordF { offset.x / 1000.0, offset.y / 1000.0 };
auto georef = map->getGeoreferencing();
auto ref_point = MapCoordF { georef.getMapRefPoint() };
auto new_projected = georef.toProjectedCoords(ref_point + offset_f);
georef.setProjectedRefPoint(new_projected, false);
map->setGeoreferencing(georef);
}
}
if (empty_geometries)
{
addWarning(tr("Unable to load %n objects, reason: %1", nullptr, empty_geometries)
.arg(tr("Empty geometry.")));
}
if (no_transformation)
{
addWarning(tr("Unable to load %n objects, reason: %1", nullptr, no_transformation)
.arg(tr("Can't determine the coordinate transformation: %1").arg(QString::fromUtf8(CPLGetLastErrorMsg()))));
}
if (failed_transformation)
{
addWarning(tr("Unable to load %n objects, reason: %1", nullptr, failed_transformation)
.arg(tr("Failed to transform the coordinates.")));
}
if (unsupported_geometry_type)
{
addWarning(tr("Unable to load %n objects, reason: %1", nullptr, unsupported_geometry_type)
.arg(tr("Unknown or unsupported geometry type.")));
}
if (too_few_coordinates)
{
addWarning(tr("Unable to load %n objects, reason: %1", nullptr, too_few_coordinates)
.arg(tr("Not enough coordinates.")));
}
return true;
}
ogr::unique_srs OgrFileImport::importGeoreferencing(OGRDataSourceH data_source)
{
auto no_srs = true;
auto local_srs = ogr::unique_srs { nullptr };
auto suitable_srs = ogr::unique_srs { nullptr };
char* projected_srs_spec = { nullptr };
auto orthographic = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
OSRSetProjCS(orthographic.get(), "Orthographic SRS");
OSRSetWellKnownGeogCS(orthographic.get(), "WGS84");
OSRSetOrthographic(orthographic.get(), 0.0, 0.0, 0.0, 0.0);
// Find any SRS which can be transformed to our orthographic SRS,
// but prefer a projected SRS.
auto num_layers = OGR_DS_GetLayerCount(data_source);
for (int i = 0; i < num_layers; ++i)
{
auto layer = OGR_DS_GetLayer(data_source, i);
if (!layer)
continue;
auto spatial_reference = OGR_L_GetSpatialRef(layer);
if (!spatial_reference)
continue;
no_srs = false;
if (OSRIsLocal(spatial_reference))
{
if (!local_srs)
local_srs.reset(OSRClone(spatial_reference));
continue;
}
auto transformation = OCTNewCoordinateTransformation(spatial_reference, orthographic.get());
if (!transformation)
{
addWarning(tr("Cannot use this spatial reference:\n%1").arg(toPrettyWkt(spatial_reference)));
continue;
}
OCTDestroyCoordinateTransformation(transformation);
if (OSRIsProjected(spatial_reference))
{
char *srs_spec = nullptr;
auto error = OSRExportToProj4(spatial_reference, &srs_spec);
if (!error)
{
projected_srs_spec = srs_spec; // transfer ownership
suitable_srs.reset(OSRClone(spatial_reference));
break;
}
CPLFree(srs_spec);
}
if (!suitable_srs)
suitable_srs.reset(OSRClone(spatial_reference));
}
if (projected_srs_spec)
{
// Found a suitable projected SRS
auto center = calcAverageCoords(data_source, suitable_srs.get());
auto georef = map->getGeoreferencing(); // copy
georef.setProjectedCRS(QStringLiteral("PROJ.4"), QString::fromLatin1(projected_srs_spec));
georef.setProjectedRefPoint({std::round(center.x()), std::round(center.y())});
map->setGeoreferencing(georef);
CPLFree(projected_srs_spec);
return suitable_srs;
}
if (suitable_srs)
{
// Found a suitable SRS but it is not projected.
// Setting up a local orthographic projection.
auto center = calcAverageLatLon(data_source);
auto latitude = 0.001 * qRound(1000 * center.latitude());
auto longitude = 0.001 * qRound(1000 * center.longitude());
auto ortho_georef = Georeferencing();
ortho_georef.setScaleDenominator(int(map->getScaleDenominator()));
ortho_georef.setProjectedCRS(QString{},
QString::fromLatin1("+proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs")
.arg(latitude, 0, 'f')
.arg(longitude, 0, 'f') );
ortho_georef.setProjectedRefPoint({}, false);
ortho_georef.setDeclination(map->getGeoreferencing().getDeclination());
map->setGeoreferencing(ortho_georef);
return srsFromMap();
}
if (local_srs || no_srs)
{
auto georef = Georeferencing();
georef.setScaleDenominator(int(map->getScaleDenominator()));
georef.setDeclination(map->getGeoreferencing().getDeclination());
map->setGeoreferencing(georef);
return local_srs ? std::move(local_srs) : srsFromMap();
}
throw FileFormatException(tr("The geospatial data has no suitable spatial reference."));
}
void OgrFileImport::importStyles(OGRDataSourceH data_source)
{
//auto style_table = OGR_DS_GetStyleTable(data_source);
Q_UNUSED(data_source)
}
void OgrFileImport::importLayer(MapPart* map_part, OGRLayerH layer)
{
Q_ASSERT(map_part);
auto feature_definition = OGR_L_GetLayerDefn(layer);
OGR_L_ResetReading(layer);
while (auto feature = ogr::unique_feature(OGR_L_GetNextFeature(layer)))
{
auto geometry = OGR_F_GetGeometryRef(feature.get());
if (!geometry || OGR_G_IsEmpty(geometry))
{
++empty_geometries;
continue;
}
importFeature(map_part, feature_definition, feature.get(), geometry);
}
}
void OgrFileImport::importFeature(MapPart* map_part, OGRFeatureDefnH feature_definition, OGRFeatureH feature, OGRGeometryH geometry)
{
to_map_coord = &OgrFileImport::fromProjected;
auto new_srs = OGR_G_GetSpatialReference(geometry);
if (new_srs && data_srs != new_srs)
{
// New SRS, indeed.
auto transformation = ogr::unique_transformation{ OCTNewCoordinateTransformation(new_srs, map_srs.get()) };
if (!transformation)
{
++no_transformation;
return;
}
// Commit change to data srs and coordinate transformation
data_srs = new_srs;
data_transform = std::move(transformation);
}
if (new_srs)
{
auto error = OGR_G_Transform(geometry, data_transform.get());
if (error)
{
++failed_transformation;
return;
}
}
else if (unit_type == UnitOnPaper)
{
to_map_coord = &OgrFileImport::fromDrawing;
}
auto objects = importGeometry(feature, geometry);
for (auto object : objects)
{
map_part->addObject(object);
if (!feature_definition)
continue;
auto num_fields = OGR_FD_GetFieldCount(feature_definition);
for (int i = 0; i < num_fields; ++i)
{
auto value = OGR_F_GetFieldAsString(feature, i);
if (value && qstrlen(value) > 0)
{
auto field_definition = OGR_FD_GetFieldDefn(feature_definition, i);
object->setTag(QString::fromUtf8(OGR_Fld_GetNameRef(field_definition)), QString::fromUtf8(value));
}
}
}
}
OgrFileImport::ObjectList OgrFileImport::importGeometry(OGRFeatureH feature, OGRGeometryH geometry)
{
ObjectList result;
auto geometry_type = wkbFlatten(OGR_G_GetGeometryType(geometry));
switch (geometry_type)
{
case OGRwkbGeometryType::wkbPoint:
if (auto object = importPointGeometry(feature, geometry))
result = { object };
break;
case OGRwkbGeometryType::wkbLineString:
if (auto object = importLineStringGeometry(feature, geometry))
result = { object };
break;
case OGRwkbGeometryType::wkbPolygon:
if (auto object = importPolygonGeometry(feature, geometry))
result = { object };
break;
case OGRwkbGeometryType::wkbGeometryCollection:
case OGRwkbGeometryType::wkbMultiLineString:
case OGRwkbGeometryType::wkbMultiPoint:
case OGRwkbGeometryType::wkbMultiPolygon:
result = importGeometryCollection(feature, geometry);
break;
default:
qDebug("OgrFileImport: Unknown or unsupported geometry type: %d", geometry_type);
++unsupported_geometry_type;
}
return result;
}
OgrFileImport::ObjectList OgrFileImport::importGeometryCollection(OGRFeatureH feature, OGRGeometryH geometry)
{
ObjectList result;
auto num_geometries = OGR_G_GetGeometryCount(geometry);
result.reserve(std::size_t(num_geometries));
for (int i = 0; i < num_geometries; ++i)
{
auto tmp = importGeometry(feature, OGR_G_GetGeometryRef(geometry, i));
result.insert(result.end(), begin(tmp), end(tmp));
}
return result;
}
Object* OgrFileImport::importPointGeometry(OGRFeatureH feature, OGRGeometryH geometry)
{
auto style = OGR_F_GetStyleString(feature);
auto symbol = getSymbol(Symbol::Point, style);
if (symbol->getType() == Symbol::Point)
{
auto object = new PointObject(symbol);
object->setPosition(toMapCoord(OGR_G_GetX(geometry, 0), OGR_G_GetY(geometry, 0)));
return object;
}
if (symbol->getType() == Symbol::Text)
{
const auto& description = symbol->getDescription();
auto length = description.length();
auto split = description.indexOf(QLatin1Char(' '));
Q_ASSERT(split > 0);
Q_ASSERT(split < length);
auto label = description.right(length - split - 1);
if (label.startsWith(QLatin1Char{'{'}) && label.endsWith(QLatin1Char{'}'}))
{
label.remove(0, 1);
label.chop(1);
int index = OGR_F_GetFieldIndex(feature, label.toLatin1().constData());
if (index >= 0)
{
label = QString::fromUtf8(OGR_F_GetFieldAsString(feature, index));
}
}
if (!label.isEmpty())
{
auto object = new TextObject(symbol);
object->setAnchorPosition(toMapCoord(OGR_G_GetX(geometry, 0), OGR_G_GetY(geometry, 0)));
// DXF observation
label.replace(QRegularExpression(QString::fromLatin1("(\\\\[^;]*;)*"), QRegularExpression::MultilineOption), QString{});
label.replace(QLatin1String("^I"), QLatin1String("\t"));
object->setText(label);
bool ok;
auto anchor = QStringRef(&description, 1, 2).toInt(&ok);
if (ok)
{
applyLabelAnchor(anchor, object);
}
auto angle = QStringRef(&description, 3, split-3).toDouble(&ok);
if (ok)
{
object->setRotation(qDegreesToRadians(angle));
}
return object;
}
}
return nullptr;
}
PathObject* OgrFileImport::importLineStringGeometry(OGRFeatureH feature, OGRGeometryH geometry)
{
auto managed_geometry = ogr::unique_geometry(nullptr);
if (OGR_G_GetGeometryType(geometry) != wkbLineString)
{
geometry = OGR_G_ForceToLineString(OGR_G_Clone(geometry));
managed_geometry.reset(geometry);
}
auto num_points = OGR_G_GetPointCount(geometry);
if (num_points < 2)
{
++too_few_coordinates;
return nullptr;
}
auto style = OGR_F_GetStyleString(feature);
auto object = new PathObject(getSymbol(Symbol::Line, style));
for (int i = 0; i < num_points; ++i)
{
object->addCoordinate(toMapCoord(OGR_G_GetX(geometry, i), OGR_G_GetY(geometry, i)));
}
return object;
}
PathObject* OgrFileImport::importPolygonGeometry(OGRFeatureH feature, OGRGeometryH geometry)
{
auto num_geometries = OGR_G_GetGeometryCount(geometry);
if (num_geometries < 1)
{
++too_few_coordinates;
return nullptr;
}
auto outline = OGR_G_GetGeometryRef(geometry, 0);
auto managed_outline = ogr::unique_geometry(nullptr);
if (OGR_G_GetGeometryType(outline) != wkbLineString)
{
outline = OGR_G_ForceToLineString(OGR_G_Clone(outline));
managed_outline.reset(outline);
}
auto num_points = OGR_G_GetPointCount(outline);
if (num_points < 3)
{
++too_few_coordinates;
return nullptr;
}
auto style = OGR_F_GetStyleString(feature);
auto object = new PathObject(getSymbol(Symbol::Area, style));
for (int i = 0; i < num_points; ++i)
{
object->addCoordinate(toMapCoord(OGR_G_GetX(outline, i), OGR_G_GetY(outline, i)));
}
for (int g = 1; g < num_geometries; ++g)
{
bool start_new_part = true;
auto hole = /*OGR_G_ForceToLineString*/(OGR_G_GetGeometryRef(geometry, g));
auto num_points = OGR_G_GetPointCount(hole);
for (int i = 0; i < num_points; ++i)
{
object->addCoordinate(toMapCoord(OGR_G_GetX(hole, i), OGR_G_GetY(hole, i)), start_new_part);
start_new_part = false;
}
}
object->closeAllParts();
return object;
}
Symbol* OgrFileImport::getSymbol(Symbol::Type type, const char* raw_style_string)
{
auto style_string = QByteArray::fromRawData(raw_style_string, int(qstrlen(raw_style_string)));
Symbol* symbol = nullptr;
switch (type)
{
case Symbol::Point:
case Symbol::Text:
symbol = point_symbols.value(style_string);
if (!symbol)
symbol = getSymbolForPointGeometry(style_string);
if (!symbol)
symbol = default_point_symbol;
break;
case Symbol::Combined:
/// \todo
Q_FALLTHROUGH();
case Symbol::Line:
symbol = line_symbols.value(style_string);
if (!symbol)
symbol = getLineSymbol(style_string);
if (!symbol)
symbol = default_line_symbol;
break;
case Symbol::Area:
symbol = area_symbols.value(style_string);
if (!symbol)
symbol = getAreaSymbol(style_string);
if (!symbol)
symbol = default_area_symbol;
break;
case Symbol::NoSymbol:
case Symbol::AllSymbols:
Q_UNREACHABLE();
}
Q_ASSERT(symbol);
return symbol;
}
MapColor* OgrFileImport::makeColor(OGRStyleToolH tool, const char* color_string)
{
auto key = QByteArray::fromRawData(color_string, int(qstrlen(color_string)));
auto color = colors.value(key);
if (!color)
{
int r, g, b, a;
auto success = OGR_ST_GetRGBFromString(tool, color_string, &r, &g, &b, &a);
if (!success)
{
color = default_pen_color;
}
else if (a > 0)
{
color = new MapColor(QString::fromUtf8(color_string), map->getNumColors());
color->setRgb(QColor{ r, g, b });
color->setCmykFromRgb();
map->addColor(color, map->getNumColors());
}
key.detach();
colors.insert(key, color);
}
return color;
}
void OgrFileImport::applyPenColor(OGRStyleToolH tool, LineSymbol* line_symbol)
{
int is_null;
auto color_string = OGR_ST_GetParamStr(tool, OGRSTPenColor, &is_null);
if (!is_null)
{
auto color = makeColor(tool, color_string);
if (color)
line_symbol->setColor(color);
else
line_symbol->setHidden(true);
}
}
void OgrFileImport::applyBrushColor(OGRStyleToolH tool, AreaSymbol* area_symbol)
{
int is_null;
auto color_string = OGR_ST_GetParamStr(tool, OGRSTBrushFColor, &is_null);
if (!is_null)
{
auto color = makeColor(tool, color_string);
if (color)
area_symbol->setColor(color);
else
area_symbol->setHidden(true);
}
}
Symbol* OgrFileImport::getSymbolForPointGeometry(const QByteArray& style_string)
{
if (style_string.isEmpty())
return nullptr;
auto manager = this->manager.get();
auto data = style_string.constData();
if (!OGR_SM_InitStyleString(manager, data))
return nullptr;
auto num_parts = OGR_SM_GetPartCount(manager, data);
if (!num_parts)
return nullptr;
Symbol* symbol = nullptr;
for (int i = 0; !symbol && i < num_parts; ++i)
{
auto tool = OGR_SM_GetPart(manager, i, nullptr);
if (!tool)
continue;
OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
auto type = OGR_ST_GetType(tool);
switch (type)
{
case OGRSTCBrush:
case OGRSTCPen:
case OGRSTCSymbol:
symbol = getSymbolForOgrSymbol(tool, style_string);
break;
case OGRSTCLabel:
symbol = getSymbolForLabel(tool, style_string);
break;
default:
;
}
OGR_ST_Destroy(tool);
}
return symbol;
}
LineSymbol* OgrFileImport::getLineSymbol(const QByteArray& style_string)
{
if (style_string.isEmpty())
return nullptr;
auto manager = this->manager.get();
auto data = style_string.constData();
if (!OGR_SM_InitStyleString(manager, data))
return nullptr;
auto num_parts = OGR_SM_GetPartCount(manager, data);
if (!num_parts)
return nullptr;
LineSymbol* symbol = nullptr;
for (int i = 0; !symbol && i < num_parts; ++i)
{
auto tool = OGR_SM_GetPart(manager, i, nullptr);
if (!tool)
continue;
OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
auto type = OGR_ST_GetType(tool);
switch (type)
{
case OGRSTCPen:
symbol = getSymbolForPen(tool, style_string);
break;
default:
;
}
OGR_ST_Destroy(tool);
}
return symbol;
}
AreaSymbol* OgrFileImport::getAreaSymbol(const QByteArray& style_string)
{
if (style_string.isEmpty())
return nullptr;
auto manager = this->manager.get();
auto data = style_string.constData();
if (!OGR_SM_InitStyleString(manager, data))
return nullptr;
auto num_parts = OGR_SM_GetPartCount(manager, data);
if (!num_parts)
return nullptr;
AreaSymbol* symbol = nullptr;
for (int i = 0; !symbol && i < num_parts; ++i)
{
auto tool = OGR_SM_GetPart(manager, i, nullptr);
if (!tool)
continue;
OGR_ST_SetUnit(tool, OGRSTUMM, map->getScaleDenominator());
auto type = OGR_ST_GetType(tool);
switch (type)
{
case OGRSTCBrush:
symbol = getSymbolForBrush(tool, style_string);
break;
default:
;
}
OGR_ST_Destroy(tool);
}
return symbol;
}
PointSymbol* OgrFileImport::getSymbolForOgrSymbol(OGRStyleToolH tool, const QByteArray& style_string)
{
auto raw_tool_key = OGR_ST_GetStyleString(tool);
auto tool_key = QByteArray::fromRawData(raw_tool_key, qstrlen(raw_tool_key));
auto symbol = point_symbols.value(tool_key);
if (symbol && symbol->getType() == Symbol::Point)
return static_cast(symbol);
int color_key;
switch (OGR_ST_GetType(tool))
{
case OGRSTCBrush:
color_key = OGRSTBrushFColor;
break;
case OGRSTCPen:
color_key = OGRSTPenColor;
break;
case OGRSTCSymbol:
color_key = OGRSTSymbolColor;
break;
default:
return nullptr;
}
int is_null;
auto color_string = OGR_ST_GetParamStr(tool, color_key, &is_null);
if (is_null)
return nullptr;
auto point_symbol = duplicate(*default_point_symbol);
auto color = makeColor(tool, color_string);
if (color)
point_symbol->setInnerColor(color);
else
point_symbol->setHidden(true);
auto key = style_string;
key.detach();
point_symbols.insert(key, point_symbol.get());
if (key != tool_key)
{
tool_key.detach();
point_symbols.insert(tool_key, point_symbol.get());
}
auto ret = point_symbol.get();
map->addSymbol(point_symbol.release(), map->getNumSymbols());
return ret;
}
TextSymbol* OgrFileImport::getSymbolForLabel(OGRStyleToolH tool, const QByteArray& /*style_string*/)
{
Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCLabel);
int is_null;
auto label_string = OGR_ST_GetParamStr(tool, OGRSTLabelTextString, &is_null);
if (is_null)
return nullptr;
auto color_string = OGR_ST_GetParamStr(tool, OGRSTLabelFColor, &is_null);
auto font_size_string = OGR_ST_GetParamStr(tool, OGRSTLabelSize, &is_null);
// Don't use the style string as a key: The style contains the label.
QByteArray key;
key.reserve(int(qstrlen(color_string) + qstrlen(font_size_string) + 1));
key.append(color_string);
key.append(font_size_string);
auto text_symbol = static_cast(text_symbols.value(key));
if (!text_symbol)
{
auto copy = duplicate(*default_text_symbol);
text_symbol = copy.get();
auto color = makeColor(tool, color_string);
if (color)
text_symbol->setColor(color);
else
text_symbol->setHidden(true);
auto font_size = OGR_ST_GetParamDbl(tool, OGRSTLabelSize, &is_null);
if (!is_null && font_size > 0.0)
text_symbol->scale(font_size / text_symbol->getFontSize());
key.detach();
text_symbols.insert(key, text_symbol);
map->addSymbol(copy.release(), map->getNumSymbols());
}
auto anchor = qBound(1, OGR_ST_GetParamNum(tool, OGRSTLabelAnchor, &is_null), 12);
if (is_null)
anchor = 1;
auto angle = OGR_ST_GetParamDbl(tool, OGRSTLabelAngle, &is_null);
if (is_null)
angle = 0.0;
QString description;
description.reserve(int(qstrlen(label_string) + 100));
description.append(QString::number(100 + anchor));
description.append(QString::number(angle, 'g', 1));
description.append(QLatin1Char(' '));
description.append(QString::fromUtf8(label_string));
text_symbol->setDescription(description);
return text_symbol;
}
LineSymbol* OgrFileImport::getSymbolForPen(OGRStyleToolH tool, const QByteArray& style_string)
{
Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCPen);
auto raw_tool_key = OGR_ST_GetStyleString(tool);
auto tool_key = QByteArray::fromRawData(raw_tool_key, int(qstrlen(raw_tool_key)));
auto symbol = line_symbols.value(tool_key);
if (symbol && symbol->getType() == Symbol::Line)
return static_cast(symbol);
auto line_symbol = duplicate(*default_line_symbol);
applyPenColor(tool, line_symbol.get());
applyPenWidth(tool, line_symbol.get());
applyPenCap(tool, line_symbol.get());
applyPenJoin(tool, line_symbol.get());
applyPenPattern(tool, line_symbol.get());
auto key = style_string;
key.detach();
line_symbols.insert(key, line_symbol.get());
if (key != tool_key)
{
tool_key.detach();
line_symbols.insert(tool_key, line_symbol.get());
}
auto ret = line_symbol.get();
map->addSymbol(line_symbol.release(), map->getNumSymbols());
return ret;
}
AreaSymbol* OgrFileImport::getSymbolForBrush(OGRStyleToolH tool, const QByteArray& style_string)
{
Q_ASSERT(OGR_ST_GetType(tool) == OGRSTCBrush);
auto raw_tool_key = OGR_ST_GetStyleString(tool);
auto tool_key = QByteArray::fromRawData(raw_tool_key, int(qstrlen(raw_tool_key)));
auto symbol = area_symbols.value(tool_key);
if (symbol && symbol->getType() == Symbol::Area)
return static_cast(symbol);
auto area_symbol = duplicate(*default_area_symbol);
applyBrushColor(tool, area_symbol.get());
auto key = style_string;
key.detach();
area_symbols.insert(key, area_symbol.get());
if (key != tool_key)
{
tool_key.detach();
area_symbols.insert(tool_key, area_symbol.get());
}
auto s = area_symbol.get();
map->addSymbol(area_symbol.release(), map->getNumSymbols());
return s;
}
MapCoord OgrFileImport::fromDrawing(double x, double y) const
{
return MapCoord::load(x, -y, MapCoord::Flags{});
}
MapCoord OgrFileImport::fromProjected(double x, double y) const
{
return MapCoord::load(map->getGeoreferencing().toMapCoordF(QPointF{ x, y }), MapCoord::Flags{});
}
// static
bool OgrFileImport::checkGeoreferencing(const QString& path, const Georeferencing& georef)
{
if (georef.isLocal() || !georef.isValid())
return false;
GdalManager();
// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
if (!data_source)
{
throw FileFormatException(QString::fromLatin1(CPLGetLastErrorMsg()));
}
return checkGeoreferencing(data_source.get(), georef);
}
// static
bool OgrFileImport::checkGeoreferencing(OGRDataSourceH data_source, const Georeferencing& georef)
{
auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
// Cf. https://github.com/OSGeo/PROJ/pull/1573
spec.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb");
#endif
auto map_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
OSRSetProjCS(map_srs.get(), "Projected map SRS");
OSRSetWellKnownGeogCS(map_srs.get(), "WGS84");
OSRImportFromProj4(map_srs.get(), spec.constData());
bool suitable_srs_found = false;
auto num_layers = OGR_DS_GetLayerCount(data_source);
for (int i = 0; i < num_layers; ++i)
{
if (auto layer = OGR_DS_GetLayer(data_source, i))
{
if (auto spatial_reference = OGR_L_GetSpatialRef(layer))
{
auto transformation = OCTNewCoordinateTransformation(spatial_reference, map_srs.get());
if (!transformation)
{
qDebug("Failed to transform this SRS:\n%s", qPrintable(toPrettyWkt(spatial_reference)));
return false;
}
OCTDestroyCoordinateTransformation(transformation);
suitable_srs_found = true;
}
}
}
return suitable_srs_found;
}
// static
LatLon OgrFileImport::calcAverageLatLon(const QString& path)
{
GdalManager();
// GDAL 2.0: ... = GDALOpenEx(template_path.toLatin1(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
auto data_source = ogr::unique_datasource(OGROpen(path.toUtf8().constData(), 0, nullptr));
if (!data_source)
{
throw FileFormatException(QString::fromLatin1(CPLGetLastErrorMsg()));
}
return calcAverageLatLon(data_source.get());
}
// static
LatLon OgrFileImport::calcAverageLatLon(OGRDataSourceH data_source)
{
auto geo_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
OSRSetWellKnownGeogCS(geo_srs.get(), "WGS84");
#if GDAL_VERSION_MAJOR >= 3
OSRSetAxisMappingStrategy(geo_srs.get(), OAMS_TRADITIONAL_GIS_ORDER);
#endif
auto const average = calcAverageCoords(data_source, geo_srs.get());
return {average.y(), average.x()};
}
// static
QPointF OgrFileImport::calcAverageCoords(OGRDataSourceH data_source, OGRDataSourceH srs)
{
return QPointF{AverageCoords(data_source, srs)};
}
// ### OgrFileExport ###
OgrFileExport::OgrFileExport(const QString& path, const Map* map, const MapView* view)
: Exporter(path, map, view)
{
GdalManager manager;
bool one_layer_per_symbol = manager.isExportOptionEnabled(GdalManager::OneLayerPerSymbol);
setOption(QString::fromLatin1("Per Symbol Layers"), one_layer_per_symbol);
}
OgrFileExport::~OgrFileExport() = default;
bool OgrFileExport::supportsQIODevice() const noexcept
{
return false;
}
bool OgrFileExport::exportImplementation()
{
// Choose driver and setup format-specific features
QFileInfo info(path);
QString file_extn = info.completeSuffix();
GDALDriverH po_driver = nullptr;
auto count = GDALGetDriverCount();
for (auto i = 0; i < count; ++i)
{
auto driver_data = GDALGetDriver(i);
auto type = GDALGetMetadataItem(driver_data, GDAL_DCAP_VECTOR, nullptr);
if (qstrcmp(type, "YES") != 0)
continue;
auto cap_create = GDALGetMetadataItem(driver_data, GDAL_DCAP_CREATE, nullptr);
if (qstrcmp(cap_create, "YES") != 0)
continue;
auto extensions_raw = GDALGetMetadataItem(driver_data, GDAL_DMD_EXTENSIONS, nullptr);
auto extensions = QByteArray::fromRawData(extensions_raw, int(qstrlen(extensions_raw)));
for (auto pos = 0; pos >= 0; )
{
auto start = pos ? pos + 1 : 0;
pos = extensions.indexOf(' ', start);
auto extension = extensions.mid(start, pos - start);
if (file_extn == QString::fromLatin1(extension))
{
po_driver = driver_data;
break;
}
}
}
if (!po_driver)
throw FileFormatException(tr("Couldn't find a driver for file extension %1").arg(file_extn));
setupQuirks(po_driver);
setupGeoreferencing(po_driver);
// Create output dataset
po_ds = ogr::unique_datasource(OGR_Dr_CreateDataSource(
po_driver,
path.toLatin1(),
nullptr));
if (!po_ds)
throw FileFormatException(tr("Failed to create dataset: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
// Name field definition
if (quirks.testFlag(UseLayerField))
{
symbol_field = "Layer";
o_name_field = nullptr;
}
else
{
symbol_field = "Name";
o_name_field = ogr::unique_fielddefn(OGR_Fld_Create(symbol_field, OFTString));
OGR_Fld_SetWidth(o_name_field.get(), 32);
}
auto symbols = symbolsForExport();
// Setup style table
populateStyleTable(symbols);
auto is_point_object = [](const Object* object) {
const auto* symbol = object->getSymbol();
return symbol && symbol->getContainedTypes() & Symbol::Point;
};
auto is_text_object = [](const Object* object) {
const auto* symbol = object->getSymbol();
return symbol && symbol->getContainedTypes() & Symbol::Text;
};
auto is_line_object = [](const Object* object) {
const auto* symbol = object->getSymbol();
return symbol && (symbol->getType() == Symbol::Line
|| (symbol->getType() == Symbol::Combined && !(symbol->getContainedTypes() & Symbol::Area)));
};
auto is_area_object = [](const Object* object) {
const auto* symbol = object->getSymbol();
return symbol && symbol->getContainedTypes() & Symbol::Area;
};
if (quirks & SingleLayer)
{
auto layer = createLayer("Layer", wkbUnknown);
if (layer == nullptr)
throw FileFormatException(tr("Failed to create layer: %2").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
for (auto symbol : symbols)
{
auto match_symbol = [symbol](auto object) { return object->getSymbol() == symbol; };
switch (symbol->getType())
{
case Symbol::Point:
addPointsToLayer(layer, match_symbol);
break;
case Symbol::Text:
addTextToLayer(layer, match_symbol);
break;
case Symbol::Line:
addLinesToLayer(layer, match_symbol);
break;
case Symbol::Combined:
if (!(symbol->getContainedTypes() & Symbol::Area))
{
addLinesToLayer(layer, match_symbol);
break;
}
Q_FALLTHROUGH();
case Symbol::Area:
addAreasToLayer(layer, match_symbol);
break;
case Symbol::NoSymbol:
case Symbol::AllSymbols:
Q_UNREACHABLE();
}
}
}
else if (option(QString::fromLatin1("Per Symbol Layers")).toBool())
{
// Add points, lines, areas in this order for driver compatibility (esp GPX)
for (auto symbol : symbols)
{
if (symbol->getType() == Symbol::Point)
{
auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPoint);
if (layer != nullptr)
addPointsToLayer(layer, [&symbol](const Object* object) {
const auto* sym = object->getSymbol();
return sym == symbol;
});
}
else if (symbol->getType() == Symbol::Text)
{
auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPoint);
if (layer != nullptr)
addTextToLayer(layer, [&symbol](const Object* object) {
const auto* sym = object->getSymbol();
return sym == symbol;
});
}
}
// Line symbols
for (auto symbol : symbols)
{
if (symbol->getType() == Symbol::Line
|| (symbol->getType() == Symbol::Combined && !(symbol->getContainedTypes() & Symbol::Area)))
{
auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbLineString);
if (layer != nullptr)
addLinesToLayer(layer, [&symbol](const Object* object) {
const auto* sym = object->getSymbol();
return sym == symbol;
});
}
}
// Area symbols
for (auto symbol : symbols)
{
if (symbol->getContainedTypes() & Symbol::Area)
{
auto layer = createLayer(QString::fromUtf8("%1_%2").arg(info.baseName(), symbol->getPlainTextName()).toUtf8(), wkbPolygon);
if (layer != nullptr)
addAreasToLayer(layer, [&symbol](const Object* object) {
const auto* sym = object->getSymbol();
return sym == symbol;
});
}
}
}
else
{
// Add points, lines, areas in this order for driver compatibility (esp GPX)
auto point_layer = createLayer(QString::fromLatin1("%1_points").arg(info.baseName()).toLatin1(), wkbPoint);
if (point_layer != nullptr)
{
addPointsToLayer(point_layer, is_point_object);
addTextToLayer(point_layer, is_text_object);
}
auto line_layer = createLayer(QString::fromLatin1("%1_lines").arg(info.baseName()).toLatin1(), wkbLineString);
if (line_layer != nullptr)
addLinesToLayer(line_layer, is_line_object);
auto area_layer = createLayer(QString::fromLatin1("%1_areas").arg(info.baseName()).toLatin1(), wkbPolygon);
if (area_layer != nullptr)
addAreasToLayer(area_layer, is_area_object);
}
return true;
}
std::vector OgrFileExport::symbolsForExport() const
{
std::vector symbols_in_use;
map->determineSymbolsInUse(symbols_in_use);
const auto num_symbols = map->getNumSymbols();
std::vector symbols;
symbols.reserve(std::size_t(num_symbols));
for (auto i = 0; i < num_symbols; ++i)
{
auto symbol = map->getSymbol(i);
if (symbols_in_use[std::size_t(i)]
&& !symbol->isHidden()
&& !symbol->isHelperSymbol())
symbols.push_back(symbol);
}
std::sort(begin(symbols), end(symbols), Symbol::lessByColorPriority);
return symbols;
}
void OgrFileExport::setupGeoreferencing(GDALDriverH po_driver)
{
// Check if map is georeferenced
const auto& georef = map->getGeoreferencing();
bool local_only = false;
if (georef.getState() == Georeferencing::Local)
{
local_only = true;
addWarning(tr("The map is not georeferenced. Local georeferencing only."));
}
// Make sure GDAL can work with the georeferencing info
map_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
if (!local_only)
{
OSRSetProjCS(map_srs.get(), "Projected map SRS");
OSRSetWellKnownGeogCS(map_srs.get(), "WGS84");
auto spec = QByteArray(georef.getProjectedCRSSpec().toLatin1() + " +wktext");
if (OSRImportFromProj4(map_srs.get(), spec) != OGRERR_NONE)
{
local_only = true;
addWarning(tr("Failed to properly export the georeferencing info. Local georeferencing only."));
}
}
/**
* Only certain drivers work without georeferencing info.
* GeorefOptional based on http://www.gdal.org/ogr_formats.html as of March 5, 2018
*/
if (local_only && !quirks.testFlag(GeorefOptional))
{
throw FileFormatException(tr("The %1 driver requires valid georefencing info.")
.arg(QString::fromLatin1(GDALGetDriverShortName(po_driver))));
}
if (quirks & NeedsWgs84)
{
// Formats with NeedsWgs84 quirk need coords in EPSG:4326/WGS 1984
auto geo_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) };
OSRSetWellKnownGeogCS(geo_srs.get(), "WGS84");
#if GDAL_VERSION_MAJOR >= 3
OSRSetAxisMappingStrategy(geo_srs.get(), OAMS_TRADITIONAL_GIS_ORDER);
#endif
transformation = ogr::unique_transformation { OCTNewCoordinateTransformation(map_srs.get(), geo_srs.get()) };
}
}
void OgrFileExport::setupQuirks(GDALDriverH po_driver)
{
static struct {
const char* name;
OgrQuirks quirks;
} driver_quirks[] = {
{ "ARCGEN", GeorefOptional },
{ "BNA", GeorefOptional },
{ "CSV", GeorefOptional },
{ "DGN", GeorefOptional },
{ "DGNv8", GeorefOptional },
{ "DWG", GeorefOptional },
{ "DXF", OgrQuirks() | GeorefOptional | SingleLayer | UseLayerField },
{ "Geomedia", GeorefOptional },
{ "GPX", NeedsWgs84 },
{ "INGRES", GeorefOptional },
{ "LIBKML", NeedsWgs84 },
{ "ODS", GeorefOptional },
{ "OpenJUMP .jml", GeorefOptional },
{ "REC", GeorefOptional },
{ "SEGY", GeorefOptional },
{ "XLS", GeorefOptional },
{ "XLSX", GeorefOptional },
};
using std::begin;
using std::end;
auto driver_name = GDALGetDriverShortName(po_driver);
auto driver_info = std::find_if(begin(driver_quirks), end(driver_quirks), [driver_name](auto entry) {
return qstrcmp(driver_name, entry.name) == 0;
});
if (driver_info != end(driver_quirks))
quirks |= driver_info->quirks;
}
void OgrFileExport::addPointsToLayer(OGRLayerH layer, const std::function& condition)
{
const auto& georef = map->getGeoreferencing();
auto add_feature = [&](const Object* object) {
auto symbol = object->getSymbol();
auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
QString sym_name = symbol->getPlainTextName();
sym_name.truncate(32);
OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
auto pt = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPoint));
QPointF proj_cord = georef.toProjectedCoords(object->asPoint()->getCoordF());
OGR_G_SetPoint_2D(pt.get(), 0, proj_cord.x(), proj_cord.y());
if (quirks & NeedsWgs84)
OGR_G_Transform(pt.get(), transformation.get());
OGR_F_SetGeometry(po_feature.get(), pt.get());
OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
};
map->applyOnMatchingObjects(add_feature, condition);
}
void OgrFileExport::addTextToLayer(OGRLayerH layer, const std::function& condition)
{
const auto& georef = map->getGeoreferencing();
auto add_feature = [&](const Object* object) {
auto symbol = object->getSymbol();
auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
QString sym_name = symbol->getPlainTextName();
sym_name.truncate(32);
OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
auto text = object->asText()->getText();
if (o_name_field)
{
// Use the name field for the text (useful e.g. for KML).
// This may overwrite the symbol name, and
// it may be too short for the full text.
auto index = OGR_F_GetFieldIndex(po_feature.get(), OGR_Fld_GetNameRef(o_name_field.get()));
OGR_F_SetFieldString(po_feature.get(), index, text.leftRef(32).toUtf8().constData());
}
auto pt = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPoint));
QPointF proj_cord = georef.toProjectedCoords(object->asText()->getAnchorCoordF());
OGR_G_SetPoint_2D(pt.get(), 0, proj_cord.x(), proj_cord.y());
if (quirks & NeedsWgs84)
OGR_G_Transform(pt.get(), transformation.get());
OGR_F_SetGeometry(po_feature.get(), pt.get());
QByteArray style = OGR_STBL_Find(table.get(), symbolId(symbol));
if (!o_name_field || text.length() > 32)
{
// There is no label field, or the text is too long.
// Put the text directly in the label.
text.replace(QRegularExpression(QLatin1String(R"((["\\]))"), QRegularExpression::MultilineOption), QLatin1String("\\\\1"));
style.replace("{Name}", text.toUtf8());
}
OGR_F_SetStyleString(po_feature.get(), style);
if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
};
map->applyOnMatchingObjects(add_feature, condition);
}
void OgrFileExport::addLinesToLayer(OGRLayerH layer, const std::function& condition)
{
const auto& georef = map->getGeoreferencing();
auto add_feature = [&](const Object* object) {
const auto* symbol = object->getSymbol();
const auto* path = object->asPath();
if (path->parts().empty())
return;
QString sym_name = symbol->getPlainTextName();
sym_name.truncate(32);
auto line_string = ogr::unique_geometry(OGR_G_CreateGeometry(wkbLineString));
const auto& parts = path->parts();
for (const auto& part : parts)
{
auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
for (const auto& coord : part.path_coords)
{
QPointF proj_cord = georef.toProjectedCoords(coord.pos);
OGR_G_AddPoint_2D(line_string.get(), proj_cord.x(), proj_cord.y());
}
if (quirks & NeedsWgs84)
OGR_G_Transform(line_string.get(), transformation.get());
OGR_F_SetGeometry(po_feature.get(), line_string.get());
OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
}
};
map->applyOnMatchingObjects(add_feature, condition);
}
void OgrFileExport::addAreasToLayer(OGRLayerH layer, const std::function& condition)
{
const auto& georef = map->getGeoreferencing();
auto add_feature = [&](const Object* object) {
const auto* symbol = object->getSymbol();
const auto* path = object->asPath();
if (path->parts().empty())
return;
auto po_feature = ogr::unique_feature(OGR_F_Create(OGR_L_GetLayerDefn(layer)));
QString sym_name = symbol->getPlainTextName();
sym_name.truncate(32);
OGR_F_SetFieldString(po_feature.get(), OGR_F_GetFieldIndex(po_feature.get(), symbol_field), sym_name.toLatin1().constData());
auto polygon = ogr::unique_geometry(OGR_G_CreateGeometry(wkbPolygon));
auto cur_ring = ogr::unique_geometry(OGR_G_CreateGeometry(wkbLinearRing));
const auto& parts = path->parts();
for (const auto& part : parts)
{
for (const auto& coord : part.path_coords)
{
QPointF proj_cord = georef.toProjectedCoords(coord.pos);
OGR_G_AddPoint_2D(cur_ring.get(), proj_cord.x(), proj_cord.y());
}
OGR_G_CloseRings(cur_ring.get());
if (quirks & NeedsWgs84)
OGR_G_Transform(cur_ring.get(), transformation.get());
OGR_G_AddGeometry(polygon.get(), cur_ring.get());
cur_ring.reset(OGR_G_CreateGeometry(wkbLinearRing));
}
OGR_F_SetGeometry(po_feature.get(), polygon.get());
OGR_F_SetStyleString(po_feature.get(), OGR_STBL_Find(table.get(), symbolId(symbol)));
if (OGR_L_CreateFeature(layer, po_feature.get()) != OGRERR_NONE)
throw FileFormatException(tr("Failed to create feature in layer: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
};
map->applyOnMatchingObjects(add_feature, condition);
}
OGRLayerH OgrFileExport::createLayer(const char* layer_name, OGRwkbGeometryType type)
{
auto po_layer = GDALDatasetCreateLayer(po_ds.get(), layer_name, map_srs.get(), type, nullptr);
if (!po_layer) {
addWarning(tr("Failed to create layer %1: %2").arg(QString::fromUtf8(layer_name), QString::fromLatin1(CPLGetLastErrorMsg())));
return nullptr;
}
if (!quirks.testFlag(UseLayerField)
&& OGR_L_CreateField(po_layer, o_name_field.get(), 1) != OGRERR_NONE)
{
addWarning(tr("Failed to create name field: %1").arg(QString::fromLatin1(CPLGetLastErrorMsg())));
}
return po_layer;
}
void OgrFileExport::populateStyleTable(const std::vector& symbols)
{
table = ogr::unique_styletable(OGR_STBL_Create());
auto manager = ogr::unique_stylemanager(OGR_SM_Create(table.get()));
// Go through all used symbols and create a style table
for (auto symbol : symbols)
{
QByteArray style_string;
switch (symbol->getType())
{
case Symbol::Text:
style_string = makeStyleString(symbol->asText());
break;
case Symbol::Point:
style_string = makeStyleString(symbol->asPoint());
break;
case Symbol::Line:
style_string = makeStyleString(symbol->asLine());
break;
case Symbol::Area:
style_string = makeStyleString(symbol->asArea());
break;
case Symbol::Combined:
style_string = makeStyleString(symbol->asCombined());
break;
case Symbol::NoSymbol:
case Symbol::AllSymbols:
Q_UNREACHABLE();
}
#ifdef MAPPER_DEVELOPMENT_BUILD
if (qEnvironmentVariableIsSet("MAPPER_DEBUG_OGR"))
qDebug("%s:\t \"%s\"", qPrintable(symbol->getPlainTextName()), style_string.constData());
#endif
OGR_SM_AddStyle(manager.get(), symbolId(symbol), style_string);
}
}
} // namespace OpenOrienteering