Coverage Report

Created: 2026-04-01 06:20

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