Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicitly list each GDAL vector export format #1779

Merged
merged 6 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/fileformats/file_format.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2012, 2013 Pete Curtis
* Copyright 2018 Kai Pastor
* Copyright 2018-2020 Kai Pastor
*
* This file is part of OpenOrienteering.
*
Expand All @@ -20,6 +20,14 @@

#include "file_format.h"

#include <algorithm>
#include <iterator>

#include <Qt>
#include <QFileInfo>
#include <QLatin1Char>
#include <QLatin1String>

#include "file_import_export.h"


Expand Down Expand Up @@ -59,7 +67,7 @@ FileFormat::~FileFormat() = default;
void FileFormat::addExtension(const QString& file_extension)
{
file_extensions << file_extension;
format_filter = QString::fromLatin1("%1 (*.%2)").arg(format_description, file_extensions.join(QString::fromLatin1(" *.")));
format_filter.clear();
}


Expand All @@ -80,6 +88,30 @@ FileFormat::ImportSupportAssumption FileFormat::understands(const char* /*buffer
}


QString FileFormat::fixupExtension(QString filepath) const
{
auto const& extensions = fileExtensions();
if (!extensions.empty())
{
using std::begin; using std::end;
auto const has_extension = std::any_of(begin(extensions), end(extensions), [&filepath](const auto& extension) {
return filepath.endsWith(extension, Qt::CaseInsensitive)
&& filepath.midRef(filepath.length() - extension.length() - 1, 1) == QLatin1String(".");
});
if (!has_extension)
{
if (!filepath.endsWith(QLatin1Char('.')))
filepath.append(QLatin1Char('.'));
filepath.append(primaryExtension());
}

// Ensure that the file name matches the format.
Q_ASSERT(extensions.contains(QFileInfo(filepath).suffix()));
}
return filepath;
}


std::unique_ptr<Importer> FileFormat::makeImporter(const QString& /*path*/, Map* /*map*/, MapView* /*view*/) const
{
qWarning("Format '%s' does not support import", format_id);
Expand All @@ -92,5 +124,20 @@ std::unique_ptr<Exporter> FileFormat::makeExporter(const QString& /*path*/, cons
return nullptr;
}

const QString& OpenOrienteering::FileFormat::filter() const
{
if (format_filter.isEmpty())
{
auto const label = [](QString description) {
description.replace(QLatin1Char('('), QLatin1Char('['));
description.replace(QLatin1Char(')'), QLatin1Char(']'));
return description;
} (format_description);
auto const extensions = file_extensions.join(QStringLiteral(" *."));
format_filter = label + QLatin1String(" (*.") + extensions + QLatin1String(")");
}
return format_filter;
}


} // namespace OpenOrienteering
19 changes: 11 additions & 8 deletions src/fileformats/file_format.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2012, 2013 Pete Curtis
* Copyright 2018 Kai Pastor
* Copyright 2018-2020 Kai Pastor
*
* This file is part of OpenOrienteering.
*
Expand Down Expand Up @@ -271,6 +271,15 @@ class FileFormat
virtual ImportSupportAssumption understands(const char* buffer, int size) const;


/**
* Returns a filepath which ends with one of the formats extensions.
*
* If the filepath does not already end with one the extensions,
* this function appends the primary extension (separated by dot).
*/
QString fixupExtension(QString filepath) const;


/**
* Creates an Importer that will read a map file from the given stream.
*
Expand All @@ -290,7 +299,7 @@ class FileFormat
const char* format_id;
QString format_description;
QStringList file_extensions;
QString format_filter;
mutable QString format_filter;
Features format_features;
};

Expand Down Expand Up @@ -361,12 +370,6 @@ const QStringList& FileFormat::fileExtensions() const
return file_extensions;
}

inline
const QString& FileFormat::filter() const
{
return format_filter;
}


} // namespace OpenOrienteering

Expand Down
6 changes: 4 additions & 2 deletions src/fileformats/file_format_registry.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2012, 2013 Pete Curtis
* Copyright 2013, 2015-2018 Kai Pastor
* Copyright 2013, 2015-2020 Kai Pastor
*
* This file is part of OpenOrienteering.
*
Expand Down Expand Up @@ -123,8 +123,10 @@ const FileFormat *FileFormatRegistry::findFormatByFilter(const QString& filter,
// Compare only before closing ')'. Needed for QTBUG 51712 workaround in
// file_dialog.cpp, and warranted by Q_ASSERT in registerFormat().
return findFormat([predicate, filter](auto format) {
auto const label_len_filter = filter.lastIndexOf(QLatin1String(" ("));
auto const label_len_format = format->filter().lastIndexOf(QLatin1String(" ("));
return (format->*predicate)()
&& filter.startsWith(format->filter().leftRef(format->filter().length()-1));
&& filter.leftRef(label_len_filter) == format->filter().leftRef(label_len_format);
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/gdal/mapper_gdal_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void dumpGdalDrivers()
{
if (cap_open && CPLTestBoolean(cap_open))
vector_import_drivers.push_back(driver_line);
if (cap_create && CPLTestBoolean(cap_create))
if (cap_create && CPLTestBoolean(cap_create) && extensions && *extensions != 0)
vector_export_drivers.push_back(driver_line);
}
}
Expand Down
84 changes: 51 additions & 33 deletions src/gdal/ogr_file_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <QtGlobal>
#include <QtMath>
#include <QByteArray>
#include <QChar>
#include <QColor>
#include <QFileInfo>
#include <QFlags>
Expand All @@ -51,6 +52,7 @@
#include <QRegularExpressionMatch>
#include <QScopedValueRollback>
#include <QString>
#include <QStringList>
#include <QStringRef>
#include <QVariant>

Expand Down Expand Up @@ -622,19 +624,48 @@ std::unique_ptr<Importer> OgrFileImportFormat::makeImporter(const QString& path,

// ### OgrFileExportFormat ###

OgrFileExportFormat::OgrFileExportFormat()
: FileFormat(OgrFile, "OGR-export",
::OpenOrienteering::ImportExport::tr("Geospatial vector data"),
OgrFileExportFormat::OgrFileExportFormat(QByteArray id, const char* name, const char* extensions)
: FileFormat(OgrFile, id.data(),
::OpenOrienteering::ImportExport::tr(qstrlen(name) > 0 ? name : id.constData()),
QString{},
Feature::FileExport | Feature::WritingLossy )
, meta_data(std::move(id))
{
for (const auto& extension : GdalManager().supportedVectorExportExtensions())
addExtension(QString::fromLatin1(extension));
auto const extension_list = QString::fromUtf8(extensions).split(QChar::Space);
for (auto const& extension : extension_list)
addExtension(extension);
}

std::unique_ptr<Exporter> OgrFileExportFormat::makeExporter(const QString& path, const Map* map, const MapView* view) const
{
return std::make_unique<OgrFileExport>(path, map, view);
return std::make_unique<OgrFileExport>(path, map, view, id());
}

// static
std::vector<std::unique_ptr<OgrFileExportFormat>> OgrFileExportFormat::makeAll()
{
std::vector<std::unique_ptr<OgrFileExportFormat>> result;

auto count = GDALGetDriverCount();
result.reserve(count / 2);

for (auto i = 0; i < count; ++i)
{
auto driver_data = GDALGetDriver(i);
auto const* cap_vector = GDALGetMetadataItem(driver_data, GDAL_DCAP_VECTOR, nullptr);
auto const* cap_create = GDALGetMetadataItem(driver_data, GDAL_DCAP_CREATE, nullptr);
auto const* extensions = GDALGetMetadataItem(driver_data, GDAL_DMD_EXTENSIONS, nullptr);
if (qstrcmp(cap_vector, "YES") != 0
|| qstrcmp(cap_create, "YES") != 0
|| qstrlen(extensions) == 0)
continue;

auto id = QByteArray("OGR-export-");
id.append(GDALGetDriverShortName(driver_data));
auto const* long_name = GDALGetDriverLongName(driver_data);
result.push_back(std::make_unique<OgrFileExportFormat>(id, long_name, extensions));
}
return result;
}


Expand Down Expand Up @@ -1815,9 +1846,15 @@ QPointF OgrFileImport::calcAverageCoords(OGRDataSourceH data_source, OGRDataSour

// ### OgrFileExport ###

OgrFileExport::OgrFileExport(const QString& path, const Map* map, const MapView* view)
OgrFileExport::OgrFileExport(const QString& path, const Map* map, const MapView* view, const char* id)
: Exporter(path, map, view)
, id(id)
{
if (qstrncmp(id, "OGR-export-", 11) == 0)
this->id += 11;
if (qstrlen(id) == 0)
this->id = nullptr;

GdalManager manager;
bool one_layer_per_symbol = manager.isExportOptionEnabled(GdalManager::OneLayerPerSymbol);
setOption(QString::fromLatin1("Per Symbol Layers"), one_layer_per_symbol);
Expand All @@ -1834,39 +1871,20 @@ 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)

if (id)
{
auto driver_data = GDALGetDriver(i);

auto driver_data = GDALGetDriverByName(id);
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 (qstrcmp(type, "YES") == 0 && qstrcmp(cap_create, "YES") == 0)
po_driver = driver_data;
}

if (!po_driver)
throw FileFormatException(tr("Couldn't find a driver for file extension %1").arg(file_extn));
throw FileFormatException(::OpenOrienteering::ImportExport::tr("Cannot find a vector data export driver named '%1'")
.arg(QString::fromUtf8(id)));

setupQuirks(po_driver);

Expand Down
14 changes: 12 additions & 2 deletions src/gdal/ogr_file_format.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Kai Pastor
* Copyright 2016-2020 Kai Pastor
*
* This file is part of OpenOrienteering.
*
Expand All @@ -21,7 +21,9 @@
#define OPENORIENTEERING_OGR_FILE_FORMAT_H

#include <memory>
#include <vector>

#include <QByteArray>
#include <QString>

#include "fileformats/file_format.h"
Expand Down Expand Up @@ -63,12 +65,20 @@ class OgrFileExportFormat : public FileFormat
/**
* Constructs a new OgrFileExportFormat.
*/
OgrFileExportFormat();
OgrFileExportFormat(QByteArray id, const char* name, const char* extensions);

/**
* Creates an exporter for files supported by OGR.
*/
std::unique_ptr<Exporter> makeExporter(const QString &path, const Map *map, const MapView *view) const override;

/**
* Returns a container of all supported variants of this format.
*/
static std::vector<std::unique_ptr<OgrFileExportFormat>> makeAll();

private:
QByteArray meta_data;
};


Expand Down
3 changes: 2 additions & 1 deletion src/gdal/ogr_file_format_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ class OgrFileExport : public Exporter
*/
Q_DECLARE_FLAGS(OgrQuirks, OgrQuirk)

OgrFileExport(const QString& path, const Map *map, const MapView *view);
OgrFileExport(const QString& path, const Map *map, const MapView *view, const char* id);
~OgrFileExport() override;

bool supportsQIODevice() const noexcept override;
Expand All @@ -432,6 +432,7 @@ class OgrFileExport : public Exporter
void setupQuirks(GDALDriverH po_driver);

private:
const char* id;
ogr::unique_datasource po_ds;
ogr::unique_fielddefn o_name_field;
ogr::unique_srs map_srs;
Expand Down
5 changes: 3 additions & 2 deletions src/global.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2012, 2013, 2014 Thomas Schöps
* Copyright 2012-2017 Kai Pastor
* Copyright 2012-2020 Kai Pastor
*
* This file is part of OpenOrienteering.
*
Expand Down Expand Up @@ -40,8 +40,9 @@ void doStaticInitializations()
FileFormats.registerFormat(format.release());
#endif
#ifdef MAPPER_USE_GDAL
FileFormats.registerFormat(new OgrFileExportFormat());
FileFormats.registerFormat(new OgrFileImportFormat());
for (auto&& format : OgrFileExportFormat::makeAll())
FileFormats.registerFormat(format.release());
#endif
}

Expand Down
17 changes: 1 addition & 16 deletions src/gui/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1266,22 +1266,7 @@ bool MainWindow::showSaveAsDialog()
return false;
}

// Ensure that the provided filename has a correct file extension.
// Among other things, this will ensure that FileFormats.formatForFilename()
// returns the same thing the user selected in the dialog.
// QString selected_extension = "." + format->primaryExtension();
QStringList selected_extensions(format->fileExtensions());
selected_extensions.replaceInStrings(QRegExp(QString::fromLatin1("^")), QString::fromLatin1("."));
bool has_extension = std::any_of(selected_extensions.constBegin(), selected_extensions.constEnd(), [&path](const auto& selected_extension) {
return path.endsWith(selected_extension, Qt::CaseInsensitive);
});
if (!has_extension)
path += QLatin1Char('.') + format->primaryExtension();
// Ensure that the file name matches the format.
Q_ASSERT(format->fileExtensions().contains(QFileInfo(path).suffix()));
// Fails when using different formats for import and export:
// Q_ASSERT(FileFormats.findFormatForFilename(path, ***) == format);

path = format->fixupExtension(path);
return saveTo(path, *format);
}

Expand Down
Loading