Coverage Report

Created: 2026-06-30 08:33

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