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