Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "reclassify" step of "raster pipeline"
5 : * Author: Daniel Baston
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, ISciences LLC
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_reclassify.h"
14 :
15 : #include "cpl_vsi_virtual.h"
16 : #include "gdal_priv.h"
17 : #include "gdal_utils.h"
18 : #include "../frmts/vrt/vrtdataset.h"
19 : #include "../frmts/vrt/vrtreclassifier.h"
20 :
21 : #include <array>
22 :
23 : //! @cond Doxygen_Suppress
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : /************************************************************************/
30 : /* GDALRasterReclassifyAlgorithm::GDALRasterReclassifyAlgorithm() */
31 : /************************************************************************/
32 :
33 97 : GDALRasterReclassifyAlgorithm::GDALRasterReclassifyAlgorithm(
34 97 : bool standaloneStep)
35 : : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
36 97 : standaloneStep)
37 : {
38 : AddArg("mapping", 'm',
39 : _("Reclassification mappings (or specify a @<filename> to point to "
40 : "a file containing mappings"),
41 194 : &m_mapping)
42 97 : .SetRequired();
43 : AddArg("keep-color-table", 0, _("Preserve the input color table"),
44 97 : &m_keepColorTable);
45 97 : AddOutputDataTypeArg(&m_type);
46 97 : }
47 :
48 : /************************************************************************/
49 : /* GDALRasterReclassifyValidateMappings */
50 : /************************************************************************/
51 :
52 12 : static bool GDALReclassifyValidateMappings(GDALDataset &input,
53 : const std::string &mappings,
54 : GDALDataType eDstType)
55 : {
56 : int hasNoData;
57 : std::optional<double> noData =
58 12 : input.GetRasterBand(1)->GetNoDataValue(&hasNoData);
59 12 : if (!hasNoData)
60 : {
61 8 : noData.reset();
62 : }
63 :
64 12 : gdal::Reclassifier reclassifier;
65 24 : return reclassifier.Init(mappings.c_str(), noData, eDstType) == CE_None;
66 : }
67 :
68 : /************************************************************************/
69 : /* GDALRasterReclassifyCreateVRTDerived */
70 : /************************************************************************/
71 :
72 : static std::unique_ptr<GDALDataset>
73 11 : GDALReclassifyCreateVRTDerived(GDALDataset &input, const std::string &mappings,
74 : GDALDataType eDstType, bool keepColorTable)
75 : {
76 11 : const auto nX = input.GetRasterXSize();
77 11 : const auto nY = input.GetRasterYSize();
78 :
79 22 : auto poDS = VRTDataset::CreateVRTDataset("", nX, nY, 0, eDstType, nullptr);
80 :
81 11 : GDALGeoTransform gt;
82 11 : if (input.GetGeoTransform(gt) == CE_None)
83 7 : poDS->SetGeoTransform(gt);
84 11 : poDS->SetSpatialRef(input.GetSpatialRef());
85 :
86 22 : CPLStringList papszBandArgs;
87 11 : papszBandArgs.SetNameValue("subclass", "VRTDerivedRasterBand");
88 :
89 24 : for (int iBand = 1; iBand <= input.GetRasterCount(); ++iBand)
90 : {
91 13 : GDALRasterBand *poSrcBand = input.GetRasterBand(iBand);
92 13 : const GDALDataType srcType = poSrcBand->GetRasterDataType();
93 13 : const GDALDataType bandType =
94 13 : eDstType == GDT_Unknown ? srcType : eDstType;
95 13 : const GDALDataType xferType = GDALDataTypeUnion(srcType, bandType);
96 :
97 13 : if (poDS->AddBand(bandType, papszBandArgs) != CE_None)
98 : {
99 0 : return nullptr;
100 : }
101 :
102 : VRTDerivedRasterBand *poDstBand =
103 13 : cpl::down_cast<VRTDerivedRasterBand *>(poDS->GetRasterBand(iBand));
104 13 : poDstBand->SetSourceTransferType(xferType);
105 13 : poDstBand->SetPixelFunctionName("reclassify");
106 13 : poDstBand->AddPixelFunctionArgument("mapping", mappings.c_str());
107 :
108 13 : if (keepColorTable && poSrcBand->GetColorTable() != nullptr)
109 : {
110 1 : poDstBand->SetColorTable(poSrcBand->GetColorTable());
111 : }
112 :
113 13 : GDALCopyNoDataValue(poDstBand, poSrcBand);
114 13 : poDstBand->AddSimpleSource(poSrcBand);
115 : }
116 :
117 11 : return poDS;
118 : }
119 :
120 : /************************************************************************/
121 : /* GDALRasterReclassifyAlgorithm::RunStep() */
122 : /************************************************************************/
123 :
124 15 : bool GDALRasterReclassifyAlgorithm::RunStep(GDALPipelineStepRunContext &)
125 : {
126 15 : const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
127 15 : CPLAssert(poSrcDS);
128 15 : CPLAssert(m_outputDataset.GetName().empty());
129 15 : CPLAssert(!m_outputDataset.GetDatasetRef());
130 :
131 : // Already validated by argument parser
132 : const GDALDataType eDstType =
133 15 : m_type.empty() ? GDT_Unknown : GDALGetDataTypeByName(m_type.c_str());
134 :
135 15 : const auto nErrorCount = CPLGetErrorCounter();
136 15 : if (!m_mapping.empty() && m_mapping[0] == '@')
137 : {
138 : auto f =
139 6 : VSIVirtualHandleUniquePtr(VSIFOpenL(m_mapping.c_str() + 1, "r"));
140 6 : if (!f)
141 : {
142 1 : ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
143 1 : m_mapping.c_str() + 1);
144 1 : return false;
145 : }
146 :
147 5 : m_mapping.clear();
148 : try
149 : {
150 5 : constexpr int MAX_CHARS_PER_LINE = 1000 * 1000;
151 5 : constexpr size_t MAX_MAPPING_SIZE = 10 * 1000 * 1000;
152 : while (const char *line =
153 1034 : CPLReadLine2L(f.get(), MAX_CHARS_PER_LINE, nullptr))
154 : {
155 1200 : while (isspace(*line))
156 : {
157 170 : line++;
158 : }
159 :
160 1030 : if (line[0])
161 : {
162 1024 : if (!m_mapping.empty())
163 : {
164 1018 : m_mapping.append(";");
165 : }
166 :
167 1024 : const char *comment = strchr(line, '#');
168 1024 : if (!comment)
169 : {
170 1018 : m_mapping.append(line);
171 : }
172 : else
173 : {
174 : m_mapping.append(line,
175 6 : static_cast<size_t>(comment - line));
176 : }
177 1024 : if (m_mapping.size() > MAX_MAPPING_SIZE)
178 : {
179 1 : ReportError(CE_Failure, CPLE_AppDefined,
180 : "Too large mapping size");
181 1 : return false;
182 : }
183 : }
184 1029 : }
185 : }
186 0 : catch (const std::exception &)
187 : {
188 0 : ReportError(CE_Failure, CPLE_OutOfMemory,
189 : "Out of memory while ingesting mapping file");
190 : }
191 : }
192 13 : if (nErrorCount == CPLGetErrorCounter())
193 : {
194 12 : if (!GDALReclassifyValidateMappings(*poSrcDS, m_mapping, eDstType))
195 : {
196 1 : return false;
197 : }
198 :
199 11 : m_outputDataset.Set(GDALReclassifyCreateVRTDerived(
200 11 : *poSrcDS, m_mapping, eDstType, m_keepColorTable));
201 : }
202 12 : return m_outputDataset.GetDatasetRef() != nullptr;
203 : }
204 :
205 : GDALRasterReclassifyAlgorithmStandalone::
206 : ~GDALRasterReclassifyAlgorithmStandalone() = default;
207 :
208 : //! @endcond
|