Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PDS 4 Driver; Planetary Data System Format
4 : * Purpose: Implementation of PDS4Dataset
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2019, Hobu Inc
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "pds4dataset.h"
14 : #include "ogrvrtgeometrytypes.h"
15 :
16 : #include "ogr_p.h"
17 :
18 : #include <algorithm>
19 : #include <cassert>
20 :
21 : /************************************************************************/
22 : /* ==================================================================== */
23 : /* PDS4TableBaseLayer */
24 : /* ==================================================================== */
25 : /************************************************************************/
26 :
27 166 : PDS4TableBaseLayer::PDS4TableBaseLayer(PDS4Dataset *poDS, const char *pszName,
28 166 : const char *pszFilename)
29 166 : : m_poDS(poDS), m_poRawFeatureDefn(new OGRFeatureDefn(pszName)),
30 332 : m_poFeatureDefn(new OGRFeatureDefn(pszName)), m_osFilename(pszFilename)
31 : {
32 166 : m_poRawFeatureDefn->SetGeomType(wkbNone);
33 166 : m_poRawFeatureDefn->Reference();
34 166 : m_poFeatureDefn->SetGeomType(wkbNone);
35 166 : m_poFeatureDefn->Reference();
36 166 : SetDescription(pszName);
37 :
38 166 : m_bKeepGeomColmuns =
39 166 : CPLFetchBool(m_poDS->GetOpenOptions(), "KEEP_GEOM_COLUMNS", false);
40 166 : }
41 :
42 : /************************************************************************/
43 : /* ~PDS4TableBaseLayer() */
44 : /************************************************************************/
45 :
46 166 : PDS4TableBaseLayer::~PDS4TableBaseLayer()
47 : {
48 166 : m_poFeatureDefn->Release();
49 166 : m_poRawFeatureDefn->Release();
50 166 : if (m_fp)
51 165 : VSIFCloseL(m_fp);
52 166 : }
53 :
54 : /************************************************************************/
55 : /* RenameFileTo() */
56 : /************************************************************************/
57 :
58 3 : bool PDS4TableBaseLayer::RenameFileTo(const char *pszNewName)
59 : {
60 3 : if (m_fp)
61 3 : VSIFCloseL(m_fp);
62 3 : m_fp = nullptr;
63 6 : CPLString osBackup(pszNewName);
64 3 : osBackup += ".bak";
65 3 : VSIRename(pszNewName, osBackup);
66 3 : bool bSuccess = VSIRename(m_osFilename, pszNewName) == 0;
67 3 : if (bSuccess)
68 : {
69 3 : m_fp = VSIFOpenL(pszNewName, "rb+");
70 3 : if (!m_fp)
71 : {
72 0 : VSIRename(osBackup, pszNewName);
73 0 : return false;
74 : }
75 :
76 3 : m_osFilename = pszNewName;
77 3 : VSIUnlink(osBackup);
78 3 : return true;
79 : }
80 : else
81 : {
82 0 : VSIRename(osBackup, pszNewName);
83 0 : return false;
84 : }
85 : }
86 :
87 : /************************************************************************/
88 : /* GetFileList() */
89 : /************************************************************************/
90 :
91 64 : char **PDS4TableBaseLayer::GetFileList() const
92 : {
93 64 : return CSLAddString(nullptr, GetFileName());
94 : }
95 :
96 : /************************************************************************/
97 : /* GetFeatureCount() */
98 : /************************************************************************/
99 :
100 100 : GIntBig PDS4TableBaseLayer::GetFeatureCount(int bForce)
101 : {
102 100 : if (m_poAttrQuery != nullptr || m_poFilterGeom != nullptr)
103 : {
104 0 : return OGRLayer::GetFeatureCount(bForce);
105 : }
106 100 : return m_nFeatureCount;
107 : }
108 :
109 : /************************************************************************/
110 : /* SetupGeomField() */
111 : /************************************************************************/
112 :
113 97 : void PDS4TableBaseLayer::SetupGeomField()
114 : {
115 97 : const char *const *papszOpenOptions = m_poDS->GetOpenOptions();
116 97 : const char *pszWKT = CSLFetchNameValue(papszOpenOptions, "WKT");
117 194 : if (pszWKT == nullptr &&
118 140 : (m_iWKT = m_poRawFeatureDefn->GetFieldIndex("WKT")) >= 0 &&
119 43 : m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetType() == OFTString)
120 : {
121 43 : pszWKT = "WKT";
122 : }
123 : else
124 : {
125 54 : m_iWKT = -1;
126 : }
127 97 : if (pszWKT && !EQUAL(pszWKT, ""))
128 : {
129 43 : m_iWKT = m_poRawFeatureDefn->GetFieldIndex(pszWKT);
130 43 : if (m_iWKT < 0)
131 : {
132 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s", pszWKT);
133 : }
134 43 : else if (m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetType() !=
135 : OFTString)
136 : {
137 0 : CPLError(CE_Warning, CPLE_AppDefined,
138 : "The %s field should be of type String", pszWKT);
139 : }
140 : else
141 : {
142 43 : m_poFeatureDefn->SetGeomType(wkbUnknown);
143 : }
144 : }
145 : else
146 : {
147 54 : const char *pszLat = CSLFetchNameValue(papszOpenOptions, "LAT");
148 54 : const char *pszLong = CSLFetchNameValue(papszOpenOptions, "LONG");
149 54 : if (pszLat == nullptr && pszLong == nullptr &&
150 54 : (m_iLatField = m_poRawFeatureDefn->GetFieldIndex("Latitude")) >=
151 11 : 0 &&
152 11 : (m_iLongField = m_poRawFeatureDefn->GetFieldIndex("Longitude")) >=
153 11 : 0 &&
154 11 : m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetType() ==
155 108 : OFTReal &&
156 11 : m_poRawFeatureDefn->GetFieldDefn(m_iLongField)->GetType() ==
157 : OFTReal)
158 : {
159 11 : pszLat = "Latitude";
160 11 : pszLong = "Longitude";
161 : }
162 : else
163 : {
164 43 : m_iLatField = -1;
165 43 : m_iLongField = -1;
166 : }
167 54 : if (pszLat && pszLong && !EQUAL(pszLat, "") && !EQUAL(pszLong, ""))
168 : {
169 11 : m_iLatField = m_poRawFeatureDefn->GetFieldIndex(pszLat);
170 11 : m_iLongField = m_poRawFeatureDefn->GetFieldIndex(pszLong);
171 11 : if (m_iLatField < 0)
172 : {
173 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s",
174 : pszLat);
175 : }
176 11 : else if (m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetType() !=
177 : OFTReal)
178 : {
179 0 : CPLError(CE_Warning, CPLE_AppDefined,
180 : "The %s field should be of type Real", pszLat);
181 0 : m_iLatField = -1;
182 : }
183 11 : if (m_iLongField < 0)
184 : {
185 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s",
186 : pszLong);
187 : }
188 22 : else if (m_poRawFeatureDefn->GetFieldDefn(m_iLongField)
189 11 : ->GetType() != OFTReal)
190 : {
191 0 : CPLError(CE_Warning, CPLE_AppDefined,
192 : "The %s field should be of type Real", pszLong);
193 0 : m_iLongField = -1;
194 : }
195 11 : if (m_iLatField < 0 || m_iLongField < 0)
196 : {
197 0 : m_iLatField = -1;
198 0 : m_iLongField = -1;
199 : }
200 : else
201 : {
202 11 : const char *pszAlt = CSLFetchNameValue(papszOpenOptions, "ALT");
203 22 : if (pszAlt == nullptr &&
204 11 : (m_iAltField =
205 22 : m_poRawFeatureDefn->GetFieldIndex("Altitude")) >= 0 &&
206 11 : m_poRawFeatureDefn->GetFieldDefn(m_iAltField)->GetType() ==
207 : OFTReal)
208 : {
209 11 : pszAlt = "Altitude";
210 : }
211 : else
212 : {
213 0 : m_iAltField = -1;
214 : }
215 11 : if (pszAlt && !EQUAL(pszAlt, ""))
216 : {
217 11 : m_iAltField = m_poRawFeatureDefn->GetFieldIndex(pszAlt);
218 11 : if (m_iAltField < 0)
219 : {
220 0 : CPLError(CE_Warning, CPLE_AppDefined,
221 : "Unknown field %s", pszAlt);
222 : }
223 22 : else if (m_poRawFeatureDefn->GetFieldDefn(m_iAltField)
224 11 : ->GetType() != OFTReal)
225 : {
226 0 : CPLError(CE_Warning, CPLE_AppDefined,
227 : "The %s field should be of type Real", pszAlt);
228 0 : m_iAltField = -1;
229 : }
230 : }
231 11 : m_poFeatureDefn->SetGeomType(m_iAltField >= 0 ? wkbPoint25D
232 11 : : wkbPoint);
233 : }
234 : }
235 : }
236 :
237 948 : for (int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
238 : {
239 851 : if (!m_bKeepGeomColmuns && (i == m_iWKT || i == m_iLatField ||
240 797 : i == m_iLongField || i == m_iAltField))
241 : {
242 : // do nothing;
243 : }
244 : else
245 : {
246 775 : m_poFeatureDefn->AddFieldDefn(m_poRawFeatureDefn->GetFieldDefn(i));
247 : }
248 : }
249 97 : }
250 :
251 : /************************************************************************/
252 : /* AddGeometryFromFields() */
253 : /************************************************************************/
254 :
255 869 : OGRFeature *PDS4TableBaseLayer::AddGeometryFromFields(OGRFeature *poRawFeature)
256 : {
257 869 : OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
258 869 : poFeature->SetFID(poRawFeature->GetFID());
259 11793 : for (int i = 0, j = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
260 : {
261 10924 : if (!m_bKeepGeomColmuns && (i == m_iWKT || i == m_iLatField ||
262 10070 : i == m_iLongField || i == m_iAltField))
263 : {
264 : // do nothing;
265 : }
266 : else
267 : {
268 9272 : poFeature->SetField(j, poRawFeature->GetRawFieldRef(i));
269 9272 : j++;
270 : }
271 : }
272 :
273 869 : if (m_iWKT >= 0)
274 : {
275 455 : const char *pszWKT = poRawFeature->GetFieldAsString(m_iWKT);
276 455 : if (pszWKT && pszWKT[0] != '\0')
277 : {
278 439 : OGRGeometry *poGeom = nullptr;
279 439 : OGRGeometryFactory::createFromWkt(pszWKT, nullptr, &poGeom);
280 439 : if (poGeom)
281 : {
282 439 : poGeom->assignSpatialReference(GetSpatialRef());
283 439 : poFeature->SetGeometryDirectly(poGeom);
284 : }
285 : }
286 : }
287 399 : else if (m_iLatField >= 0 && m_iLongField >= 0 &&
288 1210 : poRawFeature->IsFieldSetAndNotNull(m_iLatField) &&
289 397 : poRawFeature->IsFieldSetAndNotNull(m_iLongField))
290 : {
291 397 : double dfLat = poRawFeature->GetFieldAsDouble(m_iLatField);
292 397 : double dfLong = poRawFeature->GetFieldAsDouble(m_iLongField);
293 : OGRPoint *poPoint;
294 397 : if (m_iAltField >= 0 && poRawFeature->IsFieldSetAndNotNull(m_iAltField))
295 : {
296 397 : double dfAlt = poRawFeature->GetFieldAsDouble(m_iAltField);
297 397 : poPoint = new OGRPoint(dfLong, dfLat, dfAlt);
298 : }
299 : else
300 : {
301 0 : poPoint = new OGRPoint(dfLong, dfLat);
302 : }
303 397 : poPoint->assignSpatialReference(GetSpatialRef());
304 397 : poFeature->SetGeometryDirectly(poPoint);
305 : }
306 869 : return poFeature;
307 : }
308 :
309 : /************************************************************************/
310 : /* AddFieldsFromGeometry() */
311 : /************************************************************************/
312 :
313 119 : OGRFeature *PDS4TableBaseLayer::AddFieldsFromGeometry(OGRFeature *poFeature)
314 : {
315 119 : OGRFeature *poRawFeature = new OGRFeature(m_poRawFeatureDefn);
316 938 : for (int i = 0, j = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
317 : {
318 819 : if (!m_bKeepGeomColmuns && (i == m_iWKT || i == m_iLatField ||
319 715 : i == m_iLongField || i == m_iAltField))
320 : {
321 : // do nothing;
322 : }
323 : else
324 : {
325 689 : poRawFeature->SetField(i, poFeature->GetRawFieldRef(j));
326 689 : j++;
327 : }
328 : }
329 :
330 119 : auto poGeom = poFeature->GetGeometryRef();
331 119 : if (poGeom)
332 : {
333 84 : if (m_iLongField >= 0 && m_iLatField >= 0 &&
334 12 : wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
335 : {
336 12 : auto poPoint = poGeom->toPoint();
337 12 : poRawFeature->SetField(m_iLongField, poPoint->getX());
338 12 : poRawFeature->SetField(m_iLatField, poPoint->getY());
339 12 : if (m_iAltField >= 0 && poGeom->getGeometryType() == wkbPoint25D)
340 : {
341 12 : poRawFeature->SetField(m_iAltField, poPoint->getZ());
342 : }
343 : }
344 60 : else if (m_iWKT >= 0)
345 : {
346 60 : char *pszWKT = nullptr;
347 60 : poGeom->exportToWkt(&pszWKT);
348 60 : if (pszWKT)
349 : {
350 60 : poRawFeature->SetField(m_iWKT, pszWKT);
351 : }
352 60 : CPLFree(pszWKT);
353 : }
354 : }
355 119 : return poRawFeature;
356 : }
357 :
358 : /************************************************************************/
359 : /* MarkHeaderDirty() */
360 : /************************************************************************/
361 :
362 338 : void PDS4TableBaseLayer::MarkHeaderDirty()
363 : {
364 338 : m_bDirtyHeader = true;
365 338 : m_poDS->MarkHeaderDirty();
366 338 : }
367 :
368 : /************************************************************************/
369 : /* RefreshFileAreaObservationalBeginningCommon() */
370 : /************************************************************************/
371 :
372 69 : CPLXMLNode *PDS4TableBaseLayer::RefreshFileAreaObservationalBeginningCommon(
373 : CPLXMLNode *psFAO, const CPLString &osPrefix, const char *pszTableEltName,
374 : CPLString &osDescription)
375 : {
376 69 : CPLXMLNode *psFile = CPLGetXMLNode(psFAO, (osPrefix + "File").c_str());
377 69 : CPLAssert(psFile);
378 : CPLXMLNode *psfile_size =
379 69 : CPLGetXMLNode(psFile, (osPrefix + "file_size").c_str());
380 69 : if (psfile_size)
381 : {
382 3 : CPLRemoveXMLChild(psFile, psfile_size);
383 3 : CPLDestroyXMLNode(psfile_size);
384 : }
385 :
386 69 : CPLXMLNode *psHeader = CPLGetXMLNode(psFAO, (osPrefix + "Header").c_str());
387 69 : if (psHeader)
388 : {
389 3 : CPLRemoveXMLChild(psFAO, psHeader);
390 3 : CPLDestroyXMLNode(psHeader);
391 : }
392 :
393 138 : CPLString osTableEltName(osPrefix + pszTableEltName);
394 69 : CPLXMLNode *psTable = CPLGetXMLNode(psFAO, osTableEltName);
395 138 : CPLString osName;
396 69 : CPLString osLocalIdentifier;
397 69 : if (psTable)
398 : {
399 4 : osName = CPLGetXMLValue(psTable, (osPrefix + "name").c_str(), "");
400 : osLocalIdentifier = CPLGetXMLValue(
401 4 : psTable, (osPrefix + "local_identifier").c_str(), "");
402 : osDescription =
403 4 : CPLGetXMLValue(psTable, (osPrefix + "description").c_str(), "");
404 4 : CPLRemoveXMLChild(psFAO, psTable);
405 4 : CPLDestroyXMLNode(psTable);
406 : }
407 :
408 : // Write Table_Delimited/Table_Character/Table_Binary
409 69 : psTable = CPLCreateXMLNode(psFAO, CXT_Element, osTableEltName);
410 69 : if (!osName.empty())
411 3 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "name").c_str(),
412 : osName);
413 69 : if (osLocalIdentifier.empty())
414 : {
415 : // Make a valid NCName
416 69 : osLocalIdentifier = GetName();
417 69 : if (isdigit(static_cast<unsigned char>(osLocalIdentifier[0])))
418 : {
419 4 : osLocalIdentifier = '_' + osLocalIdentifier;
420 : }
421 760 : for (char &ch : osLocalIdentifier)
422 : {
423 691 : if (!isalnum(static_cast<unsigned char>(ch)) &&
424 76 : static_cast<unsigned>(ch) <= 127)
425 76 : ch = '_';
426 : }
427 : }
428 138 : CPLCreateXMLElementAndValue(
429 138 : psTable, (osPrefix + "local_identifier").c_str(), osLocalIdentifier);
430 :
431 : CPLXMLNode *psOffset =
432 69 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "offset").c_str(),
433 : CPLSPrintf(CPL_FRMT_GUIB, m_nOffset));
434 69 : CPLAddXMLAttributeAndValue(psOffset, "unit", "byte");
435 :
436 138 : return psTable;
437 : }
438 :
439 : /************************************************************************/
440 : /* ParseLineEndingOption() */
441 : /************************************************************************/
442 :
443 63 : void PDS4TableBaseLayer::ParseLineEndingOption(CSLConstList papszOptions)
444 : {
445 : const char *pszLineEnding =
446 63 : CSLFetchNameValueDef(papszOptions, "LINE_ENDING", "CRLF");
447 63 : if (EQUAL(pszLineEnding, "CRLF"))
448 : {
449 59 : m_osLineEnding = "\r\n";
450 : }
451 4 : else if (EQUAL(pszLineEnding, "LF"))
452 : {
453 2 : m_osLineEnding = "\n";
454 : }
455 : else
456 : {
457 2 : m_osLineEnding = "\r\n";
458 2 : CPLError(CE_Warning, CPLE_AppDefined,
459 : "Unhandled value for LINE_ENDING");
460 : }
461 63 : }
462 :
463 : /************************************************************************/
464 : /* GetDataset() */
465 : /************************************************************************/
466 :
467 17 : GDALDataset *PDS4TableBaseLayer::GetDataset()
468 : {
469 17 : return m_poDS;
470 : }
471 :
472 : /************************************************************************/
473 : /* ==================================================================== */
474 : /* PDS4FixedWidthTable */
475 : /* ==================================================================== */
476 : /************************************************************************/
477 :
478 46 : PDS4FixedWidthTable::PDS4FixedWidthTable(PDS4Dataset *poDS, const char *pszName,
479 46 : const char *pszFilename)
480 46 : : PDS4TableBaseLayer(poDS, pszName, pszFilename)
481 : {
482 46 : }
483 :
484 : /************************************************************************/
485 : /* ResetReading() */
486 : /************************************************************************/
487 :
488 227 : void PDS4FixedWidthTable::ResetReading()
489 : {
490 227 : m_nFID = 1;
491 227 : }
492 :
493 : /************************************************************************/
494 : /* GetNextFeature() */
495 : /************************************************************************/
496 :
497 470 : OGRFeature *PDS4FixedWidthTable::GetNextFeature()
498 : {
499 : while (true)
500 : {
501 470 : auto poFeature = GetFeature(m_nFID);
502 470 : if (poFeature == nullptr)
503 : {
504 77 : return nullptr;
505 : }
506 393 : ++m_nFID;
507 :
508 896 : if ((m_poFilterGeom == nullptr ||
509 746 : FilterGeometry(poFeature->GetGeometryRef())) &&
510 353 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
511 : {
512 353 : return poFeature;
513 : }
514 40 : delete poFeature;
515 40 : }
516 : }
517 :
518 : /************************************************************************/
519 : /* TestCapability() */
520 : /************************************************************************/
521 :
522 222 : int PDS4FixedWidthTable::TestCapability(const char *pszCap) const
523 : {
524 222 : if (EQUAL(pszCap, OLCRandomRead) || EQUAL(pszCap, OLCStringsAsUTF8) ||
525 196 : EQUAL(pszCap, OLCZGeometries))
526 : {
527 32 : return true;
528 : }
529 190 : if (EQUAL(pszCap, OLCFastFeatureCount))
530 : {
531 0 : return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
532 : }
533 190 : if (EQUAL(pszCap, OLCCreateField))
534 : {
535 98 : return m_poDS->GetAccess() == GA_Update && m_nFeatureCount == 0;
536 : }
537 92 : if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCRandomWrite))
538 : {
539 34 : return m_poDS->GetAccess() == GA_Update;
540 : }
541 58 : return false;
542 : }
543 :
544 : /************************************************************************/
545 : /* ISetFeature() */
546 : /************************************************************************/
547 :
548 22 : OGRErr PDS4FixedWidthTable::ISetFeature(OGRFeature *poFeature)
549 : {
550 22 : if (poFeature->GetFID() <= 0 || poFeature->GetFID() > m_nFeatureCount)
551 : {
552 0 : return OGRERR_NON_EXISTING_FEATURE;
553 : }
554 22 : if (m_poDS->GetAccess() != GA_Update)
555 : {
556 0 : CPLError(CE_Failure, CPLE_AppDefined,
557 : "Dataset opened in read-only mode");
558 0 : return OGRERR_FAILURE;
559 : }
560 22 : CPLAssert(static_cast<int>(m_osBuffer.size()) == m_nRecordSize);
561 22 : CPLAssert(m_nRecordSize > static_cast<int>(m_osLineEnding.size()));
562 :
563 22 : VSIFSeekL(m_fp, m_nOffset + (poFeature->GetFID() - 1) * m_nRecordSize,
564 : SEEK_SET);
565 22 : memset(&m_osBuffer[0], ' ', m_nRecordSize);
566 :
567 22 : OGRFeature *poRawFeature = AddFieldsFromGeometry(poFeature);
568 347 : for (int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
569 : {
570 325 : if (!poRawFeature->IsFieldSetAndNotNull(i))
571 : {
572 21 : continue;
573 : }
574 608 : CPLString osBuffer;
575 304 : const CPLString &osDT(m_aoFields[i].m_osDataType);
576 304 : const auto eType(m_poRawFeatureDefn->GetFieldDefn(i)->GetType());
577 304 : if (osDT == "ASCII_Real")
578 : {
579 456 : CPLString osFormat;
580 228 : osFormat.Printf("%%.%dg", m_aoFields[i].m_nLength - 2);
581 : osBuffer.Printf(osFormat.c_str(),
582 228 : poRawFeature->GetFieldAsDouble(i));
583 : }
584 143 : else if (osDT == "ASCII_Integer" ||
585 143 : osDT == "ASCII_NonNegative_Integer" || eType == OFTString)
586 : {
587 17 : osBuffer = poRawFeature->GetFieldAsString(i);
588 : }
589 59 : else if (osDT == "ASCII_Boolean")
590 : {
591 8 : osBuffer = poRawFeature->GetFieldAsInteger(i) == 1 ? "1" : "0";
592 : }
593 51 : else if (osDT == "IEEE754LSBDouble")
594 : {
595 5 : double dfVal = poRawFeature->GetFieldAsDouble(i);
596 5 : CPL_LSBPTR64(&dfVal);
597 5 : osBuffer.resize(sizeof(dfVal));
598 5 : memcpy(&osBuffer[0], &dfVal, sizeof(dfVal));
599 : }
600 46 : else if (osDT == "IEEE754MSBDouble")
601 : {
602 2 : double dfVal = poRawFeature->GetFieldAsDouble(i);
603 2 : CPL_MSBPTR64(&dfVal);
604 2 : osBuffer.resize(sizeof(dfVal));
605 2 : memcpy(&osBuffer[0], &dfVal, sizeof(dfVal));
606 : }
607 44 : else if (osDT == "IEEE754LSBSingle")
608 : {
609 2 : float fVal = static_cast<float>(poRawFeature->GetFieldAsDouble(i));
610 2 : CPL_LSBPTR32(&fVal);
611 2 : osBuffer.resize(sizeof(fVal));
612 2 : memcpy(&osBuffer[0], &fVal, sizeof(fVal));
613 : }
614 42 : else if (osDT == "IEEE754MSBSingle")
615 : {
616 2 : float fVal = static_cast<float>(poRawFeature->GetFieldAsDouble(i));
617 2 : CPL_MSBPTR32(&fVal);
618 2 : osBuffer.resize(sizeof(fVal));
619 2 : memcpy(&osBuffer[0], &fVal, sizeof(fVal));
620 : }
621 40 : else if (osDT == "SignedByte")
622 : {
623 2 : signed char bVal = static_cast<signed char>(std::max(
624 2 : -128, std::min(127, poRawFeature->GetFieldAsInteger(i))));
625 2 : osBuffer.resize(sizeof(bVal));
626 2 : memcpy(&osBuffer[0], &bVal, sizeof(bVal));
627 : }
628 38 : else if (osDT == "UnsignedByte")
629 : {
630 : GByte ubVal = static_cast<GByte>(
631 2 : std::max(0, std::min(255, poRawFeature->GetFieldAsInteger(i))));
632 2 : osBuffer.resize(sizeof(ubVal));
633 2 : memcpy(&osBuffer[0], &ubVal, sizeof(ubVal));
634 : }
635 36 : else if (osDT == "SignedLSB2")
636 : {
637 1 : GInt16 sVal = static_cast<GInt16>(std::max(
638 1 : -32768, std::min(32767, poRawFeature->GetFieldAsInteger(i))));
639 1 : CPL_LSBPTR16(&sVal);
640 1 : osBuffer.resize(sizeof(sVal));
641 1 : memcpy(&osBuffer[0], &sVal, sizeof(sVal));
642 : }
643 35 : else if (osDT == "SignedMSB2")
644 : {
645 1 : GInt16 sVal = static_cast<GInt16>(std::max(
646 1 : -32768, std::min(32767, poRawFeature->GetFieldAsInteger(i))));
647 1 : CPL_MSBPTR16(&sVal);
648 1 : osBuffer.resize(sizeof(sVal));
649 1 : memcpy(&osBuffer[0], &sVal, sizeof(sVal));
650 : }
651 34 : else if (osDT == "UnsignedLSB2")
652 : {
653 1 : GUInt16 usVal = static_cast<GUInt16>(std::max(
654 1 : 0, std::min(65535, poRawFeature->GetFieldAsInteger(i))));
655 1 : CPL_LSBPTR16(&usVal);
656 1 : osBuffer.resize(sizeof(usVal));
657 1 : memcpy(&osBuffer[0], &usVal, sizeof(usVal));
658 : }
659 33 : else if (osDT == "UnsignedMSB2")
660 : {
661 1 : GUInt16 usVal = static_cast<GUInt16>(std::max(
662 1 : 0, std::min(65535, poRawFeature->GetFieldAsInteger(i))));
663 1 : CPL_MSBPTR16(&usVal);
664 1 : osBuffer.resize(sizeof(usVal));
665 1 : memcpy(&osBuffer[0], &usVal, sizeof(usVal));
666 : }
667 32 : else if (osDT == "SignedLSB4")
668 : {
669 1 : GInt32 nVal = poRawFeature->GetFieldAsInteger(i);
670 1 : CPL_LSBPTR32(&nVal);
671 1 : osBuffer.resize(sizeof(nVal));
672 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
673 : }
674 31 : else if (osDT == "SignedMSB4")
675 : {
676 1 : GInt32 nVal = poRawFeature->GetFieldAsInteger(i);
677 1 : CPL_MSBPTR32(&nVal);
678 1 : osBuffer.resize(sizeof(nVal));
679 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
680 : }
681 30 : else if (osDT == "UnsignedLSB4")
682 : {
683 1 : GUInt32 nVal = static_cast<GUInt32>(
684 1 : std::max(0, poRawFeature->GetFieldAsInteger(i)));
685 1 : CPL_LSBPTR32(&nVal);
686 1 : osBuffer.resize(sizeof(nVal));
687 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
688 : }
689 29 : else if (osDT == "UnsignedMSB4")
690 : {
691 1 : GUInt32 nVal = static_cast<GUInt32>(
692 1 : std::max(0, poRawFeature->GetFieldAsInteger(i)));
693 1 : CPL_MSBPTR32(&nVal);
694 1 : osBuffer.resize(sizeof(nVal));
695 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
696 : }
697 28 : else if (osDT == "SignedLSB8")
698 : {
699 1 : GInt64 nVal = poRawFeature->GetFieldAsInteger64(i);
700 1 : CPL_LSBPTR64(&nVal);
701 1 : osBuffer.resize(sizeof(nVal));
702 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
703 : }
704 27 : else if (osDT == "SignedMSB8")
705 : {
706 1 : GInt64 nVal = poRawFeature->GetFieldAsInteger64(i);
707 1 : CPL_MSBPTR64(&nVal);
708 1 : osBuffer.resize(sizeof(nVal));
709 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
710 : }
711 26 : else if (osDT == "UnsignedLSB8")
712 : {
713 1 : GUInt64 nVal = static_cast<GUInt64>(std::max(
714 1 : static_cast<GIntBig>(0), poRawFeature->GetFieldAsInteger64(i)));
715 1 : CPL_LSBPTR64(&nVal);
716 1 : osBuffer.resize(sizeof(nVal));
717 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
718 : }
719 25 : else if (osDT == "UnsignedMSB8")
720 : {
721 1 : GUInt64 nVal = static_cast<GUInt64>(std::max(
722 1 : static_cast<GIntBig>(0), poRawFeature->GetFieldAsInteger64(i)));
723 1 : CPL_MSBPTR64(&nVal);
724 1 : osBuffer.resize(sizeof(nVal));
725 1 : memcpy(&osBuffer[0], &nVal, sizeof(nVal));
726 : }
727 40 : else if (osDT == "ASCII_Date_Time_YMD" ||
728 16 : osDT == "ASCII_Date_Time_YMD_UTC")
729 : {
730 : char *pszDateTime =
731 8 : OGRGetXMLDateTime(poRawFeature->GetRawFieldRef(i));
732 8 : osBuffer = pszDateTime;
733 8 : CPLFree(pszDateTime);
734 : }
735 16 : else if (osDT == "ASCII_Date_YMD")
736 : {
737 : int nYear, nMonth, nDay;
738 8 : poRawFeature->GetFieldAsDateTime(
739 : i, &nYear, &nMonth, &nDay, nullptr, nullptr,
740 : static_cast<float *>(nullptr), nullptr);
741 8 : osBuffer.Printf("%04d-%02d-%02d", nYear, nMonth, nDay);
742 : }
743 8 : else if (osDT == "ASCII_Time")
744 : {
745 : int nHour, nMin;
746 : float fSec;
747 8 : poRawFeature->GetFieldAsDateTime(i, nullptr, nullptr, nullptr,
748 : &nHour, &nMin, &fSec, nullptr);
749 8 : osBuffer.Printf("%02d:%02d:%05.3f", nHour, nMin, fSec);
750 : }
751 :
752 608 : if (!osBuffer.empty() &&
753 304 : osBuffer.size() <= static_cast<size_t>(m_aoFields[i].m_nLength))
754 : {
755 304 : memcpy(&m_osBuffer[m_aoFields[i].m_nOffset +
756 304 : m_aoFields[i].m_nLength - osBuffer.size()],
757 304 : &osBuffer[0], osBuffer.size());
758 : }
759 0 : else if (!osBuffer.empty())
760 : {
761 0 : if (eType == OFTString)
762 : {
763 0 : CPLError(CE_Warning, CPLE_AppDefined,
764 : "Value %s for field %s is too large. Truncating it",
765 : osBuffer.c_str(),
766 0 : m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());
767 0 : memcpy(&m_osBuffer[m_aoFields[i].m_nOffset], osBuffer.data(),
768 0 : m_aoFields[i].m_nLength);
769 : }
770 : else
771 : {
772 0 : CPLError(CE_Warning, CPLE_AppDefined,
773 : "Value %s for field %s is too large. Omitting i",
774 : osBuffer.c_str(),
775 0 : m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());
776 : }
777 : }
778 : }
779 22 : delete poRawFeature;
780 :
781 22 : if (!m_osLineEnding.empty())
782 : {
783 34 : memcpy(&m_osBuffer[m_osBuffer.size() - m_osLineEnding.size()],
784 17 : m_osLineEnding.data(), m_osLineEnding.size());
785 : }
786 :
787 22 : if (VSIFWriteL(&m_osBuffer[0], m_nRecordSize, 1, m_fp) != 1)
788 : {
789 0 : return OGRERR_FAILURE;
790 : }
791 :
792 22 : return OGRERR_NONE;
793 : }
794 :
795 : /************************************************************************/
796 : /* ICreateFeature() */
797 : /************************************************************************/
798 :
799 22 : OGRErr PDS4FixedWidthTable::ICreateFeature(OGRFeature *poFeature)
800 : {
801 22 : m_nFeatureCount++;
802 22 : poFeature->SetFID(m_nFeatureCount);
803 22 : OGRErr eErr = ISetFeature(poFeature);
804 22 : if (eErr == OGRERR_NONE)
805 : {
806 22 : MarkHeaderDirty();
807 : }
808 : else
809 : {
810 0 : poFeature->SetFID(-1);
811 0 : m_nFeatureCount--;
812 : }
813 22 : return eErr;
814 : }
815 :
816 : /************************************************************************/
817 : /* GetFeature() */
818 : /************************************************************************/
819 :
820 499 : OGRFeature *PDS4FixedWidthTable::GetFeature(GIntBig nFID)
821 : {
822 499 : if (nFID <= 0 || nFID > m_nFeatureCount)
823 : {
824 89 : return nullptr;
825 : }
826 410 : VSIFSeekL(m_fp, m_nOffset + (nFID - 1) * m_nRecordSize, SEEK_SET);
827 410 : if (VSIFReadL(&m_osBuffer[0], m_nRecordSize, 1, m_fp) != 1)
828 : {
829 0 : return nullptr;
830 : }
831 410 : OGRFeature *poRawFeature = new OGRFeature(m_poRawFeatureDefn);
832 410 : poRawFeature->SetFID(nFID);
833 9448 : for (int i = 0; i < poRawFeature->GetFieldCount(); i++)
834 : {
835 9038 : CPLString osVal(m_osBuffer.substr(m_aoFields[i].m_nOffset,
836 18076 : m_aoFields[i].m_nLength));
837 :
838 9038 : const CPLString &osDT(m_aoFields[i].m_osDataType);
839 9038 : if (STARTS_WITH(osDT, "ASCII_") || STARTS_WITH(osDT, "UTF8_"))
840 : {
841 8779 : osVal.Trim();
842 8779 : if (osVal.empty())
843 : {
844 42 : continue;
845 : }
846 : }
847 :
848 8996 : if (osDT == "IEEE754LSBDouble")
849 : {
850 : double dfVal;
851 5 : CPLAssert(osVal.size() == sizeof(dfVal));
852 5 : memcpy(&dfVal, osVal.data(), sizeof(dfVal));
853 5 : CPL_LSBPTR64(&dfVal);
854 5 : poRawFeature->SetField(i, dfVal);
855 : }
856 8991 : else if (osDT == "IEEE754MSBDouble")
857 : {
858 : double dfVal;
859 2 : CPLAssert(osVal.size() == sizeof(dfVal));
860 2 : memcpy(&dfVal, osVal.data(), sizeof(dfVal));
861 2 : CPL_MSBPTR64(&dfVal);
862 2 : poRawFeature->SetField(i, dfVal);
863 : }
864 8989 : else if (osDT == "IEEE754LSBSingle")
865 : {
866 : float fVal;
867 2 : CPLAssert(osVal.size() == sizeof(fVal));
868 2 : memcpy(&fVal, osVal.data(), sizeof(fVal));
869 2 : CPL_LSBPTR32(&fVal);
870 2 : poRawFeature->SetField(i, fVal);
871 : }
872 8987 : else if (osDT == "IEEE754MSBSingle")
873 : {
874 : float fVal;
875 2 : CPLAssert(osVal.size() == sizeof(fVal));
876 2 : memcpy(&fVal, osVal.data(), sizeof(fVal));
877 2 : CPL_MSBPTR32(&fVal);
878 2 : poRawFeature->SetField(i, fVal);
879 : }
880 8985 : else if (osDT == "SignedByte")
881 : {
882 : signed char bVal;
883 2 : CPLAssert(osVal.size() == sizeof(bVal));
884 2 : memcpy(&bVal, osVal.data(), sizeof(bVal));
885 2 : poRawFeature->SetField(i, bVal);
886 : }
887 8983 : else if (osDT == "UnsignedByte")
888 : {
889 : GByte bVal;
890 2 : CPLAssert(osVal.size() == sizeof(bVal));
891 2 : memcpy(&bVal, osVal.data(), sizeof(bVal));
892 2 : poRawFeature->SetField(i, bVal);
893 : }
894 8981 : else if (osDT == "SignedLSB2")
895 : {
896 : GInt16 sVal;
897 1 : CPLAssert(osVal.size() == sizeof(sVal));
898 1 : memcpy(&sVal, osVal.data(), sizeof(sVal));
899 1 : CPL_LSBPTR16(&sVal);
900 1 : poRawFeature->SetField(i, sVal);
901 : }
902 8980 : else if (osDT == "SignedMSB2")
903 : {
904 : GInt16 sVal;
905 1 : CPLAssert(osVal.size() == sizeof(sVal));
906 1 : memcpy(&sVal, osVal.data(), sizeof(sVal));
907 1 : CPL_MSBPTR16(&sVal);
908 1 : poRawFeature->SetField(i, sVal);
909 : }
910 8979 : else if (osDT == "UnsignedLSB2")
911 : {
912 : GUInt16 usVal;
913 1 : CPLAssert(osVal.size() == sizeof(usVal));
914 1 : memcpy(&usVal, osVal.data(), sizeof(usVal));
915 1 : CPL_LSBPTR16(&usVal);
916 1 : poRawFeature->SetField(i, usVal);
917 : }
918 8978 : else if (osDT == "UnsignedMSB2")
919 : {
920 : GUInt16 usVal;
921 232 : CPLAssert(osVal.size() == sizeof(usVal));
922 232 : memcpy(&usVal, osVal.data(), sizeof(usVal));
923 232 : CPL_MSBPTR16(&usVal);
924 232 : poRawFeature->SetField(i, usVal);
925 : }
926 8746 : else if (osDT == "SignedLSB4")
927 : {
928 : GInt32 nVal;
929 1 : CPLAssert(osVal.size() == sizeof(nVal));
930 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
931 1 : CPL_LSBPTR32(&nVal);
932 1 : poRawFeature->SetField(i, nVal);
933 : }
934 8745 : else if (osDT == "SignedMSB4")
935 : {
936 : GInt32 nVal;
937 1 : CPLAssert(osVal.size() == sizeof(nVal));
938 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
939 1 : CPL_MSBPTR32(&nVal);
940 1 : poRawFeature->SetField(i, nVal);
941 : }
942 8744 : else if (osDT == "UnsignedLSB4")
943 : {
944 : GUInt32 nVal;
945 1 : CPLAssert(osVal.size() == sizeof(nVal));
946 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
947 1 : CPL_LSBPTR32(&nVal);
948 1 : poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
949 : }
950 8743 : else if (osDT == "UnsignedMSB4")
951 : {
952 : GUInt32 nVal;
953 2 : CPLAssert(osVal.size() == sizeof(nVal));
954 2 : memcpy(&nVal, osVal.data(), sizeof(nVal));
955 2 : CPL_MSBPTR32(&nVal);
956 2 : poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
957 : }
958 8741 : else if (osDT == "SignedLSB8")
959 : {
960 : GInt64 nVal;
961 1 : CPLAssert(osVal.size() == sizeof(nVal));
962 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
963 1 : CPL_LSBPTR64(&nVal);
964 1 : poRawFeature->SetField(i, nVal);
965 : }
966 8740 : else if (osDT == "SignedMSB8")
967 : {
968 : GInt64 nVal;
969 1 : CPLAssert(osVal.size() == sizeof(nVal));
970 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
971 1 : CPL_MSBPTR64(&nVal);
972 1 : poRawFeature->SetField(i, nVal);
973 : }
974 8739 : else if (osDT == "UnsignedLSB8")
975 : {
976 : GUInt64 nVal;
977 1 : CPLAssert(osVal.size() == sizeof(nVal));
978 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
979 1 : CPL_LSBPTR64(&nVal);
980 1 : poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
981 : }
982 8738 : else if (osDT == "UnsignedMSB8")
983 : {
984 : GUInt64 nVal;
985 1 : CPLAssert(osVal.size() == sizeof(nVal));
986 1 : memcpy(&nVal, osVal.data(), sizeof(nVal));
987 1 : CPL_MSBPTR64(&nVal);
988 1 : poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
989 : }
990 8737 : else if (osDT == "ASCII_Boolean")
991 : {
992 9 : poRawFeature->SetField(
993 9 : i, EQUAL(osVal, "t") || EQUAL(osVal, "1") ? 1 : 0);
994 : }
995 : else
996 : {
997 8728 : poRawFeature->SetField(i, osVal.c_str());
998 : }
999 : }
1000 410 : OGRFeature *poFeature = AddGeometryFromFields(poRawFeature);
1001 410 : delete poRawFeature;
1002 410 : return poFeature;
1003 : }
1004 :
1005 : /************************************************************************/
1006 : /* GetFieldTypeFromPDS4DataType() */
1007 : /************************************************************************/
1008 :
1009 851 : static OGRFieldType GetFieldTypeFromPDS4DataType(const char *pszDataType,
1010 : int nDTSize,
1011 : OGRFieldSubType &eSubType,
1012 : bool &error)
1013 : {
1014 851 : OGRFieldType eType = OFTString;
1015 851 : eSubType = OFSTNone;
1016 851 : error = false;
1017 851 : if (EQUAL(pszDataType, "ASCII_Boolean"))
1018 : {
1019 29 : eSubType = OFSTBoolean;
1020 29 : eType = OFTInteger;
1021 : }
1022 822 : else if (EQUAL(pszDataType, "ASCII_Date_Time_YMD") ||
1023 777 : EQUAL(pszDataType, "ASCII_Date_Time_YMD_UTC"))
1024 : {
1025 45 : eType = OFTDateTime;
1026 : }
1027 777 : else if (EQUAL(pszDataType, "ASCII_Date_YMD"))
1028 : {
1029 45 : eType = OFTDate;
1030 : }
1031 732 : else if (EQUAL(pszDataType, "ASCII_Integer") ||
1032 668 : EQUAL(pszDataType, "ASCII_NonNegative_Integer"))
1033 : {
1034 64 : eType = OFTInteger;
1035 : }
1036 668 : else if (EQUAL(pszDataType, "SignedByte") ||
1037 664 : EQUAL(pszDataType, "UnsignedByte"))
1038 : {
1039 9 : if (nDTSize != 1)
1040 0 : error = true;
1041 9 : eType = OFTInteger;
1042 : }
1043 659 : else if (EQUAL(pszDataType, "SignedLSB2") ||
1044 657 : EQUAL(pszDataType, "SignedMSB2"))
1045 : {
1046 4 : if (nDTSize != 2)
1047 0 : error = true;
1048 4 : eType = OFTInteger;
1049 4 : eSubType = OFSTInt16;
1050 : }
1051 655 : else if (EQUAL(pszDataType, "UnsignedLSB2") ||
1052 653 : EQUAL(pszDataType, "UnsignedMSB2"))
1053 : {
1054 236 : if (nDTSize != 2)
1055 0 : error = true;
1056 236 : eType = OFTInteger;
1057 : }
1058 419 : else if (EQUAL(pszDataType, "SignedLSB4") ||
1059 417 : EQUAL(pszDataType, "SignedMSB4"))
1060 : {
1061 4 : if (nDTSize != 4)
1062 0 : error = true;
1063 4 : eType = OFTInteger;
1064 : }
1065 415 : else if (EQUAL(pszDataType, "UnsignedLSB4") ||
1066 413 : EQUAL(pszDataType, "UnsignedMSB4"))
1067 : {
1068 6 : if (nDTSize != 4)
1069 0 : error = true;
1070 : // Use larger data type as > 2 billion values don't hold on signed int32
1071 6 : eType = OFTInteger64;
1072 : }
1073 409 : else if (EQUAL(pszDataType, "SignedLSB8") ||
1074 407 : EQUAL(pszDataType, "SignedMSB8"))
1075 : {
1076 4 : if (nDTSize != 8)
1077 0 : error = true;
1078 4 : eType = OFTInteger64;
1079 : }
1080 405 : else if (EQUAL(pszDataType, "UnsignedLSB8") ||
1081 403 : EQUAL(pszDataType, "UnsignedMSB8"))
1082 : {
1083 5 : if (nDTSize != 8)
1084 0 : error = true;
1085 : // Hope that we won't get value larger than > 2^63...
1086 5 : eType = OFTInteger64;
1087 : }
1088 400 : else if (EQUAL(pszDataType, "ASCII_Real"))
1089 : {
1090 240 : eType = OFTReal;
1091 : }
1092 160 : else if (EQUAL(pszDataType, "IEEE754LSBDouble") ||
1093 153 : EQUAL(pszDataType, "IEEE754MSBDouble"))
1094 : {
1095 12 : if (nDTSize != 8)
1096 0 : error = true;
1097 12 : eType = OFTReal;
1098 : }
1099 148 : else if (EQUAL(pszDataType, "IEEE754LSBSingle") ||
1100 144 : EQUAL(pszDataType, "IEEE754MSBSingle"))
1101 : {
1102 9 : if (nDTSize != 4)
1103 0 : error = true;
1104 9 : eType = OFTReal;
1105 9 : eSubType = OFSTFloat32;
1106 : }
1107 139 : else if (EQUAL(pszDataType, "ASCII_Time"))
1108 : {
1109 29 : eType = OFTTime;
1110 : }
1111 851 : return eType;
1112 : }
1113 :
1114 : /************************************************************************/
1115 : /* ReadTableDef() */
1116 : /************************************************************************/
1117 :
1118 33 : bool PDS4FixedWidthTable::ReadTableDef(const CPLXMLNode *psTable)
1119 : {
1120 33 : CPLAssert(m_fp == nullptr);
1121 33 : m_fp = VSIFOpenL(m_osFilename,
1122 33 : (m_poDS->GetAccess() == GA_ReadOnly) ? "rb" : "r+b");
1123 33 : if (!m_fp)
1124 : {
1125 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
1126 : m_osFilename.c_str());
1127 0 : return false;
1128 : }
1129 :
1130 33 : m_nOffset = static_cast<GUIntBig>(
1131 33 : CPLAtoGIntBig(CPLGetXMLValue(psTable, "offset", "0")));
1132 :
1133 33 : m_nFeatureCount = CPLAtoGIntBig(CPLGetXMLValue(psTable, "records", "-1"));
1134 :
1135 : const char *pszRecordDelimiter =
1136 33 : CPLGetXMLValue(psTable, "record_delimiter", "");
1137 33 : if (EQUAL(pszRecordDelimiter, "Carriage-Return Line-Feed"))
1138 20 : m_osLineEnding = "\r\n";
1139 13 : else if (EQUAL(pszRecordDelimiter, "Line-Feed"))
1140 2 : m_osLineEnding = "\n";
1141 11 : else if (EQUAL(pszRecordDelimiter, ""))
1142 : {
1143 11 : if (GetSubType() == "Character")
1144 : {
1145 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing record_delimiter");
1146 0 : return false;
1147 : }
1148 : }
1149 : else
1150 : {
1151 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid record_delimiter");
1152 0 : return false;
1153 : }
1154 :
1155 : const CPLXMLNode *psRecord =
1156 33 : CPLGetXMLNode(psTable, ("Record_" + GetSubType()).c_str());
1157 33 : if (!psRecord)
1158 : {
1159 0 : return false;
1160 : }
1161 33 : m_nRecordSize = atoi(CPLGetXMLValue(psRecord, "record_length", "0"));
1162 66 : if (m_nRecordSize <= static_cast<int>(m_osLineEnding.size()) ||
1163 33 : m_nRecordSize > 1000 * 1000)
1164 : {
1165 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid record_length");
1166 0 : return false;
1167 : }
1168 33 : m_osBuffer.resize(m_nRecordSize);
1169 33 : if (!ReadFields(psRecord, 0, ""))
1170 : {
1171 0 : return false;
1172 : }
1173 :
1174 33 : SetupGeomField();
1175 :
1176 33 : return true;
1177 : }
1178 :
1179 : /************************************************************************/
1180 : /* ReadFields() */
1181 : /************************************************************************/
1182 :
1183 264 : bool PDS4FixedWidthTable::ReadFields(const CPLXMLNode *psParent,
1184 : int nBaseOffset,
1185 : const CPLString &osSuffixFieldName)
1186 : {
1187 2367 : for (const CPLXMLNode *psIter = psParent->psChild; psIter;
1188 2103 : psIter = psIter->psNext)
1189 : {
1190 4206 : if (psIter->eType == CXT_Element &&
1191 4206 : strcmp(psIter->pszValue, ("Field_" + GetSubType()).c_str()) == 0)
1192 : {
1193 617 : const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
1194 617 : if (!pszName)
1195 : {
1196 0 : return false;
1197 : }
1198 : const char *pszLoc =
1199 617 : CPLGetXMLValue(psIter, "field_location", nullptr);
1200 617 : if (!pszLoc)
1201 : {
1202 0 : return false;
1203 : }
1204 : const char *pszDataType =
1205 617 : CPLGetXMLValue(psIter, "data_type", nullptr);
1206 617 : if (!pszDataType)
1207 : {
1208 0 : return false;
1209 : }
1210 : const char *pszFieldLength =
1211 617 : CPLGetXMLValue(psIter, "field_length", nullptr);
1212 617 : if (!pszFieldLength)
1213 : {
1214 0 : return false;
1215 : }
1216 617 : Field f;
1217 617 : f.m_nOffset =
1218 617 : nBaseOffset + atoi(pszLoc) - 1; // Location is 1-based
1219 617 : if (f.m_nOffset < 0 || f.m_nOffset >= m_nRecordSize)
1220 : {
1221 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid field_location");
1222 0 : return false;
1223 : }
1224 617 : f.m_nLength = atoi(pszFieldLength);
1225 1234 : if (f.m_nLength <= 0 ||
1226 1234 : f.m_nLength > m_nRecordSize -
1227 617 : static_cast<int>(m_osLineEnding.size()) -
1228 617 : f.m_nOffset)
1229 : {
1230 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid field_length");
1231 0 : return false;
1232 : }
1233 617 : f.m_osDataType = pszDataType;
1234 617 : f.m_osUnit = CPLGetXMLValue(psIter, "unit", "");
1235 617 : f.m_osDescription = CPLGetXMLValue(psIter, "description", "");
1236 :
1237 : const char *pszFieldFormat =
1238 617 : CPLGetXMLValue(psIter, "field_format", "");
1239 :
1240 : CPLXMLNode *psSpecialConstants = const_cast<CPLXMLNode *>(
1241 617 : CPLGetXMLNode(psIter, "Special_Constants"));
1242 617 : if (psSpecialConstants)
1243 : {
1244 9 : auto psNext = psSpecialConstants->psNext;
1245 9 : psSpecialConstants->psNext = nullptr;
1246 9 : char *pszXML = CPLSerializeXMLTree(psSpecialConstants);
1247 9 : psSpecialConstants->psNext = psNext;
1248 9 : if (pszXML)
1249 : {
1250 9 : f.m_osSpecialConstantsXML = pszXML;
1251 9 : CPLFree(pszXML);
1252 : }
1253 : }
1254 :
1255 617 : m_aoFields.push_back(f);
1256 :
1257 617 : OGRFieldSubType eSubType = OFSTNone;
1258 617 : bool error = false;
1259 617 : auto eType = GetFieldTypeFromPDS4DataType(pszDataType, f.m_nLength,
1260 : eSubType, error);
1261 617 : if (error)
1262 : {
1263 0 : CPLError(CE_Failure, CPLE_AppDefined,
1264 : "Inconsistent field_length w.r.t datatype");
1265 0 : return false;
1266 : }
1267 658 : if (STARTS_WITH(f.m_osDataType, "ASCII_") && eType == OFTInteger &&
1268 41 : f.m_nLength >= 10)
1269 : {
1270 22 : eType = OFTInteger64;
1271 : }
1272 617 : OGRFieldDefn oFieldDefn((pszName + osSuffixFieldName).c_str(),
1273 1234 : eType);
1274 617 : oFieldDefn.SetSubType(eSubType);
1275 904 : if (eType != OFTReal && (STARTS_WITH(f.m_osDataType, "ASCII_") ||
1276 287 : STARTS_WITH(f.m_osDataType, "UTF_8")))
1277 : {
1278 98 : oFieldDefn.SetWidth(f.m_nLength);
1279 : }
1280 519 : else if ((eType == OFTInteger || eType == OFTInteger64) &&
1281 268 : pszFieldFormat && pszFieldFormat[0] == '%' &&
1282 9 : pszFieldFormat[strlen(pszFieldFormat) - 1] == 'd')
1283 : {
1284 9 : oFieldDefn.SetWidth(atoi(pszFieldFormat + 1));
1285 : }
1286 617 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
1287 : }
1288 2972 : else if (psIter->eType == CXT_Element &&
1289 1486 : strcmp(psIter->pszValue,
1290 2972 : ("Group_Field_" + GetSubType()).c_str()) == 0)
1291 : {
1292 : const char *pszRepetitions =
1293 1 : CPLGetXMLValue(psIter, "repetitions", nullptr);
1294 1 : if (!pszRepetitions)
1295 : {
1296 0 : return false;
1297 : }
1298 : const char *pszGroupLocation =
1299 1 : CPLGetXMLValue(psIter, "group_location", nullptr);
1300 1 : if (!pszGroupLocation)
1301 : {
1302 0 : return false;
1303 : }
1304 : const char *pszGroupLength =
1305 1 : CPLGetXMLValue(psIter, "group_length", nullptr);
1306 1 : if (!pszGroupLength)
1307 : {
1308 0 : return false;
1309 : }
1310 1 : int nRepetitions = std::min(1000, atoi(pszRepetitions));
1311 1 : if (nRepetitions <= 0)
1312 : {
1313 0 : return false;
1314 : }
1315 1 : int nGroupOffset =
1316 1 : atoi(pszGroupLocation) - 1; // Location is 1-based
1317 1 : if (nGroupOffset < 0 || nGroupOffset >= m_nRecordSize)
1318 : {
1319 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid group_location");
1320 0 : return false;
1321 : }
1322 1 : int nGroupLength = atoi(pszGroupLength);
1323 2 : if (nGroupLength <= 0 ||
1324 2 : nGroupLength > m_nRecordSize -
1325 1 : static_cast<int>(m_osLineEnding.size()) -
1326 2 : nGroupOffset ||
1327 1 : (nGroupLength % nRepetitions) != 0)
1328 : {
1329 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid group_length");
1330 0 : return false;
1331 : }
1332 1 : int nGroupOneRepetitionLength = nGroupLength / nRepetitions;
1333 232 : for (int i = 0; i < nRepetitions; i++)
1334 : {
1335 462 : if (!ReadFields(
1336 231 : psIter, nGroupOffset + i * nGroupOneRepetitionLength,
1337 462 : osSuffixFieldName + "_" + CPLSPrintf("%d", i + 1)))
1338 : {
1339 0 : return false;
1340 : }
1341 : }
1342 : }
1343 : }
1344 264 : return true;
1345 : }
1346 :
1347 : /************************************************************************/
1348 : /* RefreshFileAreaObservational() */
1349 : /************************************************************************/
1350 :
1351 14 : void PDS4FixedWidthTable::RefreshFileAreaObservational(CPLXMLNode *psFAO)
1352 : {
1353 28 : CPLString osPrefix;
1354 14 : if (STARTS_WITH(psFAO->pszValue, "pds:"))
1355 0 : osPrefix = "pds:";
1356 :
1357 28 : CPLString osDescription;
1358 14 : CPLXMLNode *psTable = RefreshFileAreaObservationalBeginningCommon(
1359 28 : psFAO, osPrefix, ("Table_" + GetSubType()).c_str(), osDescription);
1360 :
1361 14 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "records").c_str(),
1362 : CPLSPrintf(CPL_FRMT_GIB, m_nFeatureCount));
1363 14 : if (!osDescription.empty())
1364 0 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "description").c_str(),
1365 : osDescription);
1366 14 : if (m_osLineEnding == "\r\n")
1367 : {
1368 8 : CPLCreateXMLElementAndValue(psTable,
1369 16 : (osPrefix + "record_delimiter").c_str(),
1370 : "Carriage-Return Line-Feed");
1371 : }
1372 6 : else if (m_osLineEnding == "\n")
1373 : {
1374 1 : CPLCreateXMLElementAndValue(
1375 2 : psTable, (osPrefix + "record_delimiter").c_str(), "Line-Feed");
1376 : }
1377 :
1378 : // Write Record_Character / Record_Binary
1379 14 : CPLXMLNode *psRecord = CPLCreateXMLNode(
1380 28 : psTable, CXT_Element, (osPrefix + "Record_" + GetSubType()).c_str());
1381 28 : CPLCreateXMLElementAndValue(
1382 28 : psRecord, (osPrefix + "fields").c_str(),
1383 14 : CPLSPrintf("%d", static_cast<int>(m_aoFields.size())));
1384 14 : CPLCreateXMLElementAndValue(psRecord, (osPrefix + "groups").c_str(), "0");
1385 28 : CPLXMLNode *psrecord_length = CPLCreateXMLElementAndValue(
1386 28 : psRecord, (osPrefix + "record_length").c_str(),
1387 : CPLSPrintf("%d", m_nRecordSize));
1388 14 : CPLAddXMLAttributeAndValue(psrecord_length, "unit", "byte");
1389 :
1390 14 : CPLAssert(static_cast<int>(m_aoFields.size()) ==
1391 : m_poRawFeatureDefn->GetFieldCount());
1392 :
1393 163 : for (int i = 0; i < static_cast<int>(m_aoFields.size()); i++)
1394 : {
1395 149 : auto &f = m_aoFields[i];
1396 149 : auto poFieldDefn = m_poRawFeatureDefn->GetFieldDefn(i);
1397 :
1398 : CPLXMLNode *psField =
1399 149 : CPLCreateXMLNode(psRecord, CXT_Element,
1400 298 : (osPrefix + "Field_" + GetSubType()).c_str());
1401 :
1402 149 : CPLCreateXMLElementAndValue(psField, (osPrefix + "name").c_str(),
1403 : poFieldDefn->GetNameRef());
1404 :
1405 298 : CPLCreateXMLElementAndValue(psField,
1406 298 : (osPrefix + "field_number").c_str(),
1407 : CPLSPrintf("%d", i + 1));
1408 :
1409 149 : auto psfield_location = CPLCreateXMLElementAndValue(
1410 298 : psField, (osPrefix + "field_location").c_str(),
1411 149 : CPLSPrintf("%d", f.m_nOffset + 1));
1412 149 : CPLAddXMLAttributeAndValue(psfield_location, "unit", "byte");
1413 :
1414 149 : CPLCreateXMLElementAndValue(psField, (osPrefix + "data_type").c_str(),
1415 : f.m_osDataType.c_str());
1416 :
1417 298 : auto psfield_length = CPLCreateXMLElementAndValue(
1418 298 : psField, (osPrefix + "field_length").c_str(),
1419 : CPLSPrintf("%d", f.m_nLength));
1420 149 : CPLAddXMLAttributeAndValue(psfield_length, "unit", "byte");
1421 :
1422 149 : const auto eType(poFieldDefn->GetType());
1423 149 : const int nWidth = poFieldDefn->GetWidth();
1424 149 : if ((eType == OFTInteger || eType == OFTInteger64) && nWidth > 0)
1425 : {
1426 8 : CPLCreateXMLElementAndValue(psField,
1427 8 : (osPrefix + "field_format").c_str(),
1428 : CPLSPrintf("%%%dd", nWidth));
1429 : }
1430 :
1431 149 : if (!f.m_osUnit.empty())
1432 : {
1433 30 : CPLCreateXMLElementAndValue(psField, (osPrefix + "unit").c_str(),
1434 30 : m_aoFields[i].m_osUnit.c_str());
1435 : }
1436 :
1437 149 : if (!f.m_osDescription.empty())
1438 : {
1439 132 : CPLCreateXMLElementAndValue(psField,
1440 132 : (osPrefix + "description").c_str(),
1441 66 : m_aoFields[i].m_osDescription.c_str());
1442 : }
1443 149 : if (!f.m_osSpecialConstantsXML.empty())
1444 : {
1445 : auto psSpecialConstants =
1446 3 : CPLParseXMLString(f.m_osSpecialConstantsXML);
1447 3 : if (psSpecialConstants)
1448 : {
1449 3 : CPLAddXMLChild(psField, psSpecialConstants);
1450 : }
1451 : }
1452 : }
1453 14 : }
1454 :
1455 : /************************************************************************/
1456 : /* CreateField() */
1457 : /************************************************************************/
1458 :
1459 115 : OGRErr PDS4FixedWidthTable::CreateField(const OGRFieldDefn *poFieldIn, int)
1460 :
1461 : {
1462 115 : if (m_poDS->GetAccess() != GA_Update)
1463 : {
1464 0 : CPLError(CE_Failure, CPLE_AppDefined,
1465 : "Dataset opened in read-only mode");
1466 0 : return OGRERR_FAILURE;
1467 : }
1468 115 : if (m_nFeatureCount > 0)
1469 : {
1470 0 : return OGRERR_FAILURE;
1471 : }
1472 :
1473 230 : Field f;
1474 115 : if (!m_aoFields.empty())
1475 : {
1476 106 : f.m_nOffset = m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
1477 : }
1478 :
1479 115 : if (!CreateFieldInternal(poFieldIn->GetType(), poFieldIn->GetSubType(),
1480 115 : poFieldIn->GetWidth(), f))
1481 : {
1482 0 : return OGRERR_FAILURE;
1483 : }
1484 :
1485 115 : MarkHeaderDirty();
1486 115 : m_aoFields.push_back(f);
1487 115 : m_poRawFeatureDefn->AddFieldDefn(poFieldIn);
1488 115 : m_poFeatureDefn->AddFieldDefn(poFieldIn);
1489 115 : m_nRecordSize += f.m_nLength;
1490 115 : m_osBuffer.resize(m_nRecordSize);
1491 :
1492 115 : return OGRERR_NONE;
1493 : }
1494 :
1495 : /************************************************************************/
1496 : /* InitializeNewLayer() */
1497 : /************************************************************************/
1498 :
1499 13 : bool PDS4FixedWidthTable::InitializeNewLayer(const OGRSpatialReference *poSRS,
1500 : bool bForceGeographic,
1501 : OGRwkbGeometryType eGType,
1502 : const char *const *papszOptions)
1503 : {
1504 13 : CPLAssert(m_fp == nullptr);
1505 13 : m_fp = VSIFOpenL(m_osFilename, "wb+");
1506 13 : if (!m_fp)
1507 : {
1508 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
1509 : m_osFilename.c_str());
1510 0 : return false;
1511 : }
1512 13 : m_aosLCO.Assign(CSLDuplicate(papszOptions));
1513 :
1514 13 : m_nRecordSize = 0;
1515 :
1516 : const char *pszGeomColumns =
1517 13 : CSLFetchNameValueDef(papszOptions, "GEOM_COLUMNS", "AUTO");
1518 13 : if (EQUAL(pszGeomColumns, "WKT"))
1519 : {
1520 0 : CPLError(CE_Warning, CPLE_AppDefined,
1521 : "GEOM_COLUMNS=WKT only supported for delimited/CSV tables");
1522 : }
1523 :
1524 13 : if ((EQUAL(pszGeomColumns, "AUTO") && wkbFlatten(eGType) == wkbPoint &&
1525 26 : (bForceGeographic || (poSRS && poSRS->IsGeographic()))) ||
1526 9 : (EQUAL(pszGeomColumns, "LONG_LAT") && eGType != wkbNone))
1527 : {
1528 : {
1529 : OGRFieldDefn oFieldDefn(
1530 8 : CSLFetchNameValueDef(papszOptions, "LAT", "Latitude"), OFTReal);
1531 4 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
1532 4 : m_iLatField = m_poRawFeatureDefn->GetFieldCount() - 1;
1533 4 : Field f;
1534 4 : f.m_nOffset = m_aoFields.empty() ? 0
1535 0 : : m_aoFields.back().m_nOffset +
1536 0 : m_aoFields.back().m_nLength;
1537 4 : CreateFieldInternal(OFTReal, OFSTNone, 0, f);
1538 4 : m_aoFields.push_back(f);
1539 4 : m_nRecordSize += f.m_nLength;
1540 : }
1541 : {
1542 : OGRFieldDefn oFieldDefn(
1543 : CSLFetchNameValueDef(papszOptions, "LONG", "Longitude"),
1544 8 : OFTReal);
1545 4 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
1546 4 : m_iLongField = m_poRawFeatureDefn->GetFieldCount() - 1;
1547 4 : Field f;
1548 4 : f.m_nOffset =
1549 4 : m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
1550 4 : CreateFieldInternal(OFTReal, OFSTNone, 0, f);
1551 4 : m_aoFields.push_back(f);
1552 4 : m_nRecordSize += f.m_nLength;
1553 : }
1554 4 : if (eGType == wkbPoint25D)
1555 : {
1556 : OGRFieldDefn oFieldDefn(
1557 8 : CSLFetchNameValueDef(papszOptions, "ALT", "Altitude"), OFTReal);
1558 4 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
1559 4 : m_iAltField = m_poRawFeatureDefn->GetFieldCount() - 1;
1560 4 : Field f;
1561 4 : f.m_nOffset =
1562 4 : m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
1563 4 : CreateFieldInternal(OFTReal, OFSTNone, 0, f);
1564 4 : m_aoFields.push_back(f);
1565 4 : m_nRecordSize += f.m_nLength;
1566 : }
1567 :
1568 4 : m_poRawFeatureDefn->SetGeomType(eGType);
1569 :
1570 4 : m_poFeatureDefn->SetGeomType(eGType);
1571 4 : if (poSRS)
1572 : {
1573 2 : auto poSRSClone = poSRS->Clone();
1574 2 : poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1575 2 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRSClone);
1576 2 : poSRSClone->Release();
1577 : }
1578 : }
1579 :
1580 13 : if (GetSubType() == "Character")
1581 : {
1582 8 : ParseLineEndingOption(papszOptions);
1583 : }
1584 13 : m_nRecordSize += static_cast<int>(m_osLineEnding.size());
1585 13 : m_osBuffer.resize(m_nRecordSize);
1586 :
1587 13 : m_nFeatureCount = 0;
1588 13 : MarkHeaderDirty();
1589 13 : return true;
1590 : }
1591 :
1592 : /************************************************************************/
1593 : /* ==================================================================== */
1594 : /* PDS4TableCharacter */
1595 : /* ==================================================================== */
1596 : /************************************************************************/
1597 :
1598 30 : PDS4TableCharacter::PDS4TableCharacter(PDS4Dataset *poDS, const char *pszName,
1599 30 : const char *pszFilename)
1600 30 : : PDS4FixedWidthTable(poDS, pszName, pszFilename)
1601 : {
1602 30 : }
1603 :
1604 : /************************************************************************/
1605 : /* CreateFieldInternal() */
1606 : /************************************************************************/
1607 :
1608 80 : bool PDS4TableCharacter::CreateFieldInternal(OGRFieldType eType,
1609 : OGRFieldSubType eSubType,
1610 : int nWidth, Field &f)
1611 : {
1612 80 : if (nWidth > 0)
1613 : {
1614 0 : f.m_nLength = nWidth;
1615 : }
1616 : else
1617 : {
1618 80 : if (eType == OFTString)
1619 : {
1620 4 : f.m_nLength = 64;
1621 : }
1622 76 : else if (eType == OFTInteger)
1623 : {
1624 9 : f.m_nLength = eSubType == OFSTBoolean ? 1 : 11;
1625 : }
1626 67 : else if (eType == OFTInteger64)
1627 : {
1628 4 : f.m_nLength = 21;
1629 : }
1630 63 : else if (eType == OFTReal)
1631 : {
1632 51 : f.m_nLength = 16;
1633 : }
1634 12 : else if (eType == OFTDateTime)
1635 : {
1636 : // YYYY-MM-DDTHH:MM:SS.sssZ
1637 4 : f.m_nLength = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 3 + 1;
1638 : }
1639 8 : else if (eType == OFTDate)
1640 : {
1641 : // YYYY-MM-DD
1642 4 : f.m_nLength = 4 + 1 + 2 + 1 + 2;
1643 : }
1644 4 : else if (eType == OFTTime)
1645 : {
1646 : // HH:MM:SS.sss
1647 4 : f.m_nLength = 2 + 1 + 2 + 1 + 2 + 1 + 3;
1648 : }
1649 : }
1650 80 : if (eType == OFTString)
1651 : {
1652 4 : f.m_osDataType = "UTF8_String";
1653 : }
1654 76 : else if (eType == OFTInteger)
1655 : {
1656 : f.m_osDataType =
1657 9 : eSubType == OFSTBoolean ? "ASCII_Boolean" : "ASCII_Integer";
1658 : }
1659 67 : else if (eType == OFTInteger64)
1660 : {
1661 4 : f.m_osDataType = "ASCII_Integer";
1662 : }
1663 63 : else if (eType == OFTReal)
1664 : {
1665 51 : f.m_osDataType = "ASCII_Real";
1666 : }
1667 12 : else if (eType == OFTDateTime)
1668 : {
1669 4 : f.m_osDataType = "ASCII_Date_Time_YMD";
1670 : }
1671 8 : else if (eType == OFTDate)
1672 : {
1673 4 : f.m_osDataType = "ASCII_Date_YMD";
1674 : }
1675 4 : else if (eType == OFTTime)
1676 : {
1677 4 : f.m_osDataType = "ASCII_Time";
1678 : }
1679 : else
1680 : {
1681 0 : return false;
1682 : }
1683 80 : return true;
1684 : }
1685 :
1686 : /************************************************************************/
1687 : /* ==================================================================== */
1688 : /* PDS4TableBinary */
1689 : /* ==================================================================== */
1690 : /************************************************************************/
1691 :
1692 16 : PDS4TableBinary::PDS4TableBinary(PDS4Dataset *poDS, const char *pszName,
1693 16 : const char *pszFilename)
1694 16 : : PDS4FixedWidthTable(poDS, pszName, pszFilename)
1695 : {
1696 16 : }
1697 :
1698 : /************************************************************************/
1699 : /* CreateFieldInternal() */
1700 : /************************************************************************/
1701 :
1702 47 : bool PDS4TableBinary::CreateFieldInternal(OGRFieldType eType,
1703 : OGRFieldSubType eSubType, int nWidth,
1704 : Field &f)
1705 : {
1706 94 : CPLString osEndianness(CPLGetConfigOption("PDS4_ENDIANNESS", "LSB"));
1707 94 : CPLString osSignedness(CPLGetConfigOption("PDS4_SIGNEDNESS", "Signed"));
1708 :
1709 47 : if (eType == OFTString)
1710 : {
1711 4 : f.m_osDataType = "UTF8_String";
1712 4 : f.m_nLength = nWidth > 0 ? nWidth : 64;
1713 : }
1714 43 : else if (eType == OFTInteger)
1715 : {
1716 28 : f.m_osDataType = nWidth > 0 && nWidth <= 2 ? osSignedness + "Byte"
1717 8 : : eSubType == OFSTBoolean ? CPLString("ASCII_Boolean")
1718 : : eSubType == OFSTInt16
1719 20 : ? osSignedness + osEndianness + "2"
1720 20 : : osSignedness + osEndianness + "4";
1721 28 : f.m_nLength = nWidth > 0 && nWidth <= 2 ? 1
1722 20 : : eSubType == OFSTBoolean ? 1
1723 8 : : eSubType == OFSTInt16 ? 2
1724 : : 4;
1725 : }
1726 27 : else if (eType == OFTInteger64)
1727 : {
1728 4 : f.m_osDataType = osSignedness + osEndianness + "8";
1729 4 : f.m_nLength = 8;
1730 : }
1731 23 : else if (eType == OFTReal)
1732 : {
1733 : f.m_osDataType = eSubType == OFSTFloat32
1734 22 : ? "IEEE754" + osEndianness + "Single"
1735 18 : : "IEEE754" + osEndianness + "Double";
1736 11 : f.m_nLength = eSubType == OFSTFloat32 ? 4 : 8;
1737 : }
1738 12 : else if (eType == OFTDateTime)
1739 : {
1740 4 : f.m_osDataType = "ASCII_Date_Time_YMD";
1741 : // YYYY-MM-DDTHH:MM:SS.sssZ
1742 4 : f.m_nLength = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 3 + 1;
1743 : }
1744 8 : else if (eType == OFTDate)
1745 : {
1746 4 : f.m_osDataType = "ASCII_Date_YMD";
1747 : // YYYY-MM-DD
1748 4 : f.m_nLength = 4 + 1 + 2 + 1 + 2;
1749 : }
1750 4 : else if (eType == OFTTime)
1751 : {
1752 4 : f.m_osDataType = "ASCII_Time";
1753 : // HH:MM:SS.sss
1754 4 : f.m_nLength = 2 + 1 + 2 + 1 + 2 + 1 + 3;
1755 : }
1756 : else
1757 : {
1758 0 : return false;
1759 : }
1760 47 : return true;
1761 : }
1762 :
1763 : /************************************************************************/
1764 : /* ==================================================================== */
1765 : /* PDS4DelimitedTable */
1766 : /* ==================================================================== */
1767 : /************************************************************************/
1768 :
1769 120 : PDS4DelimitedTable::PDS4DelimitedTable(PDS4Dataset *poDS, const char *pszName,
1770 120 : const char *pszFilename)
1771 120 : : PDS4TableBaseLayer(poDS, pszName, pszFilename)
1772 : {
1773 120 : }
1774 :
1775 : /************************************************************************/
1776 : /* ~PDS4DelimitedTable() */
1777 : /************************************************************************/
1778 :
1779 240 : PDS4DelimitedTable::~PDS4DelimitedTable()
1780 : {
1781 120 : if (m_bDirtyHeader)
1782 55 : GenerateVRT();
1783 240 : }
1784 :
1785 : /************************************************************************/
1786 : /* GenerateVRT() */
1787 : /************************************************************************/
1788 :
1789 55 : void PDS4DelimitedTable::GenerateVRT()
1790 : {
1791 55 : CPLString osVRTFilename = CPLResetExtensionSafe(m_osFilename, "vrt");
1792 55 : if (m_bCreation)
1793 : {
1794 : // In creation mode, generate the VRT, unless explicitly disabled by
1795 : // CREATE_VRT=NO
1796 55 : if (!m_aosLCO.FetchBool("CREATE_VRT", true))
1797 1 : return;
1798 : }
1799 : else
1800 : {
1801 : // In a update situation, only generates the VRT if ones already exists
1802 : VSIStatBufL sStat;
1803 0 : if (VSIStatL(osVRTFilename, &sStat) != 0)
1804 0 : return;
1805 : }
1806 :
1807 : CPLXMLNode *psRoot =
1808 54 : CPLCreateXMLNode(nullptr, CXT_Element, "OGRVRTDataSource");
1809 54 : CPLXMLNode *psLayer = CPLCreateXMLNode(psRoot, CXT_Element, "OGRVRTLayer");
1810 54 : CPLAddXMLAttributeAndValue(psLayer, "name", GetName());
1811 :
1812 54 : CPLXMLNode *psSrcDataSource = CPLCreateXMLElementAndValue(
1813 : psLayer, "SrcDataSource", CPLGetFilename(m_osFilename));
1814 54 : CPLAddXMLAttributeAndValue(psSrcDataSource, "relativeToVRT", "1");
1815 :
1816 54 : CPLCreateXMLElementAndValue(
1817 108 : psLayer, "SrcLayer", CPLGetBasenameSafe(m_osFilename.c_str()).c_str());
1818 :
1819 54 : CPLXMLNode *psLastChild = CPLCreateXMLElementAndValue(
1820 : psLayer, "GeometryType",
1821 108 : OGRVRTGetSerializedGeometryType(GetGeomType()).c_str());
1822 :
1823 54 : if (GetSpatialRef())
1824 : {
1825 1 : char *pszWKT = nullptr;
1826 1 : GetSpatialRef()->exportToWkt(&pszWKT);
1827 1 : if (pszWKT)
1828 : {
1829 1 : CPLCreateXMLElementAndValue(psLayer, "LayerSRS", pszWKT);
1830 1 : CPLFree(pszWKT);
1831 : }
1832 : }
1833 :
1834 55 : while (psLastChild->psNext)
1835 1 : psLastChild = psLastChild->psNext;
1836 54 : const int nFieldCount = m_poRawFeatureDefn->GetFieldCount();
1837 221 : for (int i = 0; i < nFieldCount; i++)
1838 : {
1839 167 : if (i != m_iWKT && i != m_iLongField && i != m_iLatField &&
1840 132 : i != m_iAltField)
1841 : {
1842 132 : OGRFieldDefn *poFieldDefn = m_poRawFeatureDefn->GetFieldDefn(i);
1843 : CPLXMLNode *psField =
1844 132 : CPLCreateXMLNode(nullptr, CXT_Element, "Field");
1845 132 : psLastChild->psNext = psField;
1846 132 : psLastChild = psField;
1847 132 : CPLAddXMLAttributeAndValue(psField, "name",
1848 : poFieldDefn->GetNameRef());
1849 132 : CPLAddXMLAttributeAndValue(
1850 : psField, "type", OGR_GetFieldTypeName(poFieldDefn->GetType()));
1851 132 : if (poFieldDefn->GetSubType() != OFSTNone)
1852 : {
1853 4 : CPLAddXMLAttributeAndValue(
1854 : psField, "subtype",
1855 : OGR_GetFieldSubTypeName(poFieldDefn->GetSubType()));
1856 : }
1857 133 : if (poFieldDefn->GetWidth() > 0 &&
1858 1 : poFieldDefn->GetType() != OFTReal)
1859 : {
1860 1 : CPLAddXMLAttributeAndValue(
1861 : psField, "width",
1862 : CPLSPrintf("%d", poFieldDefn->GetWidth()));
1863 : }
1864 132 : CPLAddXMLAttributeAndValue(psField, "src",
1865 : poFieldDefn->GetNameRef());
1866 : }
1867 : }
1868 :
1869 54 : if (m_iWKT >= 0)
1870 : {
1871 : CPLXMLNode *psField =
1872 35 : CPLCreateXMLNode(nullptr, CXT_Element, "GeometryField");
1873 35 : psLastChild->psNext = psField;
1874 35 : psLastChild = psField;
1875 35 : CPLAddXMLAttributeAndValue(psField, "encoding", "WKT");
1876 35 : CPLAddXMLAttributeAndValue(
1877 : psField, "field",
1878 35 : m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetNameRef());
1879 : }
1880 19 : else if (m_iLongField >= 0 && m_iLatField >= 0)
1881 : {
1882 : CPLXMLNode *psField =
1883 0 : CPLCreateXMLNode(nullptr, CXT_Element, "GeometryField");
1884 0 : psLastChild->psNext = psField;
1885 0 : psLastChild = psField;
1886 0 : CPLAddXMLAttributeAndValue(psField, "encoding", "PointFromColumns");
1887 0 : CPLAddXMLAttributeAndValue(
1888 : psField, "x",
1889 0 : m_poRawFeatureDefn->GetFieldDefn(m_iLongField)->GetNameRef());
1890 0 : CPLAddXMLAttributeAndValue(
1891 : psField, "y",
1892 0 : m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetNameRef());
1893 0 : if (m_iAltField >= 0)
1894 : {
1895 0 : CPLAddXMLAttributeAndValue(
1896 : psField, "z",
1897 0 : m_poRawFeatureDefn->GetFieldDefn(m_iAltField)->GetNameRef());
1898 : }
1899 : }
1900 :
1901 54 : CPL_IGNORE_RET_VAL(psLastChild);
1902 :
1903 54 : CPLSerializeXMLTreeToFile(psRoot, osVRTFilename);
1904 54 : CPLDestroyXMLNode(psRoot);
1905 : }
1906 :
1907 : /************************************************************************/
1908 : /* ResetReading() */
1909 : /************************************************************************/
1910 :
1911 214 : void PDS4DelimitedTable::ResetReading()
1912 : {
1913 214 : m_nFID = 1;
1914 214 : VSIFSeekL(m_fp, m_nOffset, SEEK_SET);
1915 214 : }
1916 :
1917 : /************************************************************************/
1918 : /* GetNextFeatureRaw() */
1919 : /************************************************************************/
1920 :
1921 506 : OGRFeature *PDS4DelimitedTable::GetNextFeatureRaw()
1922 : {
1923 506 : const char *pszLine = CPLReadLine2L(m_fp, 10 * 1024 * 1024, nullptr);
1924 506 : if (pszLine == nullptr)
1925 : {
1926 47 : return nullptr;
1927 : }
1928 :
1929 459 : char szDelimiter[2] = {m_chFieldDelimiter, 0};
1930 459 : char **papszFields = CSLTokenizeString2(
1931 : pszLine, szDelimiter, CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
1932 459 : if (CSLCount(papszFields) != m_poRawFeatureDefn->GetFieldCount())
1933 : {
1934 0 : CPLError(CE_Warning, CPLE_AppDefined,
1935 : "Did not get expected number of fields at line " CPL_FRMT_GIB,
1936 : m_nFID);
1937 : }
1938 :
1939 459 : OGRFeature *poRawFeature = new OGRFeature(m_poRawFeatureDefn);
1940 459 : poRawFeature->SetFID(m_nFID);
1941 459 : m_nFID++;
1942 4231 : for (int i = 0; i < m_poRawFeatureDefn->GetFieldCount() && papszFields &&
1943 1886 : papszFields[i];
1944 : i++)
1945 : {
1946 1886 : if (!m_aoFields[i].m_osMissingConstant.empty() &&
1947 0 : m_aoFields[i].m_osMissingConstant == papszFields[i])
1948 : {
1949 : // do nothing
1950 : }
1951 1886 : else if (m_aoFields[i].m_osDataType == "ASCII_Boolean")
1952 : {
1953 10 : poRawFeature->SetField(i, EQUAL(papszFields[i], "t") ||
1954 5 : EQUAL(papszFields[i], "1")
1955 : ? 1
1956 : : 0);
1957 : }
1958 : else
1959 : {
1960 1881 : poRawFeature->SetField(i, papszFields[i]);
1961 : }
1962 : }
1963 :
1964 459 : CSLDestroy(papszFields);
1965 :
1966 459 : OGRFeature *poFeature = AddGeometryFromFields(poRawFeature);
1967 459 : delete poRawFeature;
1968 459 : return poFeature;
1969 : }
1970 :
1971 : /************************************************************************/
1972 : /* GetNextFeature() */
1973 : /************************************************************************/
1974 :
1975 506 : OGRFeature *PDS4DelimitedTable::GetNextFeature()
1976 : {
1977 : while (true)
1978 : {
1979 506 : auto poFeature = GetNextFeatureRaw();
1980 506 : if (poFeature == nullptr)
1981 : {
1982 47 : return nullptr;
1983 : }
1984 :
1985 1028 : if ((m_poFilterGeom == nullptr ||
1986 856 : FilterGeometry(poFeature->GetGeometryRef())) &&
1987 397 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
1988 : {
1989 397 : return poFeature;
1990 : }
1991 62 : delete poFeature;
1992 62 : }
1993 : }
1994 :
1995 : /************************************************************************/
1996 : /* TestCapability() */
1997 : /************************************************************************/
1998 :
1999 416 : int PDS4DelimitedTable::TestCapability(const char *pszCap) const
2000 : {
2001 416 : if (EQUAL(pszCap, OLCRandomRead) || EQUAL(pszCap, OLCStringsAsUTF8) ||
2002 402 : EQUAL(pszCap, OLCZGeometries))
2003 : {
2004 17 : return true;
2005 : }
2006 399 : if (EQUAL(pszCap, OLCFastFeatureCount))
2007 : {
2008 0 : return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
2009 : }
2010 399 : if (EQUAL(pszCap, OLCCreateField))
2011 : {
2012 175 : return m_poDS->GetAccess() == GA_Update && m_nFeatureCount == 0;
2013 : }
2014 224 : if (EQUAL(pszCap, OLCSequentialWrite))
2015 : {
2016 99 : return m_poDS->GetAccess() == GA_Update;
2017 : }
2018 125 : return false;
2019 : }
2020 :
2021 : /************************************************************************/
2022 : /* QuoteIfNeeded() */
2023 : /************************************************************************/
2024 :
2025 535 : CPLString PDS4DelimitedTable::QuoteIfNeeded(const char *pszVal)
2026 : {
2027 535 : if (strchr(pszVal, m_chFieldDelimiter) == nullptr)
2028 : {
2029 489 : return pszVal;
2030 : }
2031 92 : return '"' + CPLString(pszVal) + '"';
2032 : }
2033 :
2034 : /************************************************************************/
2035 : /* ICreateFeature() */
2036 : /************************************************************************/
2037 :
2038 97 : OGRErr PDS4DelimitedTable::ICreateFeature(OGRFeature *poFeature)
2039 : {
2040 97 : if (m_bAddWKTColumnPending)
2041 : {
2042 : OGRFieldDefn oFieldDefn(
2043 72 : CSLFetchNameValueDef(m_aosLCO.List(), "WKT", "WKT"), OFTString);
2044 36 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
2045 36 : m_iWKT = m_poRawFeatureDefn->GetFieldCount() - 1;
2046 36 : Field f;
2047 36 : f.m_osDataType = "ASCII_String";
2048 36 : m_aoFields.push_back(std::move(f));
2049 36 : m_bAddWKTColumnPending = false;
2050 : }
2051 :
2052 97 : if (m_nFeatureCount == 0)
2053 : {
2054 208 : for (int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
2055 : {
2056 169 : if (i > 0)
2057 : {
2058 130 : VSIFPrintfL(m_fp, "%c", m_chFieldDelimiter);
2059 : }
2060 169 : VSIFPrintfL(
2061 : m_fp, "%s",
2062 338 : QuoteIfNeeded(m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef())
2063 : .c_str());
2064 : }
2065 39 : VSIFPrintfL(m_fp, "%s", m_osLineEnding.c_str());
2066 39 : m_nOffset = VSIFTellL(m_fp);
2067 : }
2068 :
2069 97 : OGRFeature *poRawFeature = AddFieldsFromGeometry(poFeature);
2070 591 : for (int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++)
2071 : {
2072 494 : if (i > 0)
2073 : {
2074 397 : VSIFPrintfL(m_fp, "%c", m_chFieldDelimiter);
2075 : }
2076 494 : if (!poRawFeature->IsFieldSetAndNotNull(i))
2077 : {
2078 128 : if (!m_aoFields[i].m_osMissingConstant.empty())
2079 : {
2080 0 : VSIFPrintfL(
2081 : m_fp, "%s",
2082 0 : QuoteIfNeeded(m_aoFields[i].m_osMissingConstant).c_str());
2083 : }
2084 128 : continue;
2085 : }
2086 366 : VSIFPrintfL(m_fp, "%s",
2087 732 : QuoteIfNeeded(poRawFeature->GetFieldAsString(i)).c_str());
2088 : }
2089 97 : VSIFPrintfL(m_fp, "%s", m_osLineEnding.c_str());
2090 97 : delete poRawFeature;
2091 :
2092 97 : m_nFeatureCount++;
2093 97 : poFeature->SetFID(m_nFeatureCount);
2094 :
2095 97 : return OGRERR_NONE;
2096 : }
2097 :
2098 : /************************************************************************/
2099 : /* CreateField() */
2100 : /************************************************************************/
2101 :
2102 133 : OGRErr PDS4DelimitedTable::CreateField(const OGRFieldDefn *poFieldIn, int)
2103 :
2104 : {
2105 133 : if (m_poDS->GetAccess() != GA_Update)
2106 : {
2107 0 : CPLError(CE_Failure, CPLE_AppDefined,
2108 : "Dataset opened in read-only mode");
2109 0 : return OGRERR_FAILURE;
2110 : }
2111 133 : if (m_nFeatureCount > 0)
2112 : {
2113 0 : return OGRERR_FAILURE;
2114 : }
2115 :
2116 133 : const auto eType = poFieldIn->GetType();
2117 266 : Field f;
2118 133 : if (eType == OFTString)
2119 : {
2120 37 : f.m_osDataType = "UTF8_String";
2121 : }
2122 96 : else if (eType == OFTInteger)
2123 : {
2124 26 : f.m_osDataType = poFieldIn->GetSubType() == OFSTBoolean
2125 : ? "ASCII_Boolean"
2126 26 : : "ASCII_Integer";
2127 : }
2128 70 : else if (eType == OFTInteger64)
2129 : {
2130 5 : f.m_osDataType = "ASCII_Integer";
2131 : }
2132 65 : else if (eType == OFTReal)
2133 : {
2134 21 : f.m_osDataType = "ASCII_Real";
2135 : }
2136 44 : else if (eType == OFTDateTime)
2137 : {
2138 20 : f.m_osDataType = "ASCII_Date_Time_YMD";
2139 : }
2140 24 : else if (eType == OFTDate)
2141 : {
2142 20 : f.m_osDataType = "ASCII_Date_YMD";
2143 : }
2144 4 : else if (eType == OFTTime)
2145 : {
2146 4 : f.m_osDataType = "ASCII_Time";
2147 : }
2148 : else
2149 : {
2150 0 : return OGRERR_FAILURE;
2151 : }
2152 :
2153 133 : MarkHeaderDirty();
2154 133 : m_aoFields.push_back(std::move(f));
2155 133 : m_poRawFeatureDefn->AddFieldDefn(poFieldIn);
2156 133 : m_poFeatureDefn->AddFieldDefn(poFieldIn);
2157 :
2158 133 : return OGRERR_NONE;
2159 : }
2160 :
2161 : /************************************************************************/
2162 : /* ReadTableDef() */
2163 : /************************************************************************/
2164 :
2165 64 : bool PDS4DelimitedTable::ReadTableDef(const CPLXMLNode *psTable)
2166 : {
2167 64 : CPLAssert(m_fp == nullptr);
2168 64 : m_fp = VSIFOpenL(m_osFilename,
2169 64 : (m_poDS->GetAccess() == GA_ReadOnly) ? "rb" : "r+b");
2170 64 : if (!m_fp)
2171 : {
2172 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
2173 : m_osFilename.c_str());
2174 0 : return false;
2175 : }
2176 :
2177 64 : m_nOffset = static_cast<GUIntBig>(
2178 64 : CPLAtoGIntBig(CPLGetXMLValue(psTable, "offset", "0")));
2179 :
2180 64 : m_nFeatureCount = CPLAtoGIntBig(CPLGetXMLValue(psTable, "records", "-1"));
2181 :
2182 : const char *pszRecordDelimiter =
2183 64 : CPLGetXMLValue(psTable, "record_delimiter", "");
2184 64 : if (EQUAL(pszRecordDelimiter, "Carriage-Return Line-Feed"))
2185 62 : m_osLineEnding = "\r\n";
2186 2 : else if (EQUAL(pszRecordDelimiter, "Line-Feed"))
2187 2 : m_osLineEnding = "\n";
2188 0 : else if (EQUAL(pszRecordDelimiter, ""))
2189 : {
2190 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing record_delimiter");
2191 0 : return false;
2192 : }
2193 : else
2194 : {
2195 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid record_delimiter");
2196 0 : return false;
2197 : }
2198 :
2199 : const char *pszFieldDelimiter =
2200 64 : CPLGetXMLValue(psTable, "field_delimiter", nullptr);
2201 64 : if (pszFieldDelimiter == nullptr)
2202 : {
2203 0 : return false;
2204 : }
2205 64 : if (EQUAL(pszFieldDelimiter, "Comma"))
2206 : {
2207 64 : m_chFieldDelimiter = ',';
2208 : }
2209 0 : else if (EQUAL(pszFieldDelimiter, "Horizontal Tab"))
2210 : {
2211 0 : m_chFieldDelimiter = '\t';
2212 : }
2213 0 : else if (EQUAL(pszFieldDelimiter, "Semicolon"))
2214 : {
2215 0 : m_chFieldDelimiter = ';';
2216 : }
2217 0 : else if (EQUAL(pszFieldDelimiter, "Vertical Bar"))
2218 : {
2219 0 : m_chFieldDelimiter = '|';
2220 : }
2221 : else
2222 : {
2223 0 : CPLError(CE_Failure, CPLE_NotSupported,
2224 : "field_delimiter value not supported");
2225 0 : return false;
2226 : }
2227 :
2228 64 : const CPLXMLNode *psRecord = CPLGetXMLNode(psTable, "Record_Delimited");
2229 64 : if (!psRecord)
2230 : {
2231 0 : return false;
2232 : }
2233 64 : if (!ReadFields(psRecord, ""))
2234 : {
2235 0 : return false;
2236 : }
2237 :
2238 64 : SetupGeomField();
2239 64 : ResetReading();
2240 :
2241 64 : return true;
2242 : }
2243 :
2244 : /************************************************************************/
2245 : /* ReadFields() */
2246 : /************************************************************************/
2247 :
2248 66 : bool PDS4DelimitedTable::ReadFields(const CPLXMLNode *psParent,
2249 : const CPLString &osSuffixFieldName)
2250 : {
2251 437 : for (const CPLXMLNode *psIter = psParent->psChild; psIter;
2252 371 : psIter = psIter->psNext)
2253 : {
2254 371 : if (psIter->eType == CXT_Element &&
2255 371 : strcmp(psIter->pszValue, "Field_Delimited") == 0)
2256 : {
2257 234 : const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
2258 234 : if (!pszName)
2259 : {
2260 0 : return false;
2261 : }
2262 : const char *pszDataType =
2263 234 : CPLGetXMLValue(psIter, "data_type", nullptr);
2264 234 : if (!pszDataType)
2265 : {
2266 0 : return false;
2267 : }
2268 : int nMaximumFieldLength =
2269 234 : atoi(CPLGetXMLValue(psIter, "maximum_field_length", "0"));
2270 :
2271 234 : Field f;
2272 234 : f.m_osDataType = pszDataType;
2273 234 : f.m_osUnit = CPLGetXMLValue(psIter, "unit", "");
2274 234 : f.m_osDescription = CPLGetXMLValue(psIter, "description", "");
2275 :
2276 : CPLXMLNode *psSpecialConstants = const_cast<CPLXMLNode *>(
2277 234 : CPLGetXMLNode(psIter, "Special_Constants"));
2278 234 : if (psSpecialConstants)
2279 : {
2280 0 : auto psNext = psSpecialConstants->psNext;
2281 0 : psSpecialConstants->psNext = nullptr;
2282 0 : char *pszXML = CPLSerializeXMLTree(psSpecialConstants);
2283 0 : psSpecialConstants->psNext = psNext;
2284 0 : if (pszXML)
2285 : {
2286 0 : f.m_osSpecialConstantsXML = pszXML;
2287 0 : CPLFree(pszXML);
2288 : }
2289 : }
2290 : f.m_osMissingConstant = CPLGetXMLValue(
2291 234 : psIter, "Special_Constants.missing_constant", "");
2292 :
2293 234 : m_aoFields.push_back(f);
2294 :
2295 234 : OGRFieldSubType eSubType = OFSTNone;
2296 234 : bool error = false;
2297 : auto eType =
2298 234 : GetFieldTypeFromPDS4DataType(pszDataType, 0, eSubType, error);
2299 234 : if (error)
2300 : {
2301 0 : CPLError(CE_Failure, CPLE_AppDefined,
2302 : "Binary fields not allowed");
2303 0 : return false;
2304 : }
2305 422 : if (STARTS_WITH(f.m_osDataType, "ASCII_") && eType == OFTInteger &&
2306 424 : eSubType == OFSTNone &&
2307 2 : (nMaximumFieldLength == 0 || nMaximumFieldLength >= 10))
2308 : {
2309 42 : eType = OFTInteger64;
2310 : }
2311 234 : OGRFieldDefn oFieldDefn((pszName + osSuffixFieldName).c_str(),
2312 468 : eType);
2313 234 : oFieldDefn.SetSubType(eSubType);
2314 280 : if (eType != OFTReal && (STARTS_WITH(f.m_osDataType, "ASCII_") ||
2315 46 : STARTS_WITH(f.m_osDataType, "UTF_8")))
2316 : {
2317 159 : oFieldDefn.SetWidth(nMaximumFieldLength);
2318 : }
2319 468 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
2320 : }
2321 137 : else if (psIter->eType == CXT_Element &&
2322 137 : strcmp(psIter->pszValue, "Group_Field_Delimited") == 0)
2323 : {
2324 : const char *pszRepetitions =
2325 1 : CPLGetXMLValue(psIter, "repetitions", nullptr);
2326 1 : if (!pszRepetitions)
2327 : {
2328 0 : return false;
2329 : }
2330 1 : int nRepetitions = std::min(1000, atoi(pszRepetitions));
2331 1 : if (nRepetitions <= 0)
2332 : {
2333 0 : return false;
2334 : }
2335 3 : for (int i = 0; i < nRepetitions; i++)
2336 : {
2337 2 : if (!ReadFields(psIter, osSuffixFieldName + "_" +
2338 : CPLSPrintf("%d", i + 1)))
2339 : {
2340 0 : return false;
2341 : }
2342 : }
2343 : }
2344 : }
2345 66 : return true;
2346 : }
2347 :
2348 : /************************************************************************/
2349 : /* RefreshFileAreaObservational() */
2350 : /************************************************************************/
2351 :
2352 55 : void PDS4DelimitedTable::RefreshFileAreaObservational(CPLXMLNode *psFAO)
2353 : {
2354 110 : CPLString osPrefix;
2355 55 : if (STARTS_WITH(psFAO->pszValue, "pds:"))
2356 0 : osPrefix = "pds:";
2357 :
2358 110 : CPLString osDescription;
2359 55 : CPLXMLNode *psTable = RefreshFileAreaObservationalBeginningCommon(
2360 : psFAO, osPrefix, "Table_Delimited", osDescription);
2361 :
2362 55 : CPLCreateXMLElementAndValue(
2363 110 : psTable, (osPrefix + "parsing_standard_id").c_str(), "PDS DSV 1");
2364 :
2365 55 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "records").c_str(),
2366 : CPLSPrintf(CPL_FRMT_GIB, m_nFeatureCount));
2367 55 : if (!osDescription.empty())
2368 0 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "description").c_str(),
2369 : osDescription);
2370 :
2371 55 : if (m_osLineEnding == "\r\n")
2372 : {
2373 54 : CPLCreateXMLElementAndValue(psTable,
2374 108 : (osPrefix + "record_delimiter").c_str(),
2375 : "Carriage-Return Line-Feed");
2376 : }
2377 1 : else if (m_osLineEnding == "\n")
2378 : {
2379 1 : CPLCreateXMLElementAndValue(
2380 2 : psTable, (osPrefix + "record_delimiter").c_str(), "Line-Feed");
2381 : }
2382 :
2383 55 : CPLCreateXMLElementAndValue(psTable, (osPrefix + "field_delimiter").c_str(),
2384 55 : m_chFieldDelimiter == '\t' ? "Horizontal Tab"
2385 110 : : m_chFieldDelimiter == ';' ? "Semicolon"
2386 55 : : m_chFieldDelimiter == '|' ? "Vertical Bar"
2387 : : "Comma");
2388 :
2389 : // Write Record_Delimited
2390 55 : CPLXMLNode *psRecord = CPLCreateXMLNode(
2391 110 : psTable, CXT_Element, (osPrefix + "Record_Delimited").c_str());
2392 :
2393 110 : CPLCreateXMLElementAndValue(
2394 110 : psRecord, (osPrefix + "fields").c_str(),
2395 55 : CPLSPrintf("%d", static_cast<int>(m_aoFields.size())));
2396 :
2397 55 : CPLXMLNode *psLastChild = CPLCreateXMLElementAndValue(
2398 110 : psRecord, (osPrefix + "groups").c_str(), "0");
2399 :
2400 55 : CPLAssert(static_cast<int>(m_aoFields.size()) ==
2401 : m_poRawFeatureDefn->GetFieldCount());
2402 :
2403 110 : const auto osPrefixedFieldDelimited(osPrefix + "Field_Delimited");
2404 110 : const auto osPrefixedName(osPrefix + "name");
2405 110 : const auto osPrefixedFieldNumber(osPrefix + "field_number");
2406 110 : const auto osPrefixedFieldData(osPrefix + "data_type");
2407 110 : const auto osPrefixMaxFieldLength(osPrefix + "maximum_field_length");
2408 110 : const auto osPrefixedUnit(osPrefix + "unit");
2409 110 : const auto osPrefixedDescription(osPrefix + "description");
2410 55 : CPLAssert(psLastChild->psNext == nullptr);
2411 224 : for (int i = 0; i < static_cast<int>(m_aoFields.size()); i++)
2412 : {
2413 169 : const auto &f = m_aoFields[i];
2414 :
2415 169 : CPLXMLNode *psField = CPLCreateXMLNode(
2416 : nullptr, CXT_Element, osPrefixedFieldDelimited.c_str());
2417 169 : psLastChild->psNext = psField;
2418 169 : psLastChild = psField;
2419 :
2420 169 : CPLCreateXMLElementAndValue(
2421 : psField, osPrefixedName.c_str(),
2422 169 : m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());
2423 :
2424 169 : CPLCreateXMLElementAndValue(psField, osPrefixedFieldNumber.c_str(),
2425 : CPLSPrintf("%d", i + 1));
2426 :
2427 169 : CPLCreateXMLElementAndValue(psField, osPrefixedFieldData.c_str(),
2428 : f.m_osDataType.c_str());
2429 :
2430 169 : int nWidth = m_poRawFeatureDefn->GetFieldDefn(i)->GetWidth();
2431 169 : if (nWidth > 0)
2432 : {
2433 1 : auto psfield_length = CPLCreateXMLElementAndValue(
2434 : psField, osPrefixMaxFieldLength.c_str(),
2435 : CPLSPrintf("%d", nWidth));
2436 1 : CPLAddXMLAttributeAndValue(psfield_length, "unit", "byte");
2437 : }
2438 :
2439 169 : if (!f.m_osUnit.empty())
2440 : {
2441 0 : CPLCreateXMLElementAndValue(psField, osPrefixedUnit.c_str(),
2442 0 : m_aoFields[i].m_osUnit.c_str());
2443 : }
2444 :
2445 169 : if (!f.m_osDescription.empty())
2446 : {
2447 0 : CPLCreateXMLElementAndValue(psField, osPrefixedDescription.c_str(),
2448 0 : m_aoFields[i].m_osDescription.c_str());
2449 : }
2450 :
2451 169 : if (!f.m_osSpecialConstantsXML.empty())
2452 : {
2453 : auto psSpecialConstants =
2454 0 : CPLParseXMLString(f.m_osSpecialConstantsXML);
2455 0 : if (psSpecialConstants)
2456 : {
2457 0 : CPLAddXMLChild(psField, psSpecialConstants);
2458 : }
2459 : }
2460 : }
2461 55 : }
2462 :
2463 : /************************************************************************/
2464 : /* GetFileList() */
2465 : /************************************************************************/
2466 :
2467 53 : char **PDS4DelimitedTable::GetFileList() const
2468 : {
2469 53 : auto papszFileList = PDS4TableBaseLayer::GetFileList();
2470 53 : CPLString osVRTFilename = CPLResetExtensionSafe(m_osFilename, "vrt");
2471 : VSIStatBufL sStat;
2472 53 : if (VSIStatL(osVRTFilename, &sStat) == 0)
2473 : {
2474 52 : papszFileList = CSLAddString(papszFileList, osVRTFilename);
2475 : }
2476 106 : return papszFileList;
2477 : }
2478 :
2479 : /************************************************************************/
2480 : /* InitializeNewLayer() */
2481 : /************************************************************************/
2482 :
2483 56 : bool PDS4DelimitedTable::InitializeNewLayer(const OGRSpatialReference *poSRS,
2484 : bool bForceGeographic,
2485 : OGRwkbGeometryType eGType,
2486 : const char *const *papszOptions)
2487 : {
2488 56 : CPLAssert(m_fp == nullptr);
2489 56 : m_fp = VSIFOpenL(m_osFilename, "wb+");
2490 56 : if (!m_fp)
2491 : {
2492 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
2493 : m_osFilename.c_str());
2494 1 : return false;
2495 : }
2496 55 : m_aosLCO.Assign(CSLDuplicate(papszOptions));
2497 55 : m_bCreation = true;
2498 :
2499 : // For testing purposes
2500 55 : m_chFieldDelimiter = CPLGetConfigOption("OGR_PDS4_FIELD_DELIMITER", ",")[0];
2501 :
2502 : const char *pszGeomColumns =
2503 55 : CSLFetchNameValueDef(papszOptions, "GEOM_COLUMNS", "AUTO");
2504 55 : if ((EQUAL(pszGeomColumns, "AUTO") && wkbFlatten(eGType) == wkbPoint &&
2505 116 : (bForceGeographic || (poSRS && poSRS->IsGeographic()))) ||
2506 55 : (EQUAL(pszGeomColumns, "LONG_LAT") && eGType != wkbNone))
2507 : {
2508 : {
2509 : OGRFieldDefn oFieldDefn(
2510 0 : CSLFetchNameValueDef(papszOptions, "LAT", "Latitude"), OFTReal);
2511 0 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
2512 0 : m_iLatField = m_poRawFeatureDefn->GetFieldCount() - 1;
2513 0 : Field f;
2514 0 : f.m_osDataType = "ASCII_Real";
2515 0 : m_aoFields.push_back(std::move(f));
2516 : }
2517 : {
2518 : OGRFieldDefn oFieldDefn(
2519 : CSLFetchNameValueDef(papszOptions, "LONG", "Longitude"),
2520 0 : OFTReal);
2521 0 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
2522 0 : m_iLongField = m_poRawFeatureDefn->GetFieldCount() - 1;
2523 0 : Field f;
2524 0 : f.m_osDataType = "ASCII_Real";
2525 0 : m_aoFields.push_back(std::move(f));
2526 : }
2527 0 : if (eGType == wkbPoint25D)
2528 : {
2529 : OGRFieldDefn oFieldDefn(
2530 0 : CSLFetchNameValueDef(papszOptions, "ALT", "Altitude"), OFTReal);
2531 0 : m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
2532 0 : m_iAltField = m_poRawFeatureDefn->GetFieldCount() - 1;
2533 0 : Field f;
2534 0 : f.m_osDataType = "ASCII_Real";
2535 0 : m_aoFields.push_back(std::move(f));
2536 : }
2537 : }
2538 55 : else if (eGType != wkbNone &&
2539 51 : (EQUAL(pszGeomColumns, "AUTO") || EQUAL(pszGeomColumns, "WKT")))
2540 : {
2541 51 : m_bAddWKTColumnPending = true;
2542 : }
2543 :
2544 55 : if (eGType != wkbNone)
2545 : {
2546 51 : m_poRawFeatureDefn->SetGeomType(eGType);
2547 :
2548 51 : m_poFeatureDefn->SetGeomType(eGType);
2549 51 : if (poSRS)
2550 : {
2551 2 : auto poSRSClone = poSRS->Clone();
2552 2 : poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2553 2 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRSClone);
2554 2 : poSRSClone->Release();
2555 : }
2556 : }
2557 :
2558 55 : ParseLineEndingOption(papszOptions);
2559 :
2560 55 : m_nFeatureCount = 0;
2561 55 : MarkHeaderDirty();
2562 55 : return true;
2563 : }
2564 :
2565 : /************************************************************************/
2566 : /* ==================================================================== */
2567 : /* PDS4EditableSynchronizer */
2568 : /* ==================================================================== */
2569 : /************************************************************************/
2570 :
2571 : template <class T>
2572 : class PDS4EditableSynchronizer final : public IOGREditableLayerSynchronizer
2573 : {
2574 : public:
2575 162 : PDS4EditableSynchronizer() = default;
2576 :
2577 : OGRErr EditableSyncToDisk(OGRLayer *poEditableLayer,
2578 : OGRLayer **ppoDecoratedLayer) override;
2579 : };
2580 :
2581 : template <class T>
2582 : OGRErr
2583 3 : PDS4EditableSynchronizer<T>::EditableSyncToDisk(OGRLayer *poEditableLayer,
2584 : OGRLayer **ppoDecoratedLayer)
2585 : {
2586 3 : auto poOriLayer = cpl::down_cast<T *>(*ppoDecoratedLayer);
2587 :
2588 6 : CPLString osTmpFilename(poOriLayer->m_osFilename + ".tmp");
2589 3 : auto poNewLayer = poOriLayer->NewLayer(
2590 : poOriLayer->m_poDS, poOriLayer->GetName(), osTmpFilename);
2591 6 : CPLStringList aosLCO(poOriLayer->m_aosLCO);
2592 3 : if (poOriLayer->m_iLatField >= 0)
2593 : {
2594 2 : aosLCO.SetNameValue("LAT", poOriLayer->m_poRawFeatureDefn
2595 : ->GetFieldDefn(poOriLayer->m_iLatField)
2596 : ->GetNameRef());
2597 : }
2598 3 : if (poOriLayer->m_iLongField >= 0)
2599 : {
2600 2 : aosLCO.SetNameValue("LONG", poOriLayer->m_poRawFeatureDefn
2601 : ->GetFieldDefn(poOriLayer->m_iLongField)
2602 : ->GetNameRef());
2603 : }
2604 3 : if (poOriLayer->m_iAltField >= 0)
2605 : {
2606 2 : aosLCO.SetNameValue("ALT", poOriLayer->m_poRawFeatureDefn
2607 : ->GetFieldDefn(poOriLayer->m_iAltField)
2608 : ->GetNameRef());
2609 : }
2610 6 : if (!poNewLayer->InitializeNewLayer(
2611 3 : poOriLayer->GetSpatialRef(), poOriLayer->m_iLatField >= 0,
2612 3 : poOriLayer->GetGeomType(), aosLCO.List()))
2613 : {
2614 0 : delete poNewLayer;
2615 0 : VSIUnlink(osTmpFilename);
2616 0 : return OGRERR_FAILURE;
2617 : }
2618 :
2619 : const auto copyField =
2620 94 : [](typename T::Field &oDst, const typename T::Field &oSrc)
2621 : {
2622 47 : oDst.m_osDescription = oSrc.m_osDescription;
2623 47 : oDst.m_osUnit = oSrc.m_osUnit;
2624 47 : oDst.m_osSpecialConstantsXML = oSrc.m_osSpecialConstantsXML;
2625 : };
2626 :
2627 3 : if (poNewLayer->m_iLatField >= 0)
2628 : {
2629 2 : copyField(poNewLayer->m_aoFields[poNewLayer->m_iLatField],
2630 2 : poOriLayer->m_aoFields[poOriLayer->m_iLatField]);
2631 : }
2632 3 : if (poNewLayer->m_iLongField >= 0)
2633 : {
2634 2 : copyField(poNewLayer->m_aoFields[poNewLayer->m_iLongField],
2635 2 : poOriLayer->m_aoFields[poOriLayer->m_iLongField]);
2636 : }
2637 3 : if (poNewLayer->m_iAltField >= 0)
2638 : {
2639 2 : copyField(poNewLayer->m_aoFields[poNewLayer->m_iAltField],
2640 2 : poOriLayer->m_aoFields[poOriLayer->m_iAltField]);
2641 : }
2642 :
2643 3 : OGRFeatureDefn *poEditableFDefn = poEditableLayer->GetLayerDefn();
2644 44 : for (int i = 0; i < poEditableFDefn->GetFieldCount(); i++)
2645 : {
2646 41 : auto poFieldDefn = poEditableFDefn->GetFieldDefn(i);
2647 41 : poNewLayer->CreateField(poFieldDefn, false);
2648 41 : int idx = poOriLayer->m_poRawFeatureDefn->GetFieldIndex(
2649 : poFieldDefn->GetNameRef());
2650 41 : if (idx >= 0)
2651 : {
2652 41 : copyField(poNewLayer->m_aoFields.back(),
2653 41 : poOriLayer->m_aoFields[idx]);
2654 82 : OGRFieldDefn *poOriFieldDefn =
2655 41 : poOriLayer->m_poRawFeatureDefn->GetFieldDefn(idx);
2656 41 : if (poFieldDefn->GetType() == poOriFieldDefn->GetType())
2657 : {
2658 41 : poNewLayer->m_aoFields.back().m_osDataType =
2659 41 : poOriLayer->m_aoFields[idx].m_osDataType;
2660 : }
2661 : }
2662 : }
2663 :
2664 3 : poEditableLayer->ResetReading();
2665 :
2666 : // Disable all filters.
2667 3 : const char *pszQueryStringConst = poEditableLayer->GetAttrQueryString();
2668 3 : char *pszQueryStringBak =
2669 2 : pszQueryStringConst ? CPLStrdup(pszQueryStringConst) : nullptr;
2670 3 : poEditableLayer->SetAttributeFilter(nullptr);
2671 :
2672 3 : const int iFilterGeomIndexBak = poEditableLayer->GetGeomFieldFilter();
2673 3 : OGRGeometry *poFilterGeomBak = poEditableLayer->GetSpatialFilter();
2674 3 : if (poFilterGeomBak)
2675 0 : poFilterGeomBak = poFilterGeomBak->clone();
2676 3 : poEditableLayer->SetSpatialFilter(nullptr);
2677 :
2678 6 : auto aoMapSrcToTargetIdx = poNewLayer->GetLayerDefn()->ComputeMapForSetFrom(
2679 3 : poEditableLayer->GetLayerDefn(), true);
2680 3 : aoMapSrcToTargetIdx.push_back(
2681 3 : -1); // add dummy entry to be sure that .data() is valid
2682 :
2683 3 : OGRErr eErr = OGRERR_NONE;
2684 45 : for (auto &&poFeature : poEditableLayer)
2685 : {
2686 21 : OGRFeature *poNewFeature = new OGRFeature(poNewLayer->GetLayerDefn());
2687 21 : poNewFeature->SetFrom(poFeature.get(), aoMapSrcToTargetIdx.data(),
2688 : true);
2689 21 : eErr = poNewLayer->CreateFeature(poNewFeature);
2690 21 : delete poNewFeature;
2691 21 : if (eErr != OGRERR_NONE)
2692 : {
2693 0 : break;
2694 : }
2695 : }
2696 :
2697 : // Restore filters.
2698 3 : poEditableLayer->SetAttributeFilter(pszQueryStringBak);
2699 3 : CPLFree(pszQueryStringBak);
2700 3 : poEditableLayer->SetSpatialFilter(iFilterGeomIndexBak, poFilterGeomBak);
2701 3 : delete poFilterGeomBak;
2702 :
2703 6 : if (eErr != OGRERR_NONE ||
2704 3 : !poNewLayer->RenameFileTo(poOriLayer->GetFileName()))
2705 : {
2706 0 : delete poNewLayer;
2707 0 : VSIUnlink(osTmpFilename);
2708 0 : return OGRERR_FAILURE;
2709 : }
2710 :
2711 3 : delete poOriLayer;
2712 3 : *ppoDecoratedLayer = poNewLayer;
2713 :
2714 3 : return OGRERR_NONE;
2715 : }
2716 :
2717 : /************************************************************************/
2718 : /* ==================================================================== */
2719 : /* PDS4EditableLayer */
2720 : /* ==================================================================== */
2721 : /************************************************************************/
2722 :
2723 44 : PDS4EditableLayer::PDS4EditableLayer(
2724 44 : std::unique_ptr<PDS4FixedWidthTable> poBaseLayer)
2725 : : OGREditableLayer(
2726 44 : poBaseLayer.release(), true,
2727 44 : std::make_unique<PDS4EditableSynchronizer<PDS4FixedWidthTable>>()
2728 44 : .release(),
2729 88 : true)
2730 : {
2731 44 : }
2732 :
2733 118 : PDS4EditableLayer::PDS4EditableLayer(
2734 118 : std::unique_ptr<PDS4DelimitedTable> poBaseLayer)
2735 : : OGREditableLayer(
2736 118 : poBaseLayer.release(), true,
2737 118 : std::make_unique<PDS4EditableSynchronizer<PDS4DelimitedTable>>()
2738 118 : .release(),
2739 236 : true)
2740 : {
2741 118 : }
2742 :
2743 : PDS4EditableLayer::~PDS4EditableLayer() = default;
2744 :
2745 : /************************************************************************/
2746 : /* GetBaseLayer() */
2747 : /************************************************************************/
2748 :
2749 347 : PDS4TableBaseLayer *PDS4EditableLayer::GetBaseLayer() const
2750 : {
2751 347 : return cpl::down_cast<PDS4TableBaseLayer *>(
2752 347 : OGREditableLayer::GetBaseLayer());
2753 : }
2754 :
2755 : /************************************************************************/
2756 : /* SetSpatialRef() */
2757 : /************************************************************************/
2758 :
2759 4 : void PDS4EditableLayer::SetSpatialRef(OGRSpatialReference *poSRS)
2760 : {
2761 4 : if (GetGeomType() != wkbNone)
2762 : {
2763 4 : GetLayerDefn()->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
2764 4 : GetBaseLayer()->GetLayerDefn()->GetGeomFieldDefn(0)->SetSpatialRef(
2765 : poSRS);
2766 : }
2767 4 : }
|