Skip to content

Commit

Permalink
Merge GH-1779 (List GDAL vector formats)
Browse files Browse the repository at this point in the history
  • Loading branch information
dg0yt authored Nov 16, 2020
2 parents 84e9ecb + 4eb9095 commit d17d1e4
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 83 deletions.
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

0 comments on commit d17d1e4

Please sign in to comment.