Coverage Report

Created: 2026-02-14 06:52

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