Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster index" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_index.h"
14 :
15 : #include "cpl_conv.h"
16 : #include "gdal_priv.h"
17 : #include "gdal_utils_priv.h"
18 : #include "ogrsf_frmts.h"
19 :
20 : //! @cond Doxygen_Suppress
21 :
22 : #ifndef _
23 : #define _(x) (x)
24 : #endif
25 :
26 : /************************************************************************/
27 : /* GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm() */
28 : /************************************************************************/
29 :
30 35 : GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()
31 35 : : GDALVectorOutputAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL)
32 : {
33 35 : AddProgressArg();
34 35 : AddInputDatasetArg(&m_inputDatasets, GDAL_OF_RASTER)
35 35 : .SetAutoOpenDataset(false)
36 35 : .SetDatasetInputFlags(GADV_NAME);
37 35 : GDALVectorOutputAbstractAlgorithm::AddAllOutputArgs();
38 :
39 35 : AddCommonOptions();
40 :
41 : AddArg("source-crs-field-name", 0,
42 : _("Name of the field to store the CRS of each dataset"),
43 70 : &m_sourceCrsName)
44 35 : .SetMinCharCount(1);
45 : AddArg("source-crs-format", 0,
46 : _("Format in which the CRS of each dataset must be written"),
47 70 : &m_sourceCrsFormat)
48 35 : .SetMinCharCount(1)
49 35 : .SetDefault(m_sourceCrsFormat)
50 35 : .SetChoices("auto", "WKT", "EPSG", "PROJ");
51 35 : }
52 :
53 : /************************************************************************/
54 : /* GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm() */
55 : /************************************************************************/
56 :
57 31 : GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm(
58 : const std::string &name, const std::string &description,
59 31 : const std::string &helpURL)
60 31 : : GDALVectorOutputAbstractAlgorithm(name, description, helpURL)
61 : {
62 31 : }
63 :
64 : /************************************************************************/
65 : /* GDALRasterIndexAlgorithm::AddCommonOptions() */
66 : /************************************************************************/
67 :
68 66 : void GDALRasterIndexAlgorithm::AddCommonOptions()
69 : {
70 : AddArg("recursive", 0,
71 : _("Whether input directories should be explored recursively."),
72 66 : &m_recursive);
73 : AddArg("filename-filter", 0,
74 : _("Pattern that the filenames in input directories should follow "
75 : "('*' and '?' wildcard)"),
76 66 : &m_filenameFilter);
77 : AddArg("min-pixel-size", 0,
78 : _("Minimum pixel size in term of geospatial extent per pixel "
79 : "(resolution) that a raster should have to be selected."),
80 132 : &m_minPixelSize)
81 66 : .SetMinValueExcluded(0);
82 : AddArg("max-pixel-size", 0,
83 : _("Maximum pixel size in term of geospatial extent per pixel "
84 : "(resolution) that a raster should have to be selected."),
85 132 : &m_maxPixelSize)
86 66 : .SetMinValueExcluded(0);
87 : AddArg("location-name", 0, _("Name of the field with the raster path"),
88 132 : &m_locationName)
89 66 : .SetDefault(m_locationName)
90 66 : .SetMinCharCount(1);
91 : AddAbsolutePathArg(
92 : &m_writeAbsolutePaths,
93 : _("Whether the path to the input datasets should be stored as an "
94 66 : "absolute path"));
95 132 : AddArg("dst-crs", 0, _("Destination CRS"), &m_crs)
96 132 : .SetIsCRSArg()
97 66 : .AddHiddenAlias("t_srs");
98 :
99 : {
100 : auto &arg =
101 132 : AddArg("metadata", 0, _("Add dataset metadata item"), &m_metadata)
102 132 : .SetMetaVar("<KEY>=<VALUE>")
103 66 : .SetPackedValuesAllowed(false);
104 2 : arg.AddValidationAction([this, &arg]()
105 68 : { return ParseAndValidateKeyValue(arg); });
106 66 : arg.AddHiddenAlias("mo");
107 : }
108 :
109 : AddArg("skip-errors", 0, _("Skip errors related to input datasets"),
110 66 : &m_skipErrors);
111 132 : AddArg("profile", 0, _("Profile of output dataset"), &m_profile)
112 66 : .SetDefault(m_profile)
113 66 : .SetChoices(PROFILE_NONE, PROFILE_STAC_GEOPARQUET);
114 66 : AddArg("base-url", 0, _("Base URL for STAC-GeoParquet href"), &m_baseUrl);
115 132 : AddArg("id-method", 0, _("How to derive STAC-GeoParquet 'id'"), &m_idMethod)
116 66 : .SetDefault(m_idMethod)
117 66 : .SetChoices(ID_METHOD_FILENAME, ID_METHOD_MD5, ID_METHOD_METADATA_ITEM);
118 : AddArg("id-metadata-item", 0,
119 : _("Name of metadata item used to set STAC-GeoParquet 'id'"),
120 132 : &m_idMetadataItem)
121 66 : .SetDefault(m_idMetadataItem)
122 : .AddValidationAction(
123 2 : [this]()
124 : {
125 2 : m_idMethod = ID_METHOD_METADATA_ITEM;
126 2 : return true;
127 66 : });
128 :
129 66 : AddValidationAction(
130 96 : [this]()
131 : {
132 32 : if (m_profile == PROFILE_STAC_GEOPARQUET)
133 : {
134 11 : if (!m_outputFormat.empty() &&
135 1 : !EQUAL(m_outputFormat.c_str(), "Parquet"))
136 : {
137 1 : ReportError(CE_Failure, CPLE_NotSupported,
138 : "STAC-GeoParquet profile is only compatible "
139 : "with Parquet output format");
140 1 : return false;
141 : }
142 18 : else if (m_outputFormat.empty() &&
143 18 : !EQUAL(CPLGetExtensionSafe(
144 : m_outputDataset.GetName().c_str())
145 : .c_str(),
146 : "parquet"))
147 : {
148 1 : ReportError(CE_Failure, CPLE_NotSupported,
149 : "STAC-GeoParquet profile is only compatible "
150 : "with Parquet output format");
151 1 : return false;
152 : }
153 8 : m_outputFormat = "Parquet";
154 :
155 8 : if (!m_crs.empty() && m_crs != "EPSG:4326")
156 : {
157 1 : OGRSpatialReference oSRS;
158 1 : CPL_IGNORE_RET_VAL(oSRS.SetFromUserInput(m_crs.c_str()));
159 : const char *pszCelestialBodyName =
160 1 : oSRS.GetCelestialBodyName();
161 : // STAC-GeoParquet requires EPSG:4326, but let be nice
162 : // with planetary use cases and allow a non-Earth geographic CRS...
163 2 : if (!(pszCelestialBodyName &&
164 1 : !EQUAL(pszCelestialBodyName, "Earth") &&
165 0 : oSRS.IsGeographic()))
166 : {
167 1 : ReportError(
168 : CE_Failure, CPLE_NotSupported,
169 : "STAC-GeoParquet profile is only compatible "
170 : "with --dst-crs=EPSG:4326");
171 1 : return false;
172 : }
173 : }
174 7 : if (m_crs.empty())
175 7 : m_crs = "EPSG:4326";
176 : }
177 29 : return true;
178 : });
179 66 : }
180 :
181 : /************************************************************************/
182 : /* GDALRasterIndexAlgorithm::RunImpl() */
183 : /************************************************************************/
184 :
185 28 : bool GDALRasterIndexAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
186 : void *pProgressData)
187 : {
188 56 : CPLStringList aosSources;
189 56 : for (auto &srcDS : m_inputDatasets)
190 : {
191 29 : if (srcDS.GetDatasetRef())
192 : {
193 1 : ReportError(
194 : CE_Failure, CPLE_IllegalArg,
195 : "Input datasets must be provided by name, not as object");
196 1 : return false;
197 : }
198 28 : aosSources.push_back(srcDS.GetName());
199 : }
200 :
201 54 : auto setupRet = SetupOutputDataset();
202 27 : if (!setupRet.outDS)
203 1 : return false;
204 :
205 26 : if (!SetDefaultOutputLayerNameIfNeeded(setupRet.outDS))
206 1 : return false;
207 :
208 50 : CPLStringList aosOptions;
209 25 : aosOptions.push_back("--invoked-from-gdal-raster-index");
210 :
211 25 : if (m_profile != PROFILE_NONE)
212 : {
213 7 : aosOptions.push_back("-profile");
214 7 : aosOptions.push_back(m_profile);
215 :
216 7 : if (!m_baseUrl.empty())
217 : {
218 1 : aosOptions.push_back("--base-url");
219 1 : aosOptions.push_back(m_baseUrl);
220 : }
221 :
222 7 : aosOptions.push_back("--id-metadata-item");
223 7 : aosOptions.push_back(m_idMetadataItem);
224 :
225 7 : aosOptions.push_back("--id-method");
226 7 : aosOptions.push_back(m_idMethod);
227 : }
228 :
229 25 : if (m_skipErrors)
230 : {
231 2 : aosOptions.push_back("-skip_errors");
232 : }
233 25 : if (m_recursive)
234 : {
235 1 : aosOptions.push_back("-recursive");
236 : }
237 27 : for (const std::string &s : m_filenameFilter)
238 : {
239 2 : aosOptions.push_back("-filename_filter");
240 2 : aosOptions.push_back(s);
241 : }
242 25 : if (m_minPixelSize > 0)
243 : {
244 2 : aosOptions.push_back("-min_pixel_size");
245 2 : aosOptions.push_back(CPLSPrintf("%.17g", m_minPixelSize));
246 : }
247 25 : if (m_maxPixelSize > 0)
248 : {
249 0 : aosOptions.push_back("-max_pixel_size");
250 0 : aosOptions.push_back(CPLSPrintf("%.17g", m_maxPixelSize));
251 : }
252 :
253 25 : if (!m_outputLayerName.empty())
254 : {
255 25 : aosOptions.push_back("-lyr_name");
256 25 : aosOptions.push_back(m_outputLayerName);
257 : }
258 :
259 25 : aosOptions.push_back("-tileindex");
260 25 : aosOptions.push_back(m_locationName);
261 :
262 25 : if (m_writeAbsolutePaths)
263 : {
264 2 : aosOptions.push_back("-write_absolute_path");
265 : }
266 25 : if (m_crs.empty())
267 : {
268 15 : if (m_sourceCrsName.empty())
269 15 : aosOptions.push_back("-skip_different_projection");
270 : }
271 : else
272 : {
273 10 : aosOptions.push_back("-t_srs");
274 10 : aosOptions.push_back(m_crs);
275 : }
276 25 : if (!m_sourceCrsName.empty())
277 : {
278 1 : aosOptions.push_back("-src_srs_name");
279 1 : aosOptions.push_back(m_sourceCrsName);
280 :
281 1 : aosOptions.push_back("-src_srs_format");
282 1 : aosOptions.push_back(CPLString(m_sourceCrsFormat).toupper());
283 : }
284 :
285 26 : for (const std::string &s : m_metadata)
286 : {
287 1 : aosOptions.push_back("-mo");
288 1 : aosOptions.push_back(s);
289 : }
290 :
291 25 : if (!AddExtraOptions(aosOptions))
292 2 : return false;
293 :
294 : std::unique_ptr<GDALTileIndexOptions, decltype(&GDALTileIndexOptionsFree)>
295 : options(GDALTileIndexOptionsNew(aosOptions.List(), nullptr),
296 23 : GDALTileIndexOptionsFree);
297 :
298 23 : if (options)
299 : {
300 23 : GDALTileIndexOptionsSetProgress(options.get(), pfnProgress,
301 : pProgressData);
302 : }
303 :
304 : const bool ret =
305 46 : options && GDALTileIndexInternal(m_outputDataset.GetName().c_str(),
306 : GDALDataset::ToHandle(setupRet.outDS),
307 : OGRLayer::ToHandle(setupRet.layer),
308 23 : aosSources.size(), aosSources.List(),
309 46 : options.get(), nullptr) != nullptr;
310 :
311 23 : if (ret && setupRet.newDS)
312 : {
313 19 : m_outputDataset.Set(std::move(setupRet.newDS));
314 : }
315 :
316 23 : return ret;
317 : }
318 :
319 : //! @endcond
|