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 "gdal_manager.h"
21
22 #include <algorithm>
23 #include <cstddef>
24 #include <iterator>
25 // IWYU pragma: no_include <type_traits>
26
27 #include <cpl_conv.h>
28 #include <gdal.h>
29 // IWYU pragma: no_include <gdal_version.h>
30
31 #include <QtGlobal>
32 #include <QByteArray>
33 #include <QDir>
34 #include <QFileInfo>
35 #include <QLatin1String>
36 #include <QSettings>
37 #include <QString>
38 #include <QStringList>
39 #include <QVariant>
40
41 #include "gdal/gdal_extensions.h"
42 #include "util/backports.h" // IWYU pragma: keep
43
44
45 namespace OpenOrienteering {
46
47 class GdalManager::GdalManagerPrivate
48 {
49 public:
50 const QString gdal_manager_group{ QStringLiteral("GdalManager") };
51 const QString gdal_configuration_group{ QStringLiteral("GdalConfiguration") };
52
53 // Enabled formats
54 const QString gdal_gpx_key{ QStringLiteral("gpx") };
55
56 // Template display options
57 const QString gdal_hatch_key{ QStringLiteral("area_hatching") };
58 const QString gdal_baseline_key{ QStringLiteral("baseline_view") };
59
60 // Export options
61 const QString ogr_one_layer_per_symbol_key{ QStringLiteral("per_symbol_layer") };
62
63
64 using ExtensionList = std::vector<QByteArray>;
65
66
GdalManagerPrivate()67 GdalManagerPrivate()
68 : dirty{ true }
69 {
70 GDALAllRegister();
71
72 // Prefer LIBMKL driver to the KML driver if available
73 if (GDALGetDriverByName("LIBKML") != nullptr)
74 GDALDeregisterDriver(GDALGetDriverByName("KML"));
75 }
76
77 GdalManagerPrivate(const GdalManagerPrivate&) = delete;
78 GdalManagerPrivate(GdalManagerPrivate&&) = delete;
79 GdalManagerPrivate& operator=(const GdalManagerPrivate&) = delete;
80 GdalManagerPrivate&& operator=(GdalManagerPrivate&&) = delete;
81
configure()82 void configure()
83 {
84 if (dirty)
85 update();
86 }
87
88
settingsValue(const QString & key,const QVariant & default_value) const89 QVariant settingsValue(const QString& key, const QVariant& default_value) const
90 {
91 QSettings settings;
92 settings.beginGroup(gdal_manager_group);
93 return settings.value(key, default_value);
94 }
95
setSettingsValue(const QString & key,const QVariant & value)96 void setSettingsValue(const QString& key, const QVariant& value)
97 {
98 QSettings settings;
99 settings.beginGroup(gdal_manager_group);
100 settings.setValue(key, value);
101 }
102
103
setFormatEnabled(GdalManager::FileFormat format,bool enabled)104 void setFormatEnabled(GdalManager::FileFormat format, bool enabled)
105 {
106 QString key;
107 switch (format)
108 {
109 case GdalManager::GPX:
110 key = gdal_gpx_key;
111 break;
112
113 }
114 setSettingsValue(key, enabled);
115 dirty = true;
116 }
117
isFormatEnabled(FileFormat format) const118 bool isFormatEnabled(FileFormat format) const
119 {
120 QString key;
121 bool default_value = true;
122 switch (format)
123 {
124 case GdalManager::GPX:
125 key = gdal_gpx_key;
126 default_value = false;
127 break;
128 }
129 return settingsValue(key, default_value).toBool();
130 }
131
setExportOptionEnabled(GdalManager::ExportOption option,bool enabled)132 void setExportOptionEnabled(GdalManager::ExportOption option, bool enabled)
133 {
134 QString key;
135 switch (option)
136 {
137 case GdalManager::OneLayerPerSymbol:
138 key = ogr_one_layer_per_symbol_key;
139 break;
140 }
141 QSettings settings;
142 settings.beginGroup(gdal_manager_group);
143 settings.setValue(key, QVariant{ enabled });
144 dirty = true;
145 }
146
isExportOptionEnabled(GdalManager::ExportOption option) const147 bool isExportOptionEnabled(GdalManager::ExportOption option) const
148 {
149 QString key;
150 switch (option)
151 {
152 case GdalManager::OneLayerPerSymbol:
153 key = ogr_one_layer_per_symbol_key;
154 break;
155 }
156 QSettings settings;
157 settings.beginGroup(gdal_manager_group);
158 return settings.value(key, QVariant{ false }).toBool();
159 }
160
supportedRasterExtensions() const161 const ExtensionList& supportedRasterExtensions() const
162 {
163 if (dirty)
164 const_cast<GdalManagerPrivate*>(this)->update();
165 return enabled_raster_import_extensions;
166 }
167
supportedVectorImportExtensions() const168 const ExtensionList& supportedVectorImportExtensions() const
169 {
170 if (dirty)
171 const_cast<GdalManagerPrivate*>(this)->update();
172 return enabled_vector_import_extensions;
173 }
174
supportedVectorExportExtensions() const175 const ExtensionList& supportedVectorExportExtensions() const
176 {
177 if (dirty)
178 const_cast<GdalManagerPrivate*>(this)->update();
179 return enabled_vector_export_extensions;
180 }
181
parameterKeys() const182 QStringList parameterKeys() const
183 {
184 if (dirty)
185 const_cast<GdalManagerPrivate*>(this)->update();
186 return applied_parameters;
187 }
188
parameterValue(const QString & key) const189 QString parameterValue(const QString& key) const
190 {
191 if (dirty)
192 const_cast<GdalManagerPrivate*>(this)->update();
193 QSettings settings;
194 settings.beginGroup(gdal_configuration_group);
195 return settings.value(key).toString();
196 }
197
setParameterValue(const QString & key,const QString & value)198 void setParameterValue(const QString& key, const QString& value)
199 {
200 QSettings settings;
201 settings.beginGroup(gdal_configuration_group);
202 settings.setValue(key, QVariant{ value });
203 dirty = true;
204 }
205
unsetParameter(const QString & key)206 void unsetParameter(const QString& key)
207 {
208 QSettings settings;
209 settings.beginGroup(gdal_configuration_group);
210 settings.remove(key);
211 dirty = true;
212 }
213
214 private:
copyExtensions(QByteArray const & extension_cstring,ExtensionList & extension_list)215 static void copyExtensions(QByteArray const& extension_cstring, ExtensionList& extension_list)
216 {
217 for (auto pos = 0; pos >= 0; )
218 {
219 auto start = pos ? pos + 1 : 0;
220 pos = extension_cstring.indexOf(' ', start);
221 auto extension = extension_cstring.mid(start, pos - start);
222 if (extension.isEmpty())
223 continue;
224 extension_list.emplace_back(extension);
225 }
226 }
227
prefixDuplicates(ExtensionList const & primary_list,ExtensionList & secondary_list,char const * prefix)228 static void prefixDuplicates(ExtensionList const& primary_list, ExtensionList& secondary_list, char const* prefix)
229 {
230 for (auto& extension : secondary_list)
231 {
232 if (std::find(begin(primary_list), end(primary_list), extension) != end(primary_list))
233 extension.prepend(prefix);
234 }
235 }
236
removeExtension(ExtensionList & list,const char * extension)237 static void removeExtension(ExtensionList& list, const char* extension)
238 {
239 list.erase(std::remove(begin(list), end(list), extension), end(list));
240 }
241
updateExtensions(QSettings & settings)242 void updateExtensions(QSettings& settings)
243 {
244 static auto const qimagereader_extensions = gdal::qImageReaderExtensions<ExtensionList>();
245 static auto const secondary_vector_drivers = gdal::secondaryVectorDrivers<QByteArray>();
246 ExtensionList secondary_vector_extensions;
247
248 auto count = GDALGetDriverCount();
249 enabled_raster_import_extensions.clear();
250 enabled_raster_import_extensions.reserve(std::size_t(count));
251 enabled_vector_import_extensions.clear();
252 enabled_vector_import_extensions.reserve(std::size_t(count));
253 enabled_vector_export_extensions.clear();
254 enabled_vector_export_extensions.reserve(std::size_t(count));
255
256 // Register extensions, either directly (enabled_*), or staged for ambiguity check (secondary_*)
257 for (auto i = 0; i < count; ++i)
258 {
259 auto driver_data = GDALGetDriver(i);
260 auto cap_raster = GDALGetMetadataItem(driver_data, GDAL_DCAP_RASTER, nullptr);
261 auto cap_vector = GDALGetMetadataItem(driver_data, GDAL_DCAP_VECTOR, nullptr);
262 auto cap_open = GDALGetMetadataItem(driver_data, GDAL_DCAP_OPEN, nullptr);
263 auto cap_create = GDALGetMetadataItem(driver_data, GDAL_DCAP_CREATE, nullptr);
264 auto extensions_raw = GDALGetMetadataItem(driver_data, GDAL_DMD_EXTENSIONS, nullptr);
265 auto extensions = QByteArray::fromRawData(extensions_raw, int(qstrlen(extensions_raw)));
266
267 if (qstrcmp(cap_raster, "YES") == 0)
268 {
269 if (qstrcmp(cap_open, "YES") == 0)
270 copyExtensions(extensions, enabled_raster_import_extensions);
271 }
272
273 if (qstrcmp(cap_vector, "YES") == 0)
274 {
275 auto const driver_name = GDALGetDriverShortName(driver_data);
276 bool const is_secondary = secondary_vector_drivers.contains(driver_name);
277 auto &list = is_secondary ? secondary_vector_extensions : enabled_vector_import_extensions;
278 if (qstrcmp(cap_open, "YES") == 0)
279 copyExtensions(extensions, list);
280
281 if (qstrcmp(cap_create, "YES") == 0)
282 copyExtensions(extensions, enabled_vector_export_extensions);
283 }
284 }
285
286 // Handle GDAL/OGR activation settings, before checking ambiguity
287 settings.beginGroup(gdal_manager_group);
288 if (!settings.value(gdal_gpx_key, false).toBool())
289 {
290 removeExtension(enabled_vector_import_extensions, "gpx");
291 removeExtension(secondary_vector_extensions, "gpx");
292 }
293 settings.endGroup();
294
295 // This block is duplicated in mapper_gdal_info.cpp dumpGdalDrivers().
296 // Prefix ambiguous extensions for known vector drivers
297 prefixDuplicates(enabled_raster_import_extensions, secondary_vector_extensions, "vector.");
298 enabled_vector_import_extensions.insert(end(enabled_vector_import_extensions), begin(secondary_vector_extensions), end(secondary_vector_extensions));
299 // Prefix ambiguous extensions for remaining raster drivers
300 prefixDuplicates(enabled_vector_import_extensions, enabled_raster_import_extensions, "raster.");
301 prefixDuplicates(qimagereader_extensions, enabled_raster_import_extensions, "raster.");
302 }
303
updateConfig(QSettings & settings)304 void updateConfig(QSettings& settings)
305 {
306 settings.beginGroup(gdal_configuration_group);
307
308 // Using osmconf.ini to detect a directory with data from gdal. The
309 // data:/gdal directory will always exist, due to mapper-osmconf.ini.
310 auto osm_conf_ini = QFileInfo(QLatin1String("data:/gdal/osmconf.ini"));
311 if (osm_conf_ini.exists())
312 {
313 auto gdal_data = osm_conf_ini.absolutePath();
314 Q_ASSERT(!gdal_data.contains(QStringLiteral("data:")));
315 // The user may overwrite this default in the settings.
316 CPLSetConfigOption("GDAL_DATA", QDir::toNativeSeparators(gdal_data).toLocal8Bit());
317 }
318
319 const char* defaults[][2] = {
320 { "CPL_DEBUG", "OFF" },
321 { "USE_PROJ_480_FEATURES", "YES" },
322 { "OSM_USE_CUSTOM_INDEXING", "NO" },
323 { "GPX_ELE_AS_25D", "YES" },
324 };
325 for (const auto* setting : defaults)
326 {
327 const auto key = QString::fromLatin1(setting[0]);
328 if (!settings.contains(key))
329 {
330 settings.setValue(key, QVariant{QLatin1String(setting[1])});
331 }
332 }
333
334 osm_conf_ini = QFileInfo(QLatin1String("data:/gdal/mapper-osmconf.ini"));
335 if (osm_conf_ini.exists())
336 {
337 auto osm_conf_ini_path = QDir::toNativeSeparators(osm_conf_ini.absoluteFilePath()).toLocal8Bit();
338 auto key = QString::fromLatin1("OSM_CONFIG_FILE");
339 auto update_settings = !settings.contains(key);
340 if (!update_settings)
341 {
342 auto current = settings.value(key).toByteArray();
343 settings.beginGroup(QLatin1String("default"));
344 auto current_default = settings.value(key).toByteArray();
345 settings.endGroup();
346 update_settings = (current == current_default && current != osm_conf_ini_path);
347 }
348 if (update_settings)
349 {
350 settings.setValue(key, osm_conf_ini_path);
351 settings.beginGroup(QLatin1String("default"));
352 settings.setValue(key, osm_conf_ini_path);
353 settings.endGroup();
354 }
355 }
356
357 auto new_parameters = settings.childKeys();
358 new_parameters.sort();
359 for (const auto& parameter : qAsConst(new_parameters))
360 {
361 CPLSetConfigOption(parameter.toLatin1().constData(), settings.value(parameter).toByteArray().constData());
362 }
363 for (const auto& parameter : qAsConst(applied_parameters))
364 {
365 if (!new_parameters.contains(parameter)
366 && parameter != QLatin1String{ "GDAL_DATA" })
367 {
368 CPLSetConfigOption(parameter.toLatin1().constData(), nullptr);
369 }
370 }
371 applied_parameters.swap(new_parameters);
372 settings.endGroup();
373 }
374
update()375 void update()
376 {
377 QSettings settings;
378 updateExtensions(settings);
379 updateConfig(settings);
380 CPLFinderClean(); // force re-initialization of file finding tools
381 dirty = false;
382 }
383
384 mutable bool dirty;
385
386 mutable ExtensionList enabled_raster_import_extensions;
387
388 mutable ExtensionList enabled_vector_import_extensions;
389
390 mutable ExtensionList enabled_vector_export_extensions;
391
392 mutable QStringList applied_parameters;
393
394 };
395
396
397
398 // ### GdalManager ###
399
GdalManager()400 GdalManager::GdalManager()
401 {
402 static GdalManagerPrivate manager;
403 p = &manager;
404 }
405
configure()406 void GdalManager::configure()
407 {
408 p->configure();
409 }
410
411
isAreaHatchingEnabled() const412 bool GdalManager::isAreaHatchingEnabled() const
413 {
414 return p->settingsValue(p->gdal_hatch_key, false).toBool();
415 }
416
setAreaHatchingEnabled(bool enabled)417 void GdalManager::setAreaHatchingEnabled(bool enabled)
418 {
419 p->setSettingsValue(p->gdal_hatch_key, enabled);
420 }
421
isBaselineViewEnabled() const422 bool GdalManager::isBaselineViewEnabled() const
423 {
424 return p->settingsValue(p->gdal_baseline_key, false).toBool();
425 }
426
setBaselineViewEnabled(bool enabled)427 void GdalManager::setBaselineViewEnabled(bool enabled)
428 {
429 p->setSettingsValue(p->gdal_baseline_key, enabled);
430 }
431
432
setFormatEnabled(GdalManager::FileFormat format,bool enabled)433 void GdalManager::setFormatEnabled(GdalManager::FileFormat format, bool enabled)
434 {
435 p->setFormatEnabled(format, enabled);
436 }
437
isFormatEnabled(GdalManager::FileFormat format) const438 bool GdalManager::isFormatEnabled(GdalManager::FileFormat format) const
439 {
440 return p->isFormatEnabled(format);
441 }
442
setExportOptionEnabled(GdalManager::ExportOption option,bool enabled)443 void GdalManager::setExportOptionEnabled(GdalManager::ExportOption option, bool enabled)
444 {
445 return p->setExportOptionEnabled(option, enabled);
446 }
447
isExportOptionEnabled(GdalManager::ExportOption option) const448 bool GdalManager::isExportOptionEnabled(GdalManager::ExportOption option) const
449 {
450 return p->isExportOptionEnabled(option);
451 }
452
supportedRasterExtensions() const453 const std::vector<QByteArray>&GdalManager::supportedRasterExtensions() const
454 {
455 return p->supportedRasterExtensions();
456 }
457
supportedVectorImportExtensions() const458 const std::vector<QByteArray>& GdalManager::supportedVectorImportExtensions() const
459 {
460 return p->supportedVectorImportExtensions();
461 }
462
supportedVectorExportExtensions() const463 const std::vector<QByteArray>& GdalManager::supportedVectorExportExtensions() const
464 {
465 return p->supportedVectorExportExtensions();
466 }
467
parameterKeys() const468 QStringList GdalManager::parameterKeys() const
469 {
470 return p->parameterKeys();
471 }
472
parameterValue(const QString & key) const473 QString GdalManager::parameterValue(const QString& key) const
474 {
475 return p->parameterValue(key);
476 }
477
setParameterValue(const QString & key,const QString & value)478 void GdalManager::setParameterValue(const QString& key, const QString& value)
479 {
480 p->setParameterValue(key, value);
481 }
482
unsetParameter(const QString & key)483 void GdalManager::unsetParameter(const QString& key)
484 {
485 p->unsetParameter(key);
486 }
487
488
489 } // namespace OpenOrienteering
490