Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster pixelinfo" 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_pixel_info.h"
14 :
15 : #include "cpl_conv.h"
16 : #include "cpl_json.h"
17 : #include "cpl_minixml.h"
18 : #include "gdal_priv.h"
19 : #include "ogrsf_frmts.h"
20 : #include "ogr_spatialref.h"
21 :
22 : #include <algorithm>
23 : #include <cmath>
24 : #include <limits>
25 : #include <set>
26 : #include <vector>
27 :
28 : //! @cond Doxygen_Suppress
29 :
30 : #ifndef _
31 : #define _(x) (x)
32 : #endif
33 :
34 : /************************************************************************/
35 : /* GDALRasterPixelInfoAlgorithm::GDALRasterPixelInfoAlgorithm() */
36 : /************************************************************************/
37 :
38 79 : GDALRasterPixelInfoAlgorithm::GDALRasterPixelInfoAlgorithm(bool standaloneStep)
39 : : GDALPipelineStepAlgorithm(
40 : NAME, DESCRIPTION, HELP_URL,
41 0 : ConstructorOptions()
42 79 : .SetStandaloneStep(standaloneStep)
43 79 : .SetAddAppendLayerArgument(false)
44 79 : .SetAddOverwriteLayerArgument(false)
45 79 : .SetAddUpdateArgument(false)
46 79 : .SetAddUpsertArgument(false)
47 79 : .SetAddSkipErrorsArgument(false)
48 237 : .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
49 : {
50 79 : if (standaloneStep)
51 : {
52 : AddOutputFormatArg(&m_format, false, false,
53 : _("Output format (default is 'GeoJSON' if "
54 53 : "'position-dataset' not specified)"))
55 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
56 159 : {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
57 53 : AddOpenOptionsArg(&m_openOptions);
58 53 : AddInputFormatsArg(&m_inputFormats)
59 106 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
60 : }
61 :
62 79 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER)
63 158 : .AddAlias("dataset")
64 79 : .SetMinCount(1)
65 79 : .SetMaxCount(1);
66 :
67 : {
68 : auto &coordinateDatasetArg =
69 : AddArg("position-dataset", '\0',
70 : _("Vector dataset with coordinates"), &m_vectorDataset,
71 158 : GDAL_OF_VECTOR)
72 79 : .SetMutualExclusionGroup("position-dataset-pos");
73 79 : if (!standaloneStep)
74 26 : coordinateDatasetArg.SetPositional().SetRequired();
75 :
76 79 : SetAutoCompleteFunctionForFilename(coordinateDatasetArg,
77 : GDAL_OF_VECTOR);
78 :
79 : auto &layerArg = AddArg(GDAL_ARG_NAME_INPUT_LAYER, 'l',
80 158 : _("Input layer name"), &m_inputLayerNames)
81 79 : .SetMaxCount(1)
82 79 : .AddAlias("layer");
83 79 : SetAutoCompleteFunctionForLayerName(layerArg, coordinateDatasetArg);
84 : }
85 :
86 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR,
87 79 : /* positionalAndRequired = */ false)
88 79 : .SetHiddenForCLI(!standaloneStep);
89 79 : if (standaloneStep)
90 : {
91 53 : AddCreationOptionsArg(&m_creationOptions);
92 53 : AddLayerCreationOptionsArg(&m_layerCreationOptions);
93 53 : AddOverwriteArg(&m_overwrite);
94 53 : AddOutputStringArg(&m_output).SetHiddenForCLI();
95 : }
96 :
97 79 : AddBandArg(&m_band);
98 : AddArg("overview", 0, _("Which overview level of source file must be used"),
99 158 : &m_overview)
100 79 : .SetMinValueIncluded(0);
101 :
102 79 : if (standaloneStep)
103 : {
104 106 : AddArg("position", 'p', _("Pixel position"), &m_pos)
105 106 : .AddAlias("pos")
106 106 : .SetMetaVar("<column,line> or <X,Y>")
107 53 : .SetPositional()
108 106 : .SetMutualExclusionGroup("position-dataset-pos")
109 : .AddValidationAction(
110 48 : [this]
111 : {
112 47 : if ((m_pos.size() % 2) != 0)
113 : {
114 1 : ReportError(
115 : CE_Failure, CPLE_IllegalArg,
116 : "An even number of values must be specified "
117 : "for 'position' argument");
118 1 : return false;
119 : }
120 46 : return true;
121 53 : });
122 : }
123 :
124 : AddArg("position-crs", 0,
125 : _("CRS of position (default is 'pixel' if 'position-dataset' not "
126 : "specified)"),
127 158 : &m_posCrs)
128 316 : .SetIsCRSArg(false, {"pixel", "dataset"})
129 79 : .AddHiddenAlias("l_srs");
130 :
131 : AddArg("resampling", 'r', _("Resampling algorithm for interpolation"),
132 158 : &m_resampling)
133 79 : .SetDefault(m_resampling)
134 79 : .SetChoices("nearest", "bilinear", "cubic", "cubicspline")
135 79 : .SetHiddenChoices("near");
136 :
137 : AddArg(
138 : "promote-pixel-value-to-z", 0,
139 : _("Whether to set the pixel value as Z component of GeoJSON geometry"),
140 79 : &m_promotePixelValueToZ);
141 :
142 : AddArg("include-field", 0,
143 : _("Fields from coordinate dataset to include in output (special "
144 : "values: ALL and NONE)"),
145 158 : &m_includeFields)
146 79 : .SetDefault("ALL");
147 :
148 79 : AddValidationAction(
149 167 : [this]
150 : {
151 56 : if (m_inputDataset.size() == 1)
152 : {
153 54 : if (auto poSrcDS = m_inputDataset[0].GetDatasetRef())
154 : {
155 52 : if (poSrcDS->GetRasterCount() > 0)
156 : {
157 : const int nOvrCount =
158 51 : poSrcDS->GetRasterBand(1)->GetOverviewCount();
159 54 : if (m_overview >= 0 && poSrcDS->GetRasterCount() > 0 &&
160 3 : m_overview >= nOvrCount)
161 : {
162 2 : if (nOvrCount == 0)
163 : {
164 1 : ReportError(CE_Failure, CPLE_IllegalArg,
165 : "Source dataset has no overviews. "
166 : "Argument 'overview' must not be "
167 : "specified.");
168 : }
169 : else
170 : {
171 1 : ReportError(
172 : CE_Failure, CPLE_IllegalArg,
173 : "Source dataset has only %d overview "
174 : "level%s. "
175 : "'overview' "
176 : "value must be strictly lower than this "
177 : "number.",
178 : nOvrCount, nOvrCount > 1 ? "s" : "");
179 : }
180 2 : return false;
181 : }
182 : }
183 : else
184 : {
185 1 : ReportError(CE_Failure, CPLE_IllegalArg,
186 : "Source dataset has no raster band.");
187 1 : return false;
188 : }
189 : }
190 : }
191 53 : return true;
192 : });
193 79 : }
194 :
195 : /************************************************************************/
196 : /* GDALRasterPixelInfoAlgorithm::~GDALRasterPixelInfoAlgorithm() */
197 : /************************************************************************/
198 :
199 105 : GDALRasterPixelInfoAlgorithm::~GDALRasterPixelInfoAlgorithm()
200 : {
201 79 : if (!m_osTmpFilename.empty())
202 : {
203 6 : VSIRmdir(CPLGetPathSafe(m_osTmpFilename.c_str()).c_str());
204 6 : VSIUnlink(m_osTmpFilename.c_str());
205 : }
206 105 : }
207 :
208 : /************************************************************************/
209 : /* GDALRasterPixelInfoAlgorithm::RunImpl() */
210 : /************************************************************************/
211 :
212 40 : bool GDALRasterPixelInfoAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
213 : void *pProgressData)
214 : {
215 40 : GDALPipelineStepRunContext stepCtxt;
216 40 : stepCtxt.m_pfnProgress = pfnProgress;
217 40 : stepCtxt.m_pProgressData = pProgressData;
218 40 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
219 : }
220 :
221 : /************************************************************************/
222 : /* GDALRasterPixelInfoAlgorithm::RunStep() */
223 : /************************************************************************/
224 :
225 44 : bool GDALRasterPixelInfoAlgorithm::RunStep(GDALPipelineStepRunContext &)
226 : {
227 44 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
228 44 : CPLAssert(poSrcDS);
229 :
230 44 : auto poVectorSrcDS = m_vectorDataset.GetDatasetRef();
231 :
232 44 : if (m_pos.empty() && !poVectorSrcDS && !IsCalledFromCommandLine())
233 : {
234 1 : ReportError(
235 : CE_Failure, CPLE_AppDefined,
236 : "Argument 'position' or 'position-dataset' must be specified.");
237 1 : return false;
238 : }
239 :
240 43 : if (!m_standaloneStep)
241 : {
242 4 : m_format = "MEM";
243 : }
244 39 : else if (m_outputDataset.GetName().empty())
245 : {
246 25 : if (m_format.empty())
247 : {
248 16 : m_format = "GeoJSON";
249 : }
250 9 : else if (!EQUAL(m_format.c_str(), "CSV") &&
251 0 : !EQUAL(m_format.c_str(), "GeoJSON"))
252 : {
253 0 : ReportError(CE_Failure, CPLE_AppDefined,
254 : "Only CSV or GeoJSON output format is allowed when "
255 : "'output' dataset is not specified.");
256 0 : return false;
257 : }
258 : }
259 14 : else if (m_format.empty())
260 : {
261 : const std::string osExt =
262 26 : CPLGetExtensionSafe(m_outputDataset.GetName().c_str());
263 13 : if (EQUAL(osExt.c_str(), "csv"))
264 1 : m_format = "CSV";
265 12 : else if (EQUAL(osExt.c_str(), "json"))
266 1 : m_format = "GeoJSON";
267 : }
268 :
269 43 : const bool bIsCSV = EQUAL(m_format.c_str(), "CSV");
270 43 : const bool bIsGeoJSON = EQUAL(m_format.c_str(), "GeoJSON");
271 :
272 43 : OGRLayer *poSrcLayer = nullptr;
273 86 : std::vector<int> anSrcFieldIndicesToInclude;
274 86 : std::vector<int> anMapSrcToDstFields;
275 :
276 43 : if (poVectorSrcDS)
277 : {
278 17 : if (m_inputLayerNames.empty())
279 : {
280 15 : const int nLayerCount = poVectorSrcDS->GetLayerCount();
281 15 : if (nLayerCount == 0)
282 : {
283 1 : ReportError(CE_Failure, CPLE_AppDefined,
284 : "Dataset '%s' has no vector layer",
285 1 : poVectorSrcDS->GetDescription());
286 1 : return false;
287 : }
288 14 : else if (nLayerCount > 1)
289 : {
290 1 : ReportError(CE_Failure, CPLE_AppDefined,
291 : "Dataset '%s' has more than one vector layer. "
292 : "Please specify the 'input-layer' argument",
293 1 : poVectorSrcDS->GetDescription());
294 1 : return false;
295 : }
296 13 : poSrcLayer = poVectorSrcDS->GetLayer(0);
297 : }
298 : else
299 : {
300 : poSrcLayer =
301 2 : poVectorSrcDS->GetLayerByName(m_inputLayerNames[0].c_str());
302 : }
303 15 : if (!poSrcLayer)
304 : {
305 1 : ReportError(
306 : CE_Failure, CPLE_AppDefined, "Cannot find layer '%s' in '%s'",
307 1 : m_inputLayerNames[0].c_str(), poVectorSrcDS->GetDescription());
308 1 : return false;
309 : }
310 14 : if (poSrcLayer->GetGeomType() == wkbNone)
311 : {
312 1 : ReportError(CE_Failure, CPLE_AppDefined,
313 : "Layer '%s' of '%s' has no geometry column",
314 1 : m_inputLayerNames[0].c_str(),
315 1 : poVectorSrcDS->GetDescription());
316 1 : return false;
317 : }
318 :
319 13 : if (!GetFieldIndices(m_includeFields, OGRLayer::ToHandle(poSrcLayer),
320 : anSrcFieldIndicesToInclude))
321 : {
322 1 : return false;
323 : }
324 :
325 12 : if (m_posCrs.empty())
326 : {
327 9 : const auto poVectorLayerSRS = poSrcLayer->GetSpatialRef();
328 9 : if (poVectorLayerSRS)
329 : {
330 4 : const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
331 4 : m_posCrs = poVectorLayerSRS->exportToWkt(apszOptions);
332 : }
333 : }
334 : }
335 :
336 38 : if (m_posCrs.empty())
337 26 : m_posCrs = "pixel";
338 :
339 : const GDALRIOResampleAlg eInterpolation =
340 38 : GDALRasterIOGetResampleAlg(m_resampling.c_str());
341 :
342 38 : const auto poSrcCRS = poSrcDS->GetSpatialRef();
343 38 : GDALGeoTransform gt;
344 38 : const bool bHasGT = poSrcDS->GetGeoTransform(gt) == CE_None;
345 38 : GDALGeoTransform invGT;
346 :
347 38 : if (m_posCrs != "pixel")
348 : {
349 10 : if (!poSrcCRS)
350 : {
351 1 : ReportError(CE_Failure, CPLE_AppDefined,
352 : "Dataset has no CRS. Only 'position-crs' = 'pixel' is "
353 : "supported.");
354 1 : return false;
355 : }
356 :
357 9 : if (!bHasGT)
358 : {
359 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot get geotransform");
360 1 : return false;
361 : }
362 :
363 8 : if (!gt.GetInverse(invGT))
364 : {
365 1 : ReportError(CE_Failure, CPLE_AppDefined,
366 : "Cannot invert geotransform");
367 1 : return false;
368 : }
369 : }
370 :
371 35 : std::unique_ptr<OGRCoordinateTransformation> poCT;
372 70 : OGRSpatialReference oUserCRS;
373 35 : if (m_posCrs != "pixel" && m_posCrs != "dataset")
374 : {
375 6 : oUserCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
376 : // Already validated due SetIsCRSArg()
377 6 : CPL_IGNORE_RET_VAL(oUserCRS.SetFromUserInput(m_posCrs.c_str()));
378 6 : poCT.reset(OGRCreateCoordinateTransformation(&oUserCRS, poSrcCRS));
379 6 : if (!poCT)
380 0 : return false;
381 : }
382 :
383 35 : if (m_band.empty())
384 : {
385 70 : for (int i = 1; i <= poSrcDS->GetRasterCount(); ++i)
386 35 : m_band.push_back(i);
387 : }
388 :
389 35 : std::unique_ptr<GDALDataset> poOutDS;
390 35 : OGRLayer *poOutLayer = nullptr;
391 35 : std::unique_ptr<OGRCoordinateTransformation> poCTSrcCRSToOutCRS;
392 :
393 : const bool isInteractive =
394 57 : m_pos.empty() && m_outputDataset.GetName().empty() &&
395 57 : IsCalledFromCommandLine() && CPLIsInteractive(stdin);
396 :
397 70 : std::string osOutFilename = m_outputDataset.GetName();
398 35 : if (osOutFilename.empty() && bIsCSV)
399 : {
400 6 : if (isInteractive)
401 : {
402 0 : osOutFilename = "/vsistdout/";
403 : }
404 : else
405 : {
406 6 : osOutFilename = VSIMemGenerateHiddenFilename("subdir");
407 6 : osOutFilename += "/out.csv";
408 6 : VSIMkdir(CPLGetPathSafe(osOutFilename.c_str()).c_str(), 0755);
409 6 : m_osTmpFilename = osOutFilename;
410 : }
411 : }
412 :
413 35 : if (!osOutFilename.empty() || !m_standaloneStep)
414 : {
415 23 : if (bIsGeoJSON)
416 : {
417 1 : m_outputFile.reset(VSIFOpenL(osOutFilename.c_str(), "wb"));
418 1 : if (!m_outputFile)
419 : {
420 0 : ReportError(CE_Failure, CPLE_FileIO, "Cannot create '%s'",
421 : osOutFilename.c_str());
422 0 : return false;
423 : }
424 : }
425 : else
426 : {
427 22 : if (m_format.empty())
428 : {
429 : const auto aosFormats =
430 : CPLStringList(GDALGetOutputDriversForDatasetName(
431 : osOutFilename.c_str(), GDAL_OF_VECTOR,
432 : /* bSingleMatch = */ true,
433 10 : /* bWarn = */ true));
434 10 : if (aosFormats.size() != 1)
435 : {
436 1 : ReportError(CE_Failure, CPLE_AppDefined,
437 : "Cannot guess driver for %s",
438 : osOutFilename.c_str());
439 1 : return false;
440 : }
441 9 : m_format = aosFormats[0];
442 : }
443 :
444 : auto poOutDrv =
445 21 : GetGDALDriverManager()->GetDriverByName(m_format.c_str());
446 21 : if (!poOutDrv)
447 : {
448 0 : ReportError(CE_Failure, CPLE_AppDefined,
449 : "Cannot find driver %s", m_format.c_str());
450 0 : return false;
451 : }
452 21 : poOutDS.reset(
453 : poOutDrv->Create(osOutFilename.c_str(), 0, 0, 0, GDT_Unknown,
454 42 : CPLStringList(m_creationOptions).List()));
455 21 : if (!poOutDS)
456 1 : return false;
457 :
458 20 : std::string osOutLayerName;
459 20 : if (EQUAL(m_format.c_str(), "ESRI Shapefile") || bIsCSV ||
460 : !poSrcLayer)
461 : {
462 9 : osOutLayerName = CPLGetBasenameSafe(osOutFilename.c_str());
463 : }
464 : else
465 11 : osOutLayerName = poSrcLayer->GetName();
466 :
467 20 : const OGRSpatialReference *poOutCRS = nullptr;
468 20 : if (!oUserCRS.IsEmpty())
469 5 : poOutCRS = &oUserCRS;
470 15 : else if (poSrcLayer)
471 : {
472 6 : poOutCRS = poSrcLayer->GetSpatialRef();
473 6 : if (!poOutCRS)
474 6 : poOutCRS = poSrcCRS;
475 : }
476 40 : poOutLayer = poOutDS->CreateLayer(
477 : osOutLayerName.c_str(), poOutCRS, wkbPoint,
478 40 : CPLStringList(m_layerCreationOptions).List());
479 20 : if (!poOutLayer)
480 : {
481 1 : ReportError(CE_Failure, CPLE_AppDefined,
482 : "Cannot create layer '%s' in '%s'",
483 : osOutLayerName.c_str(), osOutFilename.c_str());
484 1 : return false;
485 : }
486 19 : if (poSrcCRS && poOutCRS)
487 : {
488 11 : poCTSrcCRSToOutCRS.reset(
489 : OGRCreateCoordinateTransformation(poSrcCRS, poOutCRS));
490 11 : if (!poCTSrcCRSToOutCRS)
491 0 : return false;
492 : }
493 : }
494 : }
495 :
496 32 : if (poOutLayer)
497 : {
498 19 : bool bOK = true;
499 :
500 19 : if (bIsCSV)
501 : {
502 16 : OGRFieldDefn oFieldLine("geom_x", OFTReal);
503 8 : bOK &= poOutLayer->CreateField(&oFieldLine) == OGRERR_NONE;
504 :
505 8 : OGRFieldDefn oFieldColumn("geom_y", OFTReal);
506 8 : bOK &= poOutLayer->CreateField(&oFieldColumn) == OGRERR_NONE;
507 : }
508 :
509 19 : if (poSrcLayer)
510 : {
511 22 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
512 11 : const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
513 11 : auto poOutLayerDefn = poOutLayer->GetLayerDefn();
514 :
515 11 : anMapSrcToDstFields.resize(poSrcLayerDefn->GetFieldCount(), -1);
516 :
517 18 : for (int nSrcIdx : anSrcFieldIndicesToInclude)
518 : {
519 : const auto poSrcFieldDefn =
520 7 : poSrcLayerDefn->GetFieldDefn(nSrcIdx);
521 7 : if (poOutLayer->CreateField(poSrcFieldDefn) == OGRERR_NONE)
522 : {
523 7 : const int nDstIdx = poOutLayerDefn->GetFieldIndex(
524 7 : poSrcFieldDefn->GetNameRef());
525 7 : if (nDstIdx >= 0)
526 7 : anMapSrcToDstFields[nSrcIdx] = nDstIdx;
527 : }
528 : }
529 : }
530 : else
531 : {
532 8 : OGRFieldDefn oFieldExtraInput("extra_content", OFTString);
533 8 : bOK &= poOutLayer->CreateField(&oFieldExtraInput) == OGRERR_NONE;
534 : }
535 :
536 19 : OGRFieldDefn oFieldColumn("column", OFTReal);
537 19 : bOK &= poOutLayer->CreateField(&oFieldColumn) == OGRERR_NONE;
538 :
539 19 : OGRFieldDefn oFieldLine("line", OFTReal);
540 19 : bOK &= poOutLayer->CreateField(&oFieldLine) == OGRERR_NONE;
541 :
542 38 : for (int nBand : m_band)
543 : {
544 : auto hBand =
545 19 : GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(nBand));
546 19 : const bool bIsComplex = CPL_TO_BOOL(
547 : GDALDataTypeIsComplex(GDALGetRasterDataType(hBand)));
548 19 : if (bIsComplex)
549 : {
550 : OGRFieldDefn oFieldReal(CPLSPrintf("band_%d_real_value", nBand),
551 6 : OFTReal);
552 3 : bOK &= poOutLayer->CreateField(&oFieldReal) == OGRERR_NONE;
553 :
554 : OGRFieldDefn oFieldImag(
555 3 : CPLSPrintf("band_%d_imaginary_value", nBand), OFTReal);
556 3 : bOK &= poOutLayer->CreateField(&oFieldImag) == OGRERR_NONE;
557 : }
558 : else
559 : {
560 : OGRFieldDefn oFieldRaw(CPLSPrintf("band_%d_raw_value", nBand),
561 32 : OFTReal);
562 16 : bOK &= poOutLayer->CreateField(&oFieldRaw) == OGRERR_NONE;
563 :
564 : OGRFieldDefn oFieldUnscaled(
565 16 : CPLSPrintf("band_%d_unscaled_value", nBand), OFTReal);
566 16 : bOK &= poOutLayer->CreateField(&oFieldUnscaled) == OGRERR_NONE;
567 : }
568 : }
569 :
570 19 : if (!bOK)
571 : {
572 0 : ReportError(CE_Failure, CPLE_AppDefined,
573 : "Cannot create fields in output layer");
574 0 : return false;
575 : }
576 : }
577 :
578 64 : CPLJSONObject oCollection;
579 32 : oCollection.Add("type", "FeatureCollection");
580 32 : std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
581 32 : bool canOutputGeoJSONGeom = false;
582 32 : if (poSrcCRS && bHasGT)
583 : {
584 30 : const char *pszAuthName = poSrcCRS->GetAuthorityName(nullptr);
585 30 : const char *pszAuthCode = poSrcCRS->GetAuthorityCode(nullptr);
586 30 : if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
587 : {
588 29 : canOutputGeoJSONGeom = true;
589 58 : CPLJSONObject jCRS;
590 29 : CPLJSONObject oProperties;
591 29 : if (EQUAL(pszAuthCode, "4326"))
592 2 : oProperties.Add("name", "urn:ogc:def:crs:OGC:1.3:CRS84");
593 : else
594 27 : oProperties.Add(
595 : "name",
596 54 : std::string("urn:ogc:def:crs:EPSG::").append(pszAuthCode));
597 29 : jCRS.Add("type", "name");
598 29 : jCRS.Add("properties", oProperties);
599 58 : oCollection.Add("crs", jCRS);
600 : }
601 : else
602 : {
603 2 : OGRSpatialReference oCRS;
604 1 : oCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
605 1 : oCRS.importFromEPSG(4326);
606 1 : poCTToWGS84.reset(
607 : OGRCreateCoordinateTransformation(poSrcCRS, &oCRS));
608 1 : if (poCTToWGS84)
609 : {
610 1 : canOutputGeoJSONGeom = true;
611 2 : CPLJSONObject jCRS;
612 1 : CPLJSONObject oProperties;
613 1 : oProperties.Add("name", "urn:ogc:def:crs:OGC:1.3:CRS84");
614 1 : jCRS.Add("type", "name");
615 1 : jCRS.Add("properties", oProperties);
616 1 : oCollection.Add("crs", jCRS);
617 : }
618 : }
619 : }
620 64 : CPLJSONArray oFeatures;
621 32 : oCollection.Add("features", oFeatures);
622 :
623 : char szLine[1024];
624 32 : int nLine = 0;
625 32 : size_t iVal = 0;
626 15 : do
627 : {
628 47 : double x = 0, y = 0;
629 47 : std::string osExtraContent;
630 0 : std::unique_ptr<OGRFeature> poSrcFeature;
631 47 : if (poSrcLayer)
632 : {
633 24 : poSrcFeature.reset(poSrcLayer->GetNextFeature());
634 24 : if (!poSrcFeature)
635 12 : break;
636 12 : const auto poGeom = poSrcFeature->GetGeometryRef();
637 12 : if (!poGeom || poGeom->IsEmpty())
638 0 : continue;
639 12 : if (wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
640 : {
641 12 : x = poGeom->toPoint()->getX();
642 12 : y = poGeom->toPoint()->getY();
643 : }
644 : else
645 : {
646 0 : OGRPoint oPoint;
647 0 : if (poGeom->Centroid(&oPoint) != OGRERR_NONE)
648 0 : return false;
649 0 : x = oPoint.getX();
650 0 : y = oPoint.getY();
651 : }
652 : }
653 23 : else if (iVal + 1 < m_pos.size())
654 : {
655 17 : x = m_pos[iVal++];
656 17 : y = m_pos[iVal++];
657 : }
658 : else
659 : {
660 6 : if (CPLIsInteractive(stdin))
661 : {
662 0 : if (m_posCrs != "pixel")
663 : {
664 0 : fprintf(stderr, "Enter X Y values separated by space, and "
665 : "press Return.\n");
666 : }
667 : else
668 : {
669 0 : fprintf(stderr,
670 : "Enter pixel line values separated by space, "
671 : "and press Return.\n");
672 : }
673 : }
674 :
675 6 : if (fgets(szLine, sizeof(szLine) - 1, stdin) && szLine[0] != '\n')
676 : {
677 3 : const CPLStringList aosTokens(CSLTokenizeString(szLine));
678 3 : const int nCount = aosTokens.size();
679 :
680 3 : ++nLine;
681 3 : if (nCount < 2)
682 : {
683 0 : fprintf(stderr, "Not enough values at line %d\n", nLine);
684 0 : return false;
685 : }
686 : else
687 : {
688 3 : x = CPLAtof(aosTokens[0]);
689 3 : y = CPLAtof(aosTokens[1]);
690 :
691 9 : for (int i = 2; i < nCount; ++i)
692 : {
693 6 : if (!osExtraContent.empty())
694 3 : osExtraContent += ' ';
695 6 : osExtraContent += aosTokens[i];
696 : }
697 6 : while (!osExtraContent.empty() &&
698 3 : isspace(static_cast<int>(osExtraContent.back())))
699 : {
700 0 : osExtraContent.pop_back();
701 : }
702 : }
703 : }
704 : else
705 : {
706 3 : break;
707 : }
708 : }
709 :
710 32 : const double xOri = x;
711 32 : const double yOri = y;
712 32 : double dfPixel{0}, dfLine{0};
713 :
714 32 : if (poCT)
715 : {
716 6 : if (!poCT->Transform(1, &x, &y, nullptr))
717 0 : return false;
718 : }
719 :
720 32 : if (m_posCrs != "pixel")
721 : {
722 7 : invGT.Apply(x, y, &dfPixel, &dfLine);
723 : }
724 : else
725 : {
726 25 : dfPixel = x;
727 25 : dfLine = y;
728 : }
729 : const int iPixel = static_cast<int>(
730 64 : std::clamp(std::floor(dfPixel), static_cast<double>(INT_MIN),
731 32 : static_cast<double>(INT_MAX)));
732 : const int iLine = static_cast<int>(
733 64 : std::clamp(std::floor(dfLine), static_cast<double>(INT_MIN),
734 32 : static_cast<double>(INT_MAX)));
735 :
736 32 : CPLJSONObject oFeature;
737 32 : CPLJSONObject oProperties;
738 0 : std::unique_ptr<OGRFeature> poFeature;
739 32 : if (bIsGeoJSON)
740 : {
741 13 : oFeature.Add("type", "Feature");
742 13 : oFeature.Add("properties", oProperties);
743 : {
744 13 : CPLJSONArray oArray;
745 13 : oArray.Add(xOri);
746 13 : oArray.Add(yOri);
747 13 : oProperties.Add("input_coordinate", oArray);
748 : }
749 13 : if (!osExtraContent.empty())
750 1 : oProperties.Add("extra_content", osExtraContent);
751 13 : oProperties.Add("column", dfPixel);
752 13 : oProperties.Add("line", dfLine);
753 :
754 13 : if (poSrcFeature)
755 : {
756 8 : for (int i : anSrcFieldIndicesToInclude)
757 : {
758 7 : const auto *poFieldDefn = poSrcFeature->GetFieldDefnRef(i);
759 7 : const char *pszName = poFieldDefn->GetNameRef();
760 7 : const auto eType = poFieldDefn->GetType();
761 7 : switch (eType)
762 : {
763 3 : case OFTInteger:
764 : case OFTInteger64:
765 : {
766 3 : if (poFieldDefn->GetSubType() == OFSTBoolean)
767 : {
768 1 : oProperties.Add(
769 : pszName,
770 1 : poSrcFeature->GetFieldAsInteger(i) != 0);
771 : }
772 : else
773 : {
774 2 : oProperties.Add(
775 : pszName,
776 : poSrcFeature->GetFieldAsInteger64(i));
777 : }
778 3 : break;
779 : }
780 :
781 1 : case OFTReal:
782 : {
783 1 : oProperties.Add(pszName,
784 : poSrcFeature->GetFieldAsDouble(i));
785 1 : break;
786 : }
787 :
788 2 : case OFTString:
789 : {
790 2 : if (poFieldDefn->GetSubType() != OFSTJSON)
791 : {
792 1 : oProperties.Add(
793 : pszName, poSrcFeature->GetFieldAsString(i));
794 1 : break;
795 : }
796 : else
797 : {
798 : [[fallthrough]];
799 : }
800 : }
801 :
802 : default:
803 : {
804 : char *pszJSON =
805 2 : poSrcFeature->GetFieldAsSerializedJSon(i);
806 4 : CPLJSONDocument oDoc;
807 2 : if (oDoc.LoadMemory(pszJSON))
808 2 : oProperties.Add(pszName, oDoc.GetRoot());
809 2 : CPLFree(pszJSON);
810 2 : break;
811 : }
812 : }
813 : }
814 : }
815 : }
816 : else
817 : {
818 19 : CPLAssert(poOutLayer);
819 : poFeature =
820 19 : std::make_unique<OGRFeature>(poOutLayer->GetLayerDefn());
821 :
822 19 : if (poSrcFeature)
823 : {
824 22 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
825 22 : poFeature->SetFrom(poSrcFeature.get(),
826 11 : anMapSrcToDstFields.data());
827 : }
828 8 : else if (!osExtraContent.empty())
829 : {
830 2 : poFeature->SetField("extra_content", osExtraContent.c_str());
831 : }
832 :
833 19 : if (poOutLayer->GetSpatialRef() == nullptr)
834 : {
835 8 : if (bIsCSV)
836 : {
837 8 : poFeature->SetField("geom_x", xOri);
838 8 : poFeature->SetField("geom_y", yOri);
839 : }
840 : else
841 : {
842 0 : poFeature->SetGeometry(
843 0 : std::make_unique<OGRPoint>(xOri, yOri));
844 : }
845 : }
846 11 : else if (bHasGT && poCTSrcCRSToOutCRS)
847 : {
848 11 : gt.Apply(dfPixel, dfLine, &x, &y);
849 11 : if (poCTSrcCRSToOutCRS->Transform(1, &x, &y))
850 : {
851 11 : if (bIsCSV)
852 : {
853 0 : poFeature->SetField("geom_x", x);
854 0 : poFeature->SetField("geom_y", y);
855 : }
856 : else
857 : {
858 22 : poFeature->SetGeometry(
859 22 : std::make_unique<OGRPoint>(x, y));
860 : }
861 : }
862 : }
863 :
864 19 : poFeature->SetField("column", dfPixel);
865 19 : poFeature->SetField("line", dfLine);
866 : }
867 :
868 32 : CPLJSONArray oBands;
869 :
870 32 : double zValue = std::numeric_limits<double>::quiet_NaN();
871 64 : for (int nBand : m_band)
872 : {
873 32 : CPLJSONObject oBand;
874 32 : oBand.Add("band_number", nBand);
875 :
876 : auto hBand =
877 32 : GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(nBand));
878 :
879 32 : int iPixelToQuery = iPixel;
880 32 : int iLineToQuery = iLine;
881 :
882 32 : double dfPixelToQuery = dfPixel;
883 32 : double dfLineToQuery = dfLine;
884 :
885 32 : if (m_overview >= 0 && hBand != nullptr)
886 : {
887 1 : GDALRasterBandH hOvrBand = GDALGetOverview(hBand, m_overview);
888 1 : if (hOvrBand != nullptr)
889 : {
890 1 : int nOvrXSize = GDALGetRasterBandXSize(hOvrBand);
891 1 : int nOvrYSize = GDALGetRasterBandYSize(hOvrBand);
892 1 : iPixelToQuery = static_cast<int>(
893 1 : 0.5 +
894 1 : 1.0 * iPixel / poSrcDS->GetRasterXSize() * nOvrXSize);
895 1 : iLineToQuery = static_cast<int>(
896 1 : 0.5 +
897 1 : 1.0 * iLine / poSrcDS->GetRasterYSize() * nOvrYSize);
898 1 : if (iPixelToQuery >= nOvrXSize)
899 0 : iPixelToQuery = nOvrXSize - 1;
900 1 : if (iLineToQuery >= nOvrYSize)
901 0 : iLineToQuery = nOvrYSize - 1;
902 1 : dfPixelToQuery =
903 1 : dfPixel / poSrcDS->GetRasterXSize() * nOvrXSize;
904 1 : dfLineToQuery =
905 1 : dfLine / poSrcDS->GetRasterYSize() * nOvrYSize;
906 : }
907 : else
908 : {
909 0 : ReportError(CE_Failure, CPLE_AppDefined,
910 : "Cannot get overview %d of band %d", m_overview,
911 : nBand);
912 0 : return false;
913 : }
914 1 : hBand = hOvrBand;
915 : }
916 :
917 32 : double adfPixel[2] = {0, 0};
918 32 : const bool bIsComplex = CPL_TO_BOOL(
919 : GDALDataTypeIsComplex(GDALGetRasterDataType(hBand)));
920 : int bIgnored;
921 32 : const double dfOffset = GDALGetRasterOffset(hBand, &bIgnored);
922 32 : const double dfScale = GDALGetRasterScale(hBand, &bIgnored);
923 32 : if (GDALRasterInterpolateAtPoint(
924 : hBand, dfPixelToQuery, dfLineToQuery, eInterpolation,
925 32 : &adfPixel[0], &adfPixel[1]) == CE_None)
926 : {
927 30 : if (!bIsComplex)
928 : {
929 27 : const double dfUnscaledVal =
930 27 : adfPixel[0] * dfScale + dfOffset;
931 27 : if (m_band.size() == 1 && m_promotePixelValueToZ)
932 1 : zValue = dfUnscaledVal;
933 27 : if (bIsGeoJSON)
934 : {
935 12 : if (GDALDataTypeIsInteger(GDALGetRasterDataType(hBand)))
936 : {
937 11 : oBand.Add("raw_value",
938 11 : static_cast<GInt64>(adfPixel[0]));
939 : }
940 : else
941 : {
942 1 : oBand.Add("raw_value", adfPixel[0]);
943 : }
944 :
945 12 : oBand.Add("unscaled_value", dfUnscaledVal);
946 : }
947 : else
948 : {
949 15 : poFeature->SetField(
950 : CPLSPrintf("band_%d_raw_value", nBand),
951 : adfPixel[0]);
952 15 : poFeature->SetField(
953 : CPLSPrintf("band_%d_unscaled_value", nBand),
954 : dfUnscaledVal);
955 : }
956 : }
957 : else
958 : {
959 3 : if (bIsGeoJSON)
960 : {
961 1 : CPLJSONObject oValue;
962 1 : oValue.Add("real", adfPixel[0]);
963 1 : oValue.Add("imaginary", adfPixel[1]);
964 1 : oBand.Add("value", oValue);
965 : }
966 : else
967 : {
968 2 : poFeature->SetField(
969 : CPLSPrintf("band_%d_real_value", nBand),
970 : adfPixel[0]);
971 2 : poFeature->SetField(
972 : CPLSPrintf("band_%d_imaginary_value", nBand),
973 : adfPixel[1]);
974 : }
975 : }
976 : }
977 :
978 : // Request location info for this location (just a few drivers,
979 : // like the VRT driver actually supports this).
980 64 : CPLString osItem;
981 32 : osItem.Printf("Pixel_%d_%d", iPixelToQuery, iLineToQuery);
982 :
983 32 : if (const char *pszLI =
984 32 : GDALGetMetadataItem(hBand, osItem, "LocationInfo"))
985 : {
986 4 : CPLXMLTreeCloser oTree(CPLParseXMLString(pszLI));
987 :
988 4 : if (oTree && oTree->psChild != nullptr &&
989 6 : oTree->eType == CXT_Element &&
990 2 : EQUAL(oTree->pszValue, "LocationInfo"))
991 : {
992 2 : CPLJSONArray oFiles;
993 :
994 2 : for (const CPLXMLNode *psNode = oTree->psChild;
995 4 : psNode != nullptr; psNode = psNode->psNext)
996 : {
997 2 : if (psNode->eType == CXT_Element &&
998 2 : EQUAL(psNode->pszValue, "File") &&
999 2 : psNode->psChild != nullptr)
1000 : {
1001 4 : char *pszUnescaped = CPLUnescapeString(
1002 2 : psNode->psChild->pszValue, nullptr, CPLES_XML);
1003 2 : oFiles.Add(pszUnescaped);
1004 2 : CPLFree(pszUnescaped);
1005 : }
1006 : }
1007 :
1008 2 : oBand.Add("files", oFiles);
1009 : }
1010 : else
1011 : {
1012 0 : oBand.Add("location_info", pszLI);
1013 : }
1014 : }
1015 :
1016 32 : oBands.Add(oBand);
1017 : }
1018 :
1019 32 : if (bIsGeoJSON)
1020 : {
1021 13 : oProperties.Add("bands", oBands);
1022 :
1023 13 : if (canOutputGeoJSONGeom)
1024 : {
1025 12 : x = dfPixel;
1026 12 : y = dfLine;
1027 :
1028 12 : gt.Apply(x, y, &x, &y);
1029 :
1030 12 : if (poCTToWGS84)
1031 1 : poCTToWGS84->Transform(1, &x, &y);
1032 :
1033 24 : CPLJSONObject oGeometry;
1034 12 : oFeature.Add("geometry", oGeometry);
1035 12 : oGeometry.Add("type", "Point");
1036 12 : CPLJSONArray oCoordinates;
1037 12 : oCoordinates.Add(x);
1038 12 : oCoordinates.Add(y);
1039 12 : if (!std::isnan(zValue))
1040 1 : oCoordinates.Add(zValue);
1041 12 : oGeometry.Add("coordinates", oCoordinates);
1042 : }
1043 : else
1044 : {
1045 1 : oFeature.AddNull("geometry");
1046 : }
1047 :
1048 13 : if (isInteractive)
1049 : {
1050 0 : CPLJSONDocument oDoc;
1051 0 : oDoc.SetRoot(oFeature);
1052 0 : printf("%s\n", oDoc.SaveAsString().c_str());
1053 : }
1054 : else
1055 : {
1056 13 : oFeatures.Add(oFeature);
1057 : }
1058 : }
1059 : else
1060 : {
1061 19 : if (poOutLayer->CreateFeature(std::move(poFeature)) != OGRERR_NONE)
1062 : {
1063 0 : return false;
1064 : }
1065 : }
1066 :
1067 32 : } while (m_pos.empty() || iVal + 1 < m_pos.size());
1068 :
1069 32 : if (bIsGeoJSON && !isInteractive)
1070 : {
1071 26 : CPLJSONDocument oDoc;
1072 13 : oDoc.SetRoot(oCollection);
1073 26 : std::string osRet = oDoc.SaveAsString();
1074 13 : if (m_outputFile)
1075 1 : m_outputFile->Write(osRet.data(), osRet.size());
1076 : else
1077 25 : m_output = std::move(osRet);
1078 : }
1079 19 : else if (poOutDS)
1080 : {
1081 19 : if (m_outputDataset.GetName().empty() && m_standaloneStep)
1082 : {
1083 6 : poOutDS.reset();
1084 6 : if (!isInteractive)
1085 : {
1086 6 : CPLAssert(!m_osTmpFilename.empty());
1087 : const GByte *pabyData =
1088 6 : VSIGetMemFileBuffer(m_osTmpFilename.c_str(), nullptr,
1089 : /* bUnlinkAndSeize = */ false);
1090 6 : m_output = reinterpret_cast<const char *>(pabyData);
1091 : }
1092 : }
1093 : else
1094 : {
1095 13 : m_outputDataset.Set(std::move(poOutDS));
1096 : }
1097 : }
1098 :
1099 32 : bool bRet = true;
1100 32 : if (m_outputFile)
1101 : {
1102 1 : bRet = m_outputFile->Close() == 0;
1103 1 : if (bRet)
1104 : {
1105 1 : poOutDS.reset(
1106 1 : GDALDataset::Open(m_outputDataset.GetName().c_str(),
1107 : GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
1108 1 : bRet = poOutDS != nullptr;
1109 1 : m_outputDataset.Set(std::move(poOutDS));
1110 : }
1111 : }
1112 :
1113 32 : return bRet;
1114 : }
1115 :
1116 : GDALRasterPixelInfoAlgorithmStandalone::
1117 : ~GDALRasterPixelInfoAlgorithmStandalone() = default;
1118 :
1119 : //! @endcond
|