Skip to content

Commit

Permalink
gdallocationinfo: make it output extra content at end of input line (…
Browse files Browse the repository at this point in the history
…unless -ignore_extra_input), and add -E echo mode, and -field_sep option (fixes OSGeo#9411)
  • Loading branch information
rouault committed Mar 7, 2024
1 parent 27fe644 commit 285c61f
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 8 deletions.
154 changes: 147 additions & 7 deletions apps/gdallocationinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "ogr_spatialref.h"
#include <vector>

#include <cctype>

#ifdef _WIN32
#include <io.h>
#else
Expand All @@ -52,6 +54,8 @@ static void Usage(bool bIsError)
bIsError ? stderr : stdout,
"Usage: gdallocationinfo [--help] [--help-general]\n"
" [-xml] [-lifonly] [-valonly]\n"
" [-E] [-field_sep <sep>] "
"[-ignore_extra_input]\n"
" [-b <band>]... [-overview <overview_level>]\n"
" [-l_srs <srs_def>] [-geoloc] [-wgs84]\n"
" [-oo <NAME>=<VALUE>]... <srcfile> [<x> <y>]\n"
Expand Down Expand Up @@ -100,6 +104,9 @@ MAIN_START(argc, argv)
bool bQuiet = false, bValOnly = false;
int nOverview = -1;
char **papszOpenOptions = nullptr;
std::string osFieldSep;
bool bIgnoreExtraInput = false;
bool bEcho = false;

GDALAllRegister();
argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
Expand Down Expand Up @@ -162,6 +169,21 @@ MAIN_START(argc, argv)
bValOnly = true;
bQuiet = true;
}
else if (i < argc - 1 && EQUAL(argv[i], "-field_sep"))
{
osFieldSep = CPLString(argv[++i])
.replaceAll("\\t", '\t')
.replaceAll("\\r", '\r')
.replaceAll("\\n", '\n');
}
else if (EQUAL(argv[i], "-ignore_extra_input"))
{
bIgnoreExtraInput = true;
}
else if (EQUAL(argv[i], "-E"))
{
bEcho = true;
}
else if (i < argc - 1 && EQUAL(argv[i], "-oo"))
{
papszOpenOptions = CSLAddString(papszOpenOptions, argv[++i]);
Expand All @@ -186,6 +208,28 @@ MAIN_START(argc, argv)
if (pszSrcFilename == nullptr || (pszLocX != nullptr && pszLocY == nullptr))
Usage(true);

if (bEcho && !bValOnly)
{
fprintf(stderr, "-E can only be used with -valonly\n");
exit(1);
}
if (bEcho && osFieldSep.empty())
{
fprintf(stderr, "-E can only be used if -field_sep is specified (to a "
"non-newline value)\n");
exit(1);
}

if (osFieldSep.empty())
{
osFieldSep = "\n";
}
else if (!bValOnly)
{
fprintf(stderr, "-field_sep can only be used with -valonly\n");
exit(1);
}

/* -------------------------------------------------------------------- */
/* Open source file. */
/* -------------------------------------------------------------------- */
Expand Down Expand Up @@ -226,10 +270,13 @@ MAIN_START(argc, argv)
/* -------------------------------------------------------------------- */
/* Turn the location into a pixel and line location. */
/* -------------------------------------------------------------------- */
int inputAvailable = 1;
bool inputAvailable = true;
double dfGeoX;
double dfGeoY;
CPLString osXML;
char szLine[1024];
int nLine = 0;
std::string osExtraContent;

if (pszLocX == nullptr && pszLocY == nullptr)
{
Expand All @@ -249,9 +296,40 @@ MAIN_START(argc, argv)
}
}

if (fscanf(stdin, "%lf %lf", &dfGeoX, &dfGeoY) != 2)
if (fgets(szLine, sizeof(szLine) - 1, stdin))
{
inputAvailable = 0;
const CPLStringList aosTokens(CSLTokenizeString(szLine));
const int nCount = aosTokens.size();

++nLine;
if (nCount < 2)
{
fprintf(stderr, "Not enough values at line %d\n", nLine);
inputAvailable = false;
}
else
{
dfGeoX = CPLAtof(aosTokens[0]);
dfGeoY = CPLAtof(aosTokens[1]);
if (!bIgnoreExtraInput)
{
for (int i = 2; i < nCount; ++i)
{
if (!osExtraContent.empty())
osExtraContent += ' ';
osExtraContent += aosTokens[i];
}
while (!osExtraContent.empty() &&
isspace(static_cast<int>(osExtraContent.back())))
{
osExtraContent.pop_back();
}
}
}
}
else
{
inputAvailable = false;
}
}
else
Expand Down Expand Up @@ -313,11 +391,28 @@ MAIN_START(argc, argv)
{
osLine.Printf("<Report pixel=\"%d\" line=\"%d\">", iPixel, iLine);
osXML += osLine;
if (!osExtraContent.empty())
{
char *pszEscaped =
CPLEscapeString(osExtraContent.c_str(), -1, CPLES_XML);
osXML += CPLString().Printf(" <ExtraInput>%s</ExtraInput>",
pszEscaped);
CPLFree(pszEscaped);
}
}
else if (!bQuiet)
{
printf("Report:\n");
printf(" Location: (%dP,%dL)\n", iPixel, iLine);
if (!osExtraContent.empty())
{
printf(" Extra input: %s\n", osExtraContent.c_str());
}
}
else if (bEcho)
{
printf("%d%s%d%s", iPixel, osFieldSep.c_str(), iLine,
osFieldSep.c_str());
}

bool bPixelReport = true;
Expand Down Expand Up @@ -468,7 +563,11 @@ MAIN_START(argc, argv)
else if (!bQuiet)
printf(" Value: %s\n", osValue.c_str());
else if (bValOnly)
printf("%s\n", osValue.c_str());
{
if (i > 0)
printf("%s", osFieldSep.c_str());
printf("%s", osValue.c_str());
}

// Report unscaled if we have scale/offset values.
int bSuccess;
Expand Down Expand Up @@ -522,10 +621,51 @@ MAIN_START(argc, argv)

osXML += "</Report>";

if ((pszLocX != nullptr && pszLocY != nullptr) ||
(fscanf(stdin, "%lf %lf", &dfGeoX, &dfGeoY) != 2))
if (bValOnly)
{
if (!osExtraContent.empty() && osFieldSep != "\n")
printf("%s%s", osFieldSep.c_str(), osExtraContent.c_str());
printf("\n");
}

if (pszLocX != nullptr && pszLocY != nullptr)
break;

osExtraContent.clear();
if (fgets(szLine, sizeof(szLine) - 1, stdin))
{
const CPLStringList aosTokens(CSLTokenizeString(szLine));
const int nCount = aosTokens.size();

++nLine;
if (nCount < 2)
{
fprintf(stderr, "Not enough values at line %d\n", nLine);
continue;
}
else
{
dfGeoX = CPLAtof(aosTokens[0]);
dfGeoY = CPLAtof(aosTokens[1]);
if (!bIgnoreExtraInput)
{
for (int i = 2; i < nCount; ++i)
{
if (!osExtraContent.empty())
osExtraContent += ' ';
osExtraContent += aosTokens[i];
}
while (!osExtraContent.empty() &&
isspace(static_cast<int>(osExtraContent.back())))
{
osExtraContent.pop_back();
}
}
}
}
else
{
inputAvailable = 0;
break;
}
}

Expand Down
93 changes: 93 additions & 0 deletions autotest/utilities/test_gdallocationinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,96 @@ def test_gdallocationinfo_wgs84(gdallocationinfo_path):

expected_ret = """115"""
assert expected_ret in ret


###############################################################################


def test_gdallocationinfo_field_sep(gdallocationinfo_path):

ret = gdaltest.runexternal(
gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/byte.tif',
strin="0 0",
)

assert "107" in ret
assert "," not in ret

ret = gdaltest.runexternal(
gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/rgbsmall.tif',
strin="15 16",
)

assert "72,102,16" in ret


###############################################################################


def test_gdallocationinfo_extra_input(gdallocationinfo_path):

ret = gdaltest.runexternal(
gdallocationinfo_path + " ../gcore/data/byte.tif", strin="0 0 foo bar"
)

assert "Extra input: foo bar" in ret

ret = gdaltest.runexternal(
gdallocationinfo_path + " -valonly ../gcore/data/byte.tif", strin="0 0 foo bar"
)

assert "107" in ret
assert "foo bar" not in ret

ret = gdaltest.runexternal(
gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/byte.tif',
strin="0 0 foo bar",
)

assert "107,foo bar" in ret

ret = gdaltest.runexternal(
gdallocationinfo_path + " -xml ../gcore/data/byte.tif", strin="0 0 foo bar"
)

assert "<ExtraInput>foo bar</ExtraInput>" in ret


###############################################################################


def test_gdallocationinfo_extra_input_ignored(gdallocationinfo_path):

ret = gdaltest.runexternal(
gdallocationinfo_path
+ ' -valonly -field_sep "," -ignore_extra_input ../gcore/data/byte.tif',
strin="0 0 foo bar",
)

assert "107" in ret
assert "foo bar" not in ret


###############################################################################
# Test echo mode


def test_gdallocationinfo_echo(gdallocationinfo_path):

_, err = gdaltest.runexternal_out_and_err(
gdallocationinfo_path + " -E ../gcore/data/byte.tif 1 2"
)
assert "-E can only be used with -valonly" in err

_, err = gdaltest.runexternal_out_and_err(
gdallocationinfo_path + " -E -valonly ../gcore/data/byte.tif 1 2"
)
assert (
"-E can only be used if -field_sep is specified (to a non-newline value)" in err
)

ret = gdaltest.runexternal(
gdallocationinfo_path + ' -E -valonly -field_sep "," ../gcore/data/byte.tif',
strin="1 2",
)
assert "1,2,132" in ret
35 changes: 34 additions & 1 deletion doc/source/programs/gdallocationinfo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Synopsis
Usage: gdallocationinfo [--help] [--help-general]
[-xml] [-lifonly] [-valonly]
[-E] [-field_sep <sep>] [-ignore_extra_input]
[-b <band>]... [-overview <overview_level>]
[-l_srs <srs_def>] [-geoloc] [-wgs84]
[-oo <NAME>=<VALUE>]... <srcfile> [<x> <y>]
Expand Down Expand Up @@ -45,7 +46,8 @@ reporting options are provided.
.. option:: -valonly

The only output is the pixel values of the selected pixel on each of
the selected bands.
the selected bands. By default, the value of each band is output on a
separate line, unless :option:`-field_sep` is specified.

.. option:: -b <band>

Expand Down Expand Up @@ -74,6 +76,32 @@ reporting options are provided.

Dataset open option (format specific)

.. option:: -ignore_extra_input

.. versionadded:: 3.9

Set this flag to avoid extra non-numeric content at end of input lines to be
appended to the output lines in -valonly mode (requires :option:`-field_sep`
to be also defined), or as a dedicated field in default or :option:`-xml` modes.

.. option:: -E

.. versionadded:: 3.9

Enable Echo mode, where input coordinates are prepended to the output lines
in :option:`-val_only` mode.

.. option:: -field_sep <sep>

.. versionadded:: 3.9

Defines the field separator, used in :option:`-val_only` mode, to separate different values.
By default is is equal to a new-line character, which means that when querying
a raster with several bands, the output will contain one value per line, which
may make it hard to recognize which value belongs to which set of input x,y
points when several ones are provided. Defining the field separator is also
needed

.. option:: <srcfile>

The source GDAL raster datasource name.
Expand Down Expand Up @@ -165,3 +193,8 @@ Reading location from stdin.
Location: (52P,59L)
Band 1:
Value: 148

$ cat coordinates.txt | gdallocationinfo -geoloc -valonly -E -field_sep , utmsmall.tif
443020,3748359,214
441197,3749005,107
443852,3747743,148

0 comments on commit 285c61f

Please sign in to comment.