Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: VDV Translator
4 : * Purpose: Implements OGRVDVFDriver.
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "ogr_vdv.h"
14 : #include "cpl_conv.h"
15 : #include "cpl_time.h"
16 :
17 : #include "memdataset.h"
18 :
19 : #include <map>
20 :
21 : #ifdef EMBED_RESOURCE_FILES
22 : #include "embedded_resources.h"
23 : #endif
24 :
25 : #ifndef STARTS_WITH_CI
26 : #define STARTS_WITH(a, b) (strncmp(a, b, strlen(b)) == 0)
27 : #define STARTS_WITH_CI(a, b) EQUALN(a, b, strlen(b))
28 : #endif
29 :
30 : typedef enum
31 : {
32 : LAYER_OTHER,
33 : LAYER_NODE,
34 : LAYER_LINK,
35 : LAYER_LINKCOORDINATE
36 : } IDFLayerType;
37 :
38 : /************************************************************************/
39 : /* OGRVDVParseAtrFrm() */
40 : /************************************************************************/
41 :
42 102 : static void OGRVDVParseAtrFrm(OGRLayer *poLayer, OGRFeatureDefn *poFeatureDefn,
43 : char **papszAtr, char **papszFrm)
44 : {
45 489 : for (int i = 0; papszAtr[i]; i++)
46 : {
47 387 : OGRFieldType eType = OFTString;
48 387 : int nWidth = 0;
49 387 : OGRFieldSubType eSubType = OFSTNone;
50 387 : if (STARTS_WITH_CI(papszFrm[i], "decimal"))
51 : {
52 90 : if (papszFrm[i][strlen("decimal")] == '(')
53 : {
54 90 : if (strchr(papszFrm[i], ',') &&
55 34 : atoi(strchr(papszFrm[i], ',') + 1) > 0)
56 : {
57 34 : eType = OFTReal;
58 : }
59 : else
60 : {
61 56 : nWidth = atoi(papszFrm[i] + strlen("decimal") + 1);
62 56 : if (nWidth >= 10)
63 48 : eType = OFTInteger64;
64 : else
65 8 : eType = OFTInteger;
66 : }
67 : }
68 : else
69 0 : eType = OFTInteger;
70 : }
71 297 : else if (STARTS_WITH_CI(papszFrm[i], "num"))
72 : {
73 113 : if (papszFrm[i][strlen("num")] == '[')
74 : {
75 113 : if (strchr(papszFrm[i], '.') &&
76 113 : atoi(strchr(papszFrm[i], '.') + 1) > 0)
77 : {
78 0 : eType = OFTReal;
79 : }
80 : else
81 : {
82 113 : nWidth = atoi(papszFrm[i] + strlen("num") + 1);
83 113 : if (nWidth < 0 || nWidth >= 100)
84 : {
85 0 : nWidth = 0;
86 0 : eType = OFTInteger;
87 : }
88 : else
89 : {
90 113 : nWidth += 1; /* VDV-451 width is without sign */
91 113 : if (nWidth >= 10)
92 70 : eType = OFTInteger64;
93 : else
94 43 : eType = OFTInteger;
95 : }
96 : }
97 : }
98 : else
99 0 : eType = OFTInteger;
100 : }
101 184 : else if (STARTS_WITH_CI(papszFrm[i], "char"))
102 : {
103 145 : if (papszFrm[i][strlen("char")] == '[')
104 : {
105 145 : nWidth = atoi(papszFrm[i] + strlen("char") + 1);
106 145 : if (nWidth < 0)
107 0 : nWidth = 0;
108 : }
109 : }
110 39 : else if (STARTS_WITH_CI(papszFrm[i], "boolean"))
111 : {
112 15 : eType = OFTInteger;
113 15 : eSubType = OFSTBoolean;
114 : }
115 774 : OGRFieldDefn oFieldDefn(papszAtr[i], eType);
116 387 : oFieldDefn.SetSubType(eSubType);
117 387 : oFieldDefn.SetWidth(nWidth);
118 387 : if (poLayer)
119 114 : poLayer->CreateField(&oFieldDefn);
120 273 : else if (poFeatureDefn)
121 273 : poFeatureDefn->AddFieldDefn(&oFieldDefn);
122 : else
123 : {
124 0 : CPLAssert(false);
125 : }
126 : }
127 102 : }
128 :
129 : /************************************************************************/
130 : /* OGRIDFDataSource() */
131 : /************************************************************************/
132 :
133 8 : OGRIDFDataSource::OGRIDFDataSource(const char *pszFilename, VSILFILE *fpLIn)
134 8 : : m_osFilename(pszFilename), m_fpL(fpLIn)
135 : {
136 8 : }
137 :
138 : /************************************************************************/
139 : /* ~OGRIDFDataSource() */
140 : /************************************************************************/
141 :
142 16 : OGRIDFDataSource::~OGRIDFDataSource()
143 : {
144 16 : CPLString osTmpFilename;
145 8 : if (m_bDestroyTmpDS && m_poTmpDS)
146 : {
147 0 : osTmpFilename = m_poTmpDS->GetDescription();
148 : }
149 8 : delete m_poTmpDS;
150 8 : if (m_bDestroyTmpDS)
151 : {
152 0 : VSIUnlink(osTmpFilename);
153 : }
154 8 : if (m_fpL)
155 : {
156 8 : VSIFCloseL(m_fpL);
157 : }
158 16 : }
159 :
160 : /************************************************************************/
161 : /* Parse() */
162 : /************************************************************************/
163 :
164 8 : std::pair<GDALDataset *, bool> OGRIDFDataSource::Parse() const
165 : {
166 8 : GDALDataset *poTmpDS = nullptr;
167 8 : bool bDestroyTmpDS = false;
168 : VSIStatBufL sStatBuf;
169 8 : bool bGPKG = false;
170 8 : vsi_l_offset nFileSize = 0;
171 8 : bool bSpatialIndex = false;
172 16 : if (VSIStatL(m_osFilename, &sStatBuf) == 0 &&
173 8 : sStatBuf.st_size > CPLAtoGIntBig(CPLGetConfigOption(
174 : "OGR_IDF_TEMP_DB_THRESHOLD", "100000000")))
175 : {
176 1 : nFileSize = sStatBuf.st_size;
177 :
178 : GDALDriver *poGPKGDriver =
179 1 : reinterpret_cast<GDALDriver *>(GDALGetDriverByName("GPKG"));
180 1 : if (poGPKGDriver)
181 : {
182 2 : CPLString osTmpFilename(m_osFilename + "_tmp.gpkg");
183 1 : VSILFILE *fp = VSIFOpenL(osTmpFilename, "wb");
184 1 : if (fp)
185 : {
186 1 : VSIFCloseL(fp);
187 : }
188 : else
189 : {
190 0 : osTmpFilename = CPLGenerateTempFilenameSafe(
191 0 : CPLGetBasenameSafe(m_osFilename).c_str());
192 0 : osTmpFilename += ".gpkg";
193 : }
194 1 : VSIUnlink(osTmpFilename);
195 : {
196 : CPLConfigOptionSetter oSetter1("OGR_SQLITE_JOURNAL", "OFF",
197 2 : false);
198 : // For use of OGR VSI-based SQLite3 VFS implementation, as
199 : // the regular SQLite3 implementation has some issues to deal
200 : // with a file that is deleted after having been created.
201 : // For example on MacOS Big Sur system's sqlite 3.32.3
202 : // when chaining ogr_sqlite.py and ogr_vdv.py, or in Vagrant
203 : // Ubuntu 22.04 environment with sqlite 3.37.2
204 : CPLConfigOptionSetter oSetter2("SQLITE_USE_OGR_VFS", "YES",
205 1 : false);
206 1 : poTmpDS = poGPKGDriver->Create(osTmpFilename, 0, 0, 0,
207 : GDT_Unknown, nullptr);
208 : }
209 1 : bGPKG = poTmpDS != nullptr;
210 1 : bDestroyTmpDS = CPLTestBool(CPLGetConfigOption(
211 2 : "OGR_IDF_DELETE_TEMP_DB", "YES")) &&
212 1 : poTmpDS != nullptr;
213 1 : if (bDestroyTmpDS)
214 : {
215 1 : CPLPushErrorHandler(CPLQuietErrorHandler);
216 1 : bDestroyTmpDS = VSIUnlink(osTmpFilename) != 0;
217 1 : CPLPopErrorHandler();
218 : }
219 : else
220 : {
221 0 : bSpatialIndex = true;
222 : }
223 : }
224 : }
225 :
226 8 : bool bIsMEMLayer = false;
227 8 : if (poTmpDS == nullptr)
228 : {
229 7 : bIsMEMLayer = true;
230 7 : poTmpDS = MEMDataset::Create("", 0, 0, 0, GDT_Unknown, nullptr);
231 : }
232 :
233 8 : poTmpDS->StartTransaction();
234 :
235 8 : OGRLayer *poCurLayer = nullptr;
236 :
237 : struct Point
238 : {
239 : double x;
240 : double y;
241 : double z;
242 :
243 32 : explicit Point(double xIn = 0, double yIn = 0, double zIn = 0)
244 32 : : x(xIn), y(yIn), z(zIn)
245 : {
246 32 : }
247 : };
248 :
249 16 : std::map<GIntBig, Point> oMapNode; // map from NODE_ID to Point
250 : std::map<GIntBig, OGRLineString *>
251 16 : oMapLinkCoordinate; // map from LINK_ID to OGRLineString*
252 16 : CPLString osTablename, osAtr, osFrm;
253 8 : int iX = -1, iY = -1, iZ = -1;
254 8 : bool bAdvertiseUTF8 = false;
255 8 : bool bRecodeFromLatin1 = false;
256 8 : int iNodeID = -1;
257 8 : int iLinkID = -1;
258 8 : int iFromNode = -1;
259 8 : int iToNode = -1;
260 8 : IDFLayerType eLayerType = LAYER_OTHER;
261 :
262 : // We assume that layers are in the order Node, Link, LinkCoordinate
263 :
264 8 : GUIntBig nLineCount = 0;
265 : while (true)
266 : {
267 312 : if (nFileSize)
268 : {
269 39 : ++nLineCount;
270 39 : if ((nLineCount % 32768) == 0)
271 : {
272 0 : const vsi_l_offset nPos = VSIFTellL(m_fpL);
273 0 : CPLDebug("IDF", "Reading progress: %.2f %%",
274 0 : 100.0 * nPos / nFileSize);
275 : }
276 : }
277 :
278 312 : const char *pszLine = CPLReadLineL(m_fpL);
279 312 : if (pszLine == nullptr)
280 8 : break;
281 :
282 304 : if (strcmp(pszLine, "chs;ISO_LATIN_1") == 0)
283 : {
284 8 : bAdvertiseUTF8 = true;
285 8 : bRecodeFromLatin1 = true;
286 : }
287 296 : else if (STARTS_WITH(pszLine, "tbl;"))
288 : {
289 32 : poCurLayer = nullptr;
290 32 : osTablename = pszLine + 4;
291 32 : osAtr = "";
292 32 : osFrm = "";
293 32 : iX = iY = iNodeID = iLinkID = iFromNode = iToNode = -1;
294 32 : eLayerType = LAYER_OTHER;
295 : }
296 264 : else if (STARTS_WITH(pszLine, "atr;"))
297 : {
298 32 : osAtr = pszLine + 4;
299 32 : osAtr.Trim();
300 : }
301 232 : else if (STARTS_WITH(pszLine, "frm;"))
302 : {
303 32 : osFrm = pszLine + 4;
304 32 : osFrm.Trim();
305 : }
306 200 : else if (STARTS_WITH(pszLine, "rec;"))
307 : {
308 80 : if (poCurLayer == nullptr)
309 : {
310 32 : char **papszAtr = CSLTokenizeString2(osAtr, ";",
311 : CSLT_ALLOWEMPTYTOKENS |
312 : CSLT_STRIPLEADSPACES |
313 : CSLT_STRIPENDSPACES);
314 32 : char **papszFrm = CSLTokenizeString2(osFrm, ";",
315 : CSLT_ALLOWEMPTYTOKENS |
316 : CSLT_STRIPLEADSPACES |
317 : CSLT_STRIPENDSPACES);
318 32 : char *apszOptions[2] = {nullptr, nullptr};
319 32 : if (bAdvertiseUTF8 && !bGPKG)
320 28 : apszOptions[0] = (char *)"ADVERTIZE_UTF8=YES";
321 4 : else if (bGPKG && !bSpatialIndex)
322 4 : apszOptions[0] = (char *)"SPATIAL_INDEX=NO";
323 :
324 32 : if (EQUAL(osTablename, "Node") &&
325 40 : (iX = CSLFindString(papszAtr, "X")) >= 0 &&
326 8 : (iY = CSLFindString(papszAtr, "Y")) >= 0)
327 : {
328 8 : iZ = CSLFindString(papszAtr, "Z");
329 8 : eLayerType = LAYER_NODE;
330 8 : iNodeID = CSLFindString(papszAtr, "NODE_ID");
331 : OGRSpatialReference *poSRS =
332 8 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
333 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
334 8 : poCurLayer = poTmpDS->CreateLayer(
335 : osTablename, poSRS, iZ < 0 ? wkbPoint : wkbPoint25D,
336 : apszOptions);
337 8 : poSRS->Release();
338 : }
339 24 : else if (EQUAL(osTablename, "Link") &&
340 8 : (iLinkID = CSLFindString(papszAtr, "LINK_ID")) >= 0 &&
341 8 : ((iFromNode = CSLFindString(papszAtr, "FROM_NODE")) >=
342 32 : 0) &&
343 8 : ((iToNode = CSLFindString(papszAtr, "TO_NODE")) >= 0))
344 : {
345 8 : eLayerType = LAYER_LINK;
346 : OGRSpatialReference *poSRS =
347 8 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
348 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
349 8 : poCurLayer = poTmpDS->CreateLayer(
350 : osTablename, poSRS,
351 : iZ < 0 ? wkbLineString : wkbLineString25D, apszOptions);
352 8 : poSRS->Release();
353 : }
354 16 : else if (EQUAL(osTablename, "LinkCoordinate") &&
355 8 : (iLinkID = CSLFindString(papszAtr, "LINK_ID")) >= 0 &&
356 8 : CSLFindString(papszAtr, "COUNT") >= 0 &&
357 32 : (iX = CSLFindString(papszAtr, "X")) >= 0 &&
358 8 : (iY = CSLFindString(papszAtr, "Y")) >= 0)
359 : {
360 8 : iZ = CSLFindString(papszAtr, "Z");
361 8 : eLayerType = LAYER_LINKCOORDINATE;
362 : OGRSpatialReference *poSRS =
363 8 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
364 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
365 8 : poCurLayer = poTmpDS->CreateLayer(
366 : osTablename, poSRS, iZ < 0 ? wkbPoint : wkbPoint25D,
367 : apszOptions);
368 8 : poSRS->Release();
369 : }
370 : else
371 : {
372 8 : poCurLayer = poTmpDS->CreateLayer(osTablename, nullptr,
373 : wkbNone, apszOptions);
374 : }
375 32 : if (poCurLayer == nullptr)
376 : {
377 0 : CSLDestroy(papszAtr);
378 0 : CSLDestroy(papszFrm);
379 0 : break;
380 : }
381 :
382 32 : if (!osAtr.empty() && CSLCount(papszAtr) == CSLCount(papszFrm))
383 : {
384 32 : OGRVDVParseAtrFrm(poCurLayer, nullptr, papszAtr, papszFrm);
385 : }
386 32 : CSLDestroy(papszAtr);
387 32 : CSLDestroy(papszFrm);
388 : }
389 :
390 80 : OGRErr eErr = OGRERR_NONE;
391 : char **papszTokens =
392 80 : CSLTokenizeStringComplex(pszLine + 4, ";", TRUE, TRUE);
393 80 : OGRFeatureDefn *poFDefn = poCurLayer->GetLayerDefn();
394 80 : OGRFeature *poFeature = new OGRFeature(poFDefn);
395 405 : for (int i = 0;
396 405 : i < poFDefn->GetFieldCount() && papszTokens[i] != nullptr; i++)
397 : {
398 325 : if (papszTokens[i][0])
399 : {
400 650 : if (bRecodeFromLatin1 &&
401 325 : poFDefn->GetFieldDefn(i)->GetType() == OFTString)
402 : {
403 144 : char *pszRecoded = CPLRecode(
404 72 : papszTokens[i], CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
405 72 : poFeature->SetField(i, pszRecoded);
406 72 : CPLFree(pszRecoded);
407 : }
408 : else
409 : {
410 253 : poFeature->SetField(i, papszTokens[i]);
411 : }
412 : }
413 : }
414 :
415 80 : if (eLayerType == LAYER_NODE && iX >= 0 && iY >= 0 && iNodeID >= 0)
416 : {
417 16 : double dfX = poFeature->GetFieldAsDouble(iX);
418 16 : double dfY = poFeature->GetFieldAsDouble(iY);
419 : OGRGeometry *poGeom;
420 16 : if (iZ >= 0)
421 : {
422 2 : double dfZ = poFeature->GetFieldAsDouble(iZ);
423 2 : oMapNode[poFeature->GetFieldAsInteger64(iNodeID)] =
424 2 : Point(dfX, dfY, dfZ);
425 2 : poGeom = new OGRPoint(dfX, dfY, dfZ);
426 : }
427 : else
428 : {
429 14 : oMapNode[poFeature->GetFieldAsInteger64(iNodeID)] =
430 14 : Point(dfX, dfY);
431 14 : poGeom = new OGRPoint(dfX, dfY);
432 : }
433 16 : poGeom->assignSpatialReference(
434 16 : poFDefn->GetGeomFieldDefn(0)->GetSpatialRef());
435 16 : poFeature->SetGeometryDirectly(poGeom);
436 : }
437 64 : else if (eLayerType == LAYER_LINK && iFromNode >= 0 && iToNode >= 0)
438 : {
439 32 : GIntBig nFromNode = poFeature->GetFieldAsInteger64(iFromNode);
440 32 : GIntBig nToNode = poFeature->GetFieldAsInteger64(iToNode);
441 : std::map<GIntBig, Point>::iterator oIterFrom =
442 32 : oMapNode.find(nFromNode);
443 : std::map<GIntBig, Point>::iterator oIterTo =
444 32 : oMapNode.find(nToNode);
445 32 : if (oIterFrom != oMapNode.end() && oIterTo != oMapNode.end())
446 : {
447 16 : OGRLineString *poLS = new OGRLineString();
448 16 : if (iZ >= 0)
449 : {
450 2 : poLS->addPoint(oIterFrom->second.x, oIterFrom->second.y,
451 2 : oIterFrom->second.z);
452 2 : poLS->addPoint(oIterTo->second.x, oIterTo->second.y,
453 2 : oIterTo->second.z);
454 : }
455 : else
456 : {
457 14 : poLS->addPoint(oIterFrom->second.x,
458 14 : oIterFrom->second.y);
459 14 : poLS->addPoint(oIterTo->second.x, oIterTo->second.y);
460 : }
461 16 : poLS->assignSpatialReference(
462 16 : poFDefn->GetGeomFieldDefn(0)->GetSpatialRef());
463 16 : poFeature->SetGeometryDirectly(poLS);
464 32 : }
465 : }
466 32 : else if (eLayerType == LAYER_LINKCOORDINATE && iX >= 0 && iY >= 0 &&
467 : iLinkID >= 0)
468 : {
469 24 : double dfX = poFeature->GetFieldAsDouble(iX);
470 24 : double dfY = poFeature->GetFieldAsDouble(iY);
471 24 : double dfZ = 0.0;
472 : OGRGeometry *poGeom;
473 24 : if (iZ >= 0)
474 : {
475 3 : dfZ = poFeature->GetFieldAsDouble(iZ);
476 3 : poGeom = new OGRPoint(dfX, dfY, dfZ);
477 : }
478 : else
479 : {
480 21 : poGeom = new OGRPoint(dfX, dfY);
481 : }
482 24 : poGeom->assignSpatialReference(
483 24 : poFDefn->GetGeomFieldDefn(0)->GetSpatialRef());
484 24 : poFeature->SetGeometryDirectly(poGeom);
485 :
486 24 : GIntBig nCurLinkID = poFeature->GetFieldAsInteger64(iLinkID);
487 : std::map<GIntBig, OGRLineString *>::iterator
488 : oMapLinkCoordinateIter =
489 24 : oMapLinkCoordinate.find(nCurLinkID);
490 24 : if (oMapLinkCoordinateIter == oMapLinkCoordinate.end())
491 : {
492 16 : OGRLineString *poLS = new OGRLineString();
493 16 : if (iZ >= 0)
494 2 : poLS->addPoint(dfX, dfY, dfZ);
495 : else
496 14 : poLS->addPoint(dfX, dfY);
497 16 : oMapLinkCoordinate[nCurLinkID] = poLS;
498 : }
499 : else
500 : {
501 8 : if (iZ >= 0)
502 : {
503 1 : oMapLinkCoordinateIter->second->addPoint(dfX, dfY, dfZ);
504 : }
505 : else
506 : {
507 7 : oMapLinkCoordinateIter->second->addPoint(dfX, dfY);
508 : }
509 : }
510 : }
511 80 : eErr = poCurLayer->CreateFeature(poFeature);
512 80 : delete poFeature;
513 :
514 80 : CSLDestroy(papszTokens);
515 :
516 80 : if (eErr == OGRERR_FAILURE)
517 0 : break;
518 : }
519 304 : }
520 :
521 8 : oMapNode.clear();
522 :
523 : // Patch Link geometries with the intermediate points of LinkCoordinate
524 8 : OGRLayer *poLinkLyr = poTmpDS->GetLayerByName("Link");
525 8 : if (poLinkLyr && poLinkLyr->GetLayerDefn()->GetGeomFieldCount())
526 : {
527 8 : iLinkID = poLinkLyr->GetLayerDefn()->GetFieldIndex("LINK_ID");
528 8 : if (iLinkID >= 0)
529 : {
530 8 : poLinkLyr->ResetReading();
531 : const OGRSpatialReference *poSRS =
532 8 : poLinkLyr->GetLayerDefn()->GetGeomFieldDefn(0)->GetSpatialRef();
533 40 : for (auto &&poFeat : poLinkLyr)
534 : {
535 32 : GIntBig nLinkID = poFeat->GetFieldAsInteger64(iLinkID);
536 : std::map<GIntBig, OGRLineString *>::iterator
537 32 : oMapLinkCoordinateIter = oMapLinkCoordinate.find(nLinkID);
538 32 : OGRGeometry *poGeom = poFeat->GetGeometryRef();
539 48 : if (poGeom &&
540 48 : oMapLinkCoordinateIter != oMapLinkCoordinate.end())
541 : {
542 8 : OGRLineString *poLS = poGeom->toLineString();
543 8 : if (poLS)
544 : {
545 : OGRLineString *poLSIntermediate =
546 8 : oMapLinkCoordinateIter->second;
547 8 : OGRLineString *poLSNew = new OGRLineString();
548 8 : if (poLS->getGeometryType() == wkbLineString25D)
549 : {
550 1 : poLSNew->addPoint(poLS->getX(0), poLS->getY(0),
551 : poLS->getZ(0));
552 3 : for (int i = 0;
553 3 : i < poLSIntermediate->getNumPoints(); i++)
554 : {
555 2 : poLSNew->addPoint(poLSIntermediate->getX(i),
556 : poLSIntermediate->getY(i),
557 : poLSIntermediate->getZ(i));
558 : }
559 1 : poLSNew->addPoint(poLS->getX(1), poLS->getY(1),
560 : poLS->getZ(1));
561 : }
562 : else
563 : {
564 7 : poLSNew->addPoint(poLS->getX(0), poLS->getY(0));
565 21 : for (int i = 0;
566 21 : i < poLSIntermediate->getNumPoints(); i++)
567 : {
568 14 : poLSNew->addPoint(poLSIntermediate->getX(i),
569 : poLSIntermediate->getY(i));
570 : }
571 7 : poLSNew->addPoint(poLS->getX(1), poLS->getY(1));
572 : }
573 8 : poLSNew->assignSpatialReference(poSRS);
574 8 : poFeat->SetGeometryDirectly(poLSNew);
575 8 : CPL_IGNORE_RET_VAL(poLinkLyr->SetFeature(poFeat.get()));
576 : }
577 : }
578 : }
579 8 : poLinkLyr->ResetReading();
580 : }
581 : }
582 :
583 8 : poTmpDS->CommitTransaction();
584 :
585 8 : if (bIsMEMLayer)
586 7 : poTmpDS->ExecuteSQL("PRAGMA read_only=1", nullptr, nullptr);
587 :
588 : std::map<GIntBig, OGRLineString *>::iterator oMapLinkCoordinateIter =
589 8 : oMapLinkCoordinate.begin();
590 24 : for (; oMapLinkCoordinateIter != oMapLinkCoordinate.end();
591 16 : ++oMapLinkCoordinateIter)
592 16 : delete oMapLinkCoordinateIter->second;
593 :
594 16 : return {poTmpDS, bDestroyTmpDS};
595 : }
596 :
597 : /************************************************************************/
598 : /* GetLayerCount() */
599 : /************************************************************************/
600 :
601 450 : int OGRIDFDataSource::GetLayerCount() const
602 : {
603 450 : GDALDataset *poTmpDS = m_poTmpDS;
604 450 : bool bDestroyTmpDS = m_bDestroyTmpDS;
605 : {
606 900 : std::lock_guard oLock(m_oMutex);
607 450 : if (!m_bHasParsed)
608 : {
609 8 : m_bHasParsed = true;
610 8 : std::tie(poTmpDS, bDestroyTmpDS) = Parse();
611 : }
612 : }
613 450 : m_poTmpDS = poTmpDS;
614 450 : m_bDestroyTmpDS = bDestroyTmpDS;
615 450 : if (m_poTmpDS == nullptr)
616 0 : return 0;
617 450 : return m_poTmpDS->GetLayerCount();
618 : }
619 :
620 : /************************************************************************/
621 : /* GetLayer() */
622 : /************************************************************************/
623 :
624 232 : const OGRLayer *OGRIDFDataSource::GetLayer(int iLayer) const
625 : {
626 232 : if (iLayer < 0 || iLayer >= GetLayerCount())
627 2 : return nullptr;
628 230 : CPLAssert(m_poTmpDS);
629 230 : return m_poTmpDS->GetLayer(iLayer);
630 : }
631 :
632 : /************************************************************************/
633 : /* TestCapability() */
634 : /************************************************************************/
635 :
636 27 : int OGRIDFDataSource::TestCapability(const char *pszCap) const
637 : {
638 27 : if (EQUAL(pszCap, ODsCMeasuredGeometries))
639 8 : return true;
640 19 : else if (EQUAL(pszCap, ODsCCurveGeometries))
641 8 : return true;
642 11 : else if (EQUAL(pszCap, ODsCZGeometries))
643 8 : return true;
644 :
645 3 : return false;
646 : }
647 :
648 : /************************************************************************/
649 : /* OGRVDVDataSource() */
650 : /************************************************************************/
651 :
652 119 : OGRVDVDataSource::OGRVDVDataSource(const char *pszFilename, VSILFILE *fpL,
653 119 : bool bUpdate, bool bSingleFile, bool bNew)
654 : : m_osFilename(pszFilename), m_fpL(fpL), m_bUpdate(bUpdate),
655 : m_bSingleFile(bSingleFile), m_bNew(bNew),
656 119 : m_bLayersDetected(bNew || fpL == nullptr), m_nLayerCount(0),
657 : m_papoLayers(nullptr), m_poCurrentWriterLayer(nullptr),
658 238 : m_bMustWriteEof(false), m_bVDV452Loaded(false)
659 : {
660 119 : }
661 :
662 : /************************************************************************/
663 : /* ~OGRVDVDataSource() */
664 : /************************************************************************/
665 :
666 238 : OGRVDVDataSource::~OGRVDVDataSource()
667 : {
668 119 : if (m_poCurrentWriterLayer)
669 : {
670 27 : m_poCurrentWriterLayer->StopAsCurrentLayer();
671 27 : m_poCurrentWriterLayer = nullptr;
672 : }
673 :
674 299 : for (int i = 0; i < m_nLayerCount; i++)
675 180 : delete m_papoLayers[i];
676 119 : CPLFree(m_papoLayers);
677 :
678 : // Close after destroying layers since they might use it (single file write)
679 119 : if (m_fpL)
680 : {
681 109 : if (m_bMustWriteEof)
682 : {
683 48 : VSIFPrintfL(m_fpL, "eof; %d\n", m_nLayerCount);
684 : }
685 109 : VSIFCloseL(m_fpL);
686 : }
687 238 : }
688 :
689 : /************************************************************************/
690 : /* GetLayerCount() */
691 : /************************************************************************/
692 :
693 1075 : int OGRVDVDataSource::GetLayerCount() const
694 : {
695 1075 : DetectLayers();
696 1075 : return m_nLayerCount;
697 : }
698 :
699 : /************************************************************************/
700 : /* GetLayer() */
701 : /************************************************************************/
702 :
703 502 : const OGRLayer *OGRVDVDataSource::GetLayer(int iLayer) const
704 : {
705 502 : if (iLayer < 0 || iLayer >= GetLayerCount())
706 4 : return nullptr;
707 498 : return m_papoLayers[iLayer];
708 : }
709 :
710 : /************************************************************************/
711 : /* DetectLayers() */
712 : /************************************************************************/
713 :
714 1075 : void OGRVDVDataSource::DetectLayers() const
715 : {
716 1075 : std::lock_guard oLock(m_oMutex);
717 :
718 1075 : if (m_bLayersDetected)
719 1044 : return;
720 :
721 31 : m_bLayersDetected = true;
722 :
723 : char szBuffer[1 + 1024 + 1];
724 31 : char chNextExpected = 't';
725 31 : char chNextExpected2 = 'r';
726 31 : char chNextExpected3 = 'e';
727 31 : bool bInTableName = false;
728 62 : CPLString osTableName;
729 31 : GIntBig nFeatureCount = 0;
730 31 : vsi_l_offset nStartOffset = 0;
731 31 : OGRVDVLayer *poLayer = nullptr;
732 31 : bool bFirstBuffer = true;
733 31 : bool bRecodeFromLatin1 = false;
734 :
735 31 : VSIFSeekL(m_fpL, 0, SEEK_SET);
736 :
737 : while (true)
738 : {
739 31 : size_t nRead = VSIFReadL(szBuffer, 1, 1024, m_fpL);
740 31 : szBuffer[nRead] = '\0';
741 31 : if (bFirstBuffer)
742 : {
743 31 : const char *pszChs = strstr(szBuffer, "\nchs;");
744 31 : if (pszChs)
745 : {
746 28 : pszChs += 5;
747 28 : CPLString osChs;
748 364 : for (; *pszChs != '\0' && *pszChs != '\r' && *pszChs != '\n';
749 : ++pszChs)
750 : {
751 336 : if (*pszChs != ' ' && *pszChs != '"')
752 252 : osChs += *pszChs;
753 : }
754 28 : bRecodeFromLatin1 =
755 28 : EQUAL(osChs, "ISO8859-1") || EQUAL(osChs, "ISO_LATIN_1");
756 : }
757 31 : bFirstBuffer = false;
758 : }
759 15561 : for (size_t i = 0; i < nRead; i++)
760 : {
761 15530 : if (bInTableName)
762 : {
763 640 : if (szBuffer[i] == '\r' || szBuffer[i] == '\n')
764 : {
765 70 : bInTableName = false;
766 70 : poLayer = new OGRVDVLayer(
767 : const_cast<OGRVDVDataSource *>(this), osTableName,
768 70 : m_fpL, false, bRecodeFromLatin1, nStartOffset);
769 70 : m_papoLayers = static_cast<OGRLayer **>(
770 140 : CPLRealloc(m_papoLayers,
771 70 : sizeof(OGRLayer *) * (m_nLayerCount + 1)));
772 70 : m_papoLayers[m_nLayerCount] = poLayer;
773 70 : m_nLayerCount++;
774 : }
775 570 : else if (szBuffer[i] != ' ')
776 : {
777 500 : osTableName += szBuffer[i];
778 500 : continue;
779 : }
780 : }
781 :
782 : // Reset state on end of line characters
783 15030 : if (szBuffer[i] == '\n' || szBuffer[i] == '\r')
784 : {
785 644 : chNextExpected = szBuffer[i];
786 644 : chNextExpected2 = szBuffer[i];
787 644 : chNextExpected3 = szBuffer[i];
788 : }
789 :
790 : // Detect tbl;
791 15030 : if (szBuffer[i] == chNextExpected)
792 : {
793 924 : if (chNextExpected == '\n' || chNextExpected == '\r')
794 644 : chNextExpected = 't';
795 280 : else if (chNextExpected == 't')
796 70 : chNextExpected = 'b';
797 210 : else if (chNextExpected == 'b')
798 70 : chNextExpected = 'l';
799 140 : else if (chNextExpected == 'l')
800 70 : chNextExpected = ';';
801 70 : else if (chNextExpected == ';')
802 : {
803 70 : if (poLayer != nullptr)
804 1 : poLayer->SetFeatureCount(nFeatureCount);
805 70 : poLayer = nullptr;
806 70 : nFeatureCount = 0;
807 70 : nStartOffset = VSIFTellL(m_fpL) + i + 1 - nRead - 4;
808 70 : bInTableName = true;
809 70 : osTableName.resize(0);
810 70 : chNextExpected = 0;
811 : }
812 : }
813 : else
814 14106 : chNextExpected = 0;
815 :
816 : // Detect rec;
817 15030 : if (szBuffer[i] == chNextExpected2)
818 : {
819 1220 : if (chNextExpected2 == '\n' || chNextExpected2 == '\r')
820 644 : chNextExpected2 = 'r';
821 576 : else if (chNextExpected2 == 'r')
822 144 : chNextExpected2 = 'e';
823 432 : else if (chNextExpected2 == 'e')
824 144 : chNextExpected2 = 'c';
825 288 : else if (chNextExpected2 == 'c')
826 144 : chNextExpected2 = ';';
827 144 : else if (chNextExpected2 == ';')
828 : {
829 144 : nFeatureCount++;
830 144 : chNextExpected2 = 0;
831 : }
832 : }
833 : else
834 13810 : chNextExpected2 = 0;
835 :
836 : // Detect end;
837 15030 : if (szBuffer[i] == chNextExpected3)
838 : {
839 939 : if (chNextExpected3 == '\n' || chNextExpected3 == '\r')
840 644 : chNextExpected3 = 'e';
841 295 : else if (chNextExpected3 == 'e')
842 94 : chNextExpected3 = 'n';
843 201 : else if (chNextExpected3 == 'n')
844 67 : chNextExpected3 = 'd';
845 134 : else if (chNextExpected3 == 'd')
846 67 : chNextExpected3 = ';';
847 67 : else if (chNextExpected3 == ';')
848 : {
849 67 : if (poLayer != nullptr)
850 67 : poLayer->SetFeatureCount(nFeatureCount);
851 67 : poLayer = nullptr;
852 67 : chNextExpected3 = 0;
853 : }
854 : }
855 : else
856 14091 : chNextExpected3 = 0;
857 : }
858 31 : if (nRead < 1024)
859 31 : break;
860 0 : }
861 31 : if (poLayer != nullptr)
862 2 : poLayer->SetFeatureCount(nFeatureCount);
863 : }
864 :
865 : /************************************************************************/
866 : /* OGRVDVLayer() */
867 : /************************************************************************/
868 :
869 95 : OGRVDVLayer::OGRVDVLayer(GDALDataset *poDS, const CPLString &osTableName,
870 : VSILFILE *fpL, bool bOwnFP, bool bRecodeFromLatin1,
871 95 : vsi_l_offset nStartOffset)
872 : : m_poDS(poDS), m_fpL(fpL), m_bOwnFP(bOwnFP),
873 : m_bRecodeFromLatin1(bRecodeFromLatin1), m_nStartOffset(nStartOffset),
874 : m_nCurOffset(0), m_nTotalFeatureCount(0), m_nFID(0), m_bEOF(false),
875 95 : m_iLongitudeVDV452(-1), m_iLatitudeVDV452(-1)
876 : {
877 95 : m_poFeatureDefn = new OGRFeatureDefn(osTableName);
878 95 : m_poFeatureDefn->SetGeomType(wkbNone);
879 95 : m_poFeatureDefn->Reference();
880 95 : SetDescription(osTableName);
881 95 : vsi_l_offset nCurOffset = VSIFTellL(fpL);
882 95 : VSIFSeekL(m_fpL, m_nStartOffset, SEEK_SET);
883 190 : CPLString osAtr, osFrm;
884 :
885 : /* skip until first tbl; */
886 95 : bool bFoundTbl = false;
887 548 : for (int i = 0; i < 20; i++)
888 : {
889 548 : const char *pszLine = CPLReadLineL(m_fpL);
890 548 : if (pszLine == nullptr)
891 1 : break;
892 547 : if (STARTS_WITH(pszLine, "chs;"))
893 : {
894 24 : CPLString osChs(pszLine + 4);
895 24 : osChs.Trim();
896 24 : if (osChs.size() >= 2 && osChs[0] == '"' && osChs.back() == '"')
897 24 : osChs = osChs.substr(1, osChs.size() - 2);
898 24 : m_bRecodeFromLatin1 =
899 24 : EQUAL(osChs, "ISO8859-1") || EQUAL(osChs, "ISO_LATIN_1");
900 : }
901 523 : else if (STARTS_WITH(pszLine, "tbl;"))
902 : {
903 95 : if (bFoundTbl)
904 0 : break; /* shouldn't happen in correctly formed files */
905 95 : bFoundTbl = true;
906 95 : m_nStartOffset = VSIFTellL(fpL);
907 : }
908 428 : else if (STARTS_WITH(pszLine, "atr;"))
909 : {
910 95 : osAtr = pszLine + 4;
911 95 : osAtr.Trim();
912 : }
913 333 : else if (STARTS_WITH(pszLine, "frm;"))
914 : {
915 95 : osFrm = pszLine + 4;
916 95 : osFrm.Trim();
917 : }
918 238 : else if (STARTS_WITH(pszLine, "rec;") || STARTS_WITH(pszLine, "end;"))
919 : break;
920 : }
921 95 : if (!bFoundTbl)
922 0 : CPLDebug("VDV", "Didn't find tbl; line");
923 :
924 95 : VSIFSeekL(m_fpL, nCurOffset, SEEK_SET);
925 95 : if (!osAtr.empty() && !osFrm.empty())
926 : {
927 70 : char **papszAtr = CSLTokenizeString2(
928 : osAtr, ";",
929 : CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
930 70 : char **papszFrm = CSLTokenizeString2(
931 : osFrm, ";",
932 : CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
933 70 : if (CSLCount(papszAtr) == CSLCount(papszFrm))
934 : {
935 70 : OGRVDVParseAtrFrm(nullptr, m_poFeatureDefn, papszAtr, papszFrm);
936 : }
937 70 : CSLDestroy(papszAtr);
938 70 : CSLDestroy(papszFrm);
939 : }
940 :
941 : // Identify longitude, latitude columns of VDV-452 STOP table
942 95 : if (EQUAL(osTableName, "STOP")) /* English */
943 : {
944 2 : m_iLongitudeVDV452 = m_poFeatureDefn->GetFieldIndex("POINT_LONGITUDE");
945 2 : m_iLatitudeVDV452 = m_poFeatureDefn->GetFieldIndex("POINT_LATITUDE");
946 : }
947 93 : else if (EQUAL(osTableName, "REC_ORT")) /* German */
948 : {
949 2 : m_iLongitudeVDV452 = m_poFeatureDefn->GetFieldIndex("ORT_POS_LAENGE");
950 2 : m_iLatitudeVDV452 = m_poFeatureDefn->GetFieldIndex("ORT_POS_BREITE");
951 : }
952 95 : if (m_iLongitudeVDV452 >= 0 && m_iLatitudeVDV452 >= 0)
953 : {
954 4 : m_poFeatureDefn->SetGeomType(wkbPoint);
955 : OGRSpatialReference *poSRS =
956 4 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
957 4 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
958 4 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
959 4 : poSRS->Release();
960 : }
961 : else
962 91 : m_iLongitudeVDV452 = m_iLatitudeVDV452 = -1;
963 95 : }
964 :
965 : /************************************************************************/
966 : /* ~OGRVDVLayer() */
967 : /************************************************************************/
968 :
969 190 : OGRVDVLayer::~OGRVDVLayer()
970 : {
971 95 : m_poFeatureDefn->Release();
972 95 : if (m_bOwnFP)
973 25 : VSIFCloseL(m_fpL);
974 190 : }
975 :
976 : /************************************************************************/
977 : /* ResetReading() */
978 : /************************************************************************/
979 :
980 510 : void OGRVDVLayer::ResetReading()
981 : {
982 510 : VSIFSeekL(m_fpL, m_nStartOffset, SEEK_SET);
983 510 : m_nCurOffset = m_nStartOffset;
984 510 : m_nFID = 1;
985 510 : m_bEOF = false;
986 510 : }
987 :
988 : /************************************************************************/
989 : /* OGRVDVUnescapeString() */
990 : /************************************************************************/
991 :
992 369 : static CPLString OGRVDVUnescapeString(const char *pszValue)
993 : {
994 369 : CPLString osRet;
995 882 : for (; *pszValue != '\0'; ++pszValue)
996 : {
997 513 : if (*pszValue == '"' && pszValue[1] == '"')
998 : {
999 70 : osRet += '"';
1000 70 : ++pszValue;
1001 : }
1002 : else
1003 : {
1004 443 : osRet += *pszValue;
1005 : }
1006 : }
1007 369 : return osRet;
1008 : }
1009 :
1010 : /************************************************************************/
1011 : /* GetNextFeature() */
1012 : /************************************************************************/
1013 :
1014 577 : OGRFeature *OGRVDVLayer::GetNextFeature()
1015 : {
1016 577 : if (m_nFID == 0)
1017 8 : ResetReading();
1018 577 : VSIFSeekL(m_fpL, m_nCurOffset, SEEK_SET);
1019 577 : OGRFeature *poFeature = nullptr;
1020 1189 : while (!m_bEOF)
1021 : {
1022 1165 : const char *pszLine = CPLReadLineL(m_fpL);
1023 1165 : if (pszLine == nullptr)
1024 0 : break;
1025 1165 : if (strncmp(pszLine, "end;", 4) == 0 ||
1026 962 : strncmp(pszLine, "tbl;", 4) == 0)
1027 : {
1028 204 : m_bEOF = true;
1029 204 : break;
1030 : }
1031 961 : if (strncmp(pszLine, "rec;", 4) != 0)
1032 540 : continue;
1033 :
1034 421 : char **papszTokens = CSLTokenizeString2(
1035 : pszLine + 4, ";",
1036 : CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
1037 421 : poFeature = new OGRFeature(m_poFeatureDefn);
1038 421 : poFeature->SetFID(m_nFID++);
1039 1496 : for (int i = 0;
1040 1496 : i < m_poFeatureDefn->GetFieldCount() && papszTokens[i] != nullptr;
1041 : i++)
1042 : {
1043 1075 : if (papszTokens[i][0] && !EQUAL(papszTokens[i], "NULL"))
1044 : {
1045 517 : size_t nLen = strlen(papszTokens[i]);
1046 1034 : CPLString osToken;
1047 517 : if (nLen >= 2 && papszTokens[i][0] == '"' &&
1048 369 : papszTokens[i][nLen - 1] == '"')
1049 : {
1050 369 : papszTokens[i][nLen - 1] = 0;
1051 369 : osToken = OGRVDVUnescapeString(papszTokens[i] + 1);
1052 : }
1053 : else
1054 148 : osToken = papszTokens[i];
1055 : // Strip trailing spaces
1056 517 : while (!osToken.empty() && osToken.back() == ' ')
1057 0 : osToken.pop_back();
1058 : OGRFieldType eFieldType =
1059 517 : m_poFeatureDefn->GetFieldDefn(i)->GetType();
1060 517 : if (m_bRecodeFromLatin1 && eFieldType == OFTString)
1061 : {
1062 : char *pszRecoded =
1063 367 : CPLRecode(osToken, CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
1064 367 : poFeature->SetField(i, pszRecoded);
1065 367 : CPLFree(pszRecoded);
1066 : }
1067 150 : else if (eFieldType == OFTString || !EQUAL(osToken, "NULL"))
1068 : {
1069 150 : poFeature->SetField(i, osToken);
1070 : }
1071 : }
1072 : }
1073 421 : CSLDestroy(papszTokens);
1074 :
1075 421 : if (m_iLongitudeVDV452 >= 0 && m_iLatitudeVDV452 >= 0)
1076 : {
1077 : int nLongDegMinMS =
1078 4 : poFeature->GetFieldAsInteger(m_iLongitudeVDV452);
1079 4 : int nLongSign = 1;
1080 4 : if (nLongDegMinMS < 0)
1081 : {
1082 4 : nLongSign = -1;
1083 4 : nLongDegMinMS = -nLongDegMinMS;
1084 : }
1085 4 : const int nLongDeg = nLongDegMinMS / (100 * 100000);
1086 4 : const int nLongMin = (nLongDegMinMS / 100000) % 100;
1087 4 : const int nLongMS = nLongDegMinMS % 100000;
1088 4 : const double dfLong =
1089 4 : (nLongDeg + nLongMin / 60.0 + nLongMS / (3600.0 * 1000.0)) *
1090 : nLongSign;
1091 :
1092 4 : int nLatDegMinMS = poFeature->GetFieldAsInteger(m_iLatitudeVDV452);
1093 4 : int nLatSign = 1;
1094 4 : if (nLatDegMinMS < 0)
1095 : {
1096 4 : nLatSign = -1;
1097 4 : nLatDegMinMS = -nLatDegMinMS;
1098 : }
1099 4 : const int nLatDeg = nLatDegMinMS / (100 * 100000);
1100 4 : const int nLatMin = (nLatDegMinMS / 100000) % 100;
1101 4 : const int nLatMS = nLatDegMinMS % 100000;
1102 4 : const double dfLat =
1103 4 : (nLatDeg + nLatMin / 60.0 + nLatMS / (3600.0 * 1000.0)) *
1104 : nLatSign;
1105 :
1106 4 : if (dfLong != 0.0 || dfLat != 0.0)
1107 : {
1108 4 : OGRPoint *poPoint = new OGRPoint(dfLong, dfLat);
1109 4 : poPoint->assignSpatialReference(
1110 4 : m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
1111 4 : poFeature->SetGeometryDirectly(poPoint);
1112 : }
1113 : }
1114 :
1115 842 : if ((m_poFilterGeom == nullptr ||
1116 842 : FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
1117 421 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
1118 : {
1119 349 : break;
1120 : }
1121 72 : delete poFeature;
1122 72 : poFeature = nullptr;
1123 : }
1124 577 : m_nCurOffset = VSIFTellL(m_fpL);
1125 577 : return poFeature;
1126 : }
1127 :
1128 : /************************************************************************/
1129 : /* TestCapability() */
1130 : /************************************************************************/
1131 :
1132 228 : int OGRVDVLayer::TestCapability(const char *pszCap) const
1133 : {
1134 228 : if (EQUAL(pszCap, OLCFastFeatureCount) && m_nTotalFeatureCount > 0 &&
1135 0 : m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
1136 : {
1137 0 : return TRUE;
1138 : }
1139 228 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
1140 : {
1141 60 : return m_bRecodeFromLatin1;
1142 : }
1143 168 : else if (EQUAL(pszCap, OLCZGeometries))
1144 : {
1145 24 : return TRUE;
1146 : }
1147 144 : return FALSE;
1148 : }
1149 :
1150 : /************************************************************************/
1151 : /* GetFeatureCount() */
1152 : /************************************************************************/
1153 :
1154 78 : GIntBig OGRVDVLayer::GetFeatureCount(int bForce)
1155 : {
1156 78 : if (m_nTotalFeatureCount == 0 || m_poFilterGeom != nullptr ||
1157 24 : m_poAttrQuery != nullptr)
1158 : {
1159 58 : return OGRLayer::GetFeatureCount(bForce);
1160 : }
1161 20 : return m_nTotalFeatureCount;
1162 : }
1163 :
1164 : /************************************************************************/
1165 : /* Identify() */
1166 : /************************************************************************/
1167 :
1168 52741 : static int OGRVDVDriverIdentify(GDALOpenInfo *poOpenInfo)
1169 :
1170 : {
1171 52741 : if (poOpenInfo->bIsDirectory)
1172 1303 : return -1; /* perhaps... */
1173 : return (
1174 54267 : poOpenInfo->nHeaderBytes > 0 &&
1175 2829 : (strstr((const char *)poOpenInfo->pabyHeader, "\ntbl;") != nullptr ||
1176 2680 : strncmp((const char *)poOpenInfo->pabyHeader, "tbl;", 4) == 0) &&
1177 54420 : strstr((const char *)poOpenInfo->pabyHeader, "\natr;") != nullptr &&
1178 51591 : strstr((const char *)poOpenInfo->pabyHeader, "\nfrm;") != nullptr);
1179 : }
1180 :
1181 : /************************************************************************/
1182 : /* Open() */
1183 : /************************************************************************/
1184 :
1185 663 : GDALDataset *OGRVDVDataSource::Open(GDALOpenInfo *poOpenInfo)
1186 :
1187 : {
1188 663 : if (!OGRVDVDriverIdentify(poOpenInfo))
1189 : {
1190 0 : return nullptr;
1191 : }
1192 663 : if (poOpenInfo->bIsDirectory)
1193 : {
1194 590 : char **papszFiles = VSIReadDir(poOpenInfo->pszFilename);
1195 :
1196 : // Identify the extension with the most occurrences
1197 1180 : std::map<CPLString, int> oMapOtherExtensions;
1198 1180 : CPLString osMajorityExtension, osMajorityFile;
1199 590 : int nFiles = 0;
1200 24621 : for (char **papszIter = papszFiles; papszIter && *papszIter;
1201 : ++papszIter)
1202 : {
1203 24031 : if (EQUAL(*papszIter, ".") || EQUAL(*papszIter, ".."))
1204 550 : continue;
1205 23481 : nFiles++;
1206 46962 : const std::string osExtension(CPLGetExtensionSafe(*papszIter));
1207 23481 : int nCount = ++oMapOtherExtensions[osExtension];
1208 46301 : if (osMajorityExtension == "" ||
1209 22820 : nCount > oMapOtherExtensions[osMajorityExtension])
1210 : {
1211 1308 : osMajorityExtension = osExtension;
1212 1308 : osMajorityFile = *papszIter;
1213 : }
1214 : }
1215 :
1216 : // Check it is at least 50% of the files in the directory
1217 1089 : if (osMajorityExtension == "" ||
1218 499 : 2 * oMapOtherExtensions[osMajorityExtension] < nFiles)
1219 : {
1220 518 : CSLDestroy(papszFiles);
1221 518 : return nullptr;
1222 : }
1223 :
1224 : // And check that one of those files is a VDV one if it isn't .x10
1225 72 : if (osMajorityExtension != "x10")
1226 : {
1227 71 : GDALOpenInfo oOpenInfo(CPLFormFilenameSafe(poOpenInfo->pszFilename,
1228 : osMajorityFile, nullptr)
1229 : .c_str(),
1230 71 : GA_ReadOnly);
1231 71 : if (OGRVDVDriverIdentify(&oOpenInfo) != TRUE)
1232 : {
1233 64 : CSLDestroy(papszFiles);
1234 64 : return nullptr;
1235 : }
1236 : }
1237 :
1238 : OGRVDVDataSource *poDS = new OGRVDVDataSource(
1239 8 : poOpenInfo->pszFilename, nullptr, /* fp */
1240 8 : poOpenInfo->eAccess == GA_Update, false, /* single file */
1241 8 : false /* new */);
1242 :
1243 : // Instantiate the layers.
1244 49 : for (char **papszIter = papszFiles; papszIter && *papszIter;
1245 : ++papszIter)
1246 : {
1247 41 : if (!EQUAL(CPLGetExtensionSafe(*papszIter).c_str(),
1248 : osMajorityExtension))
1249 16 : continue;
1250 : VSILFILE *fp =
1251 25 : VSIFOpenL(CPLFormFilenameSafe(poOpenInfo->pszFilename,
1252 : *papszIter, nullptr)
1253 : .c_str(),
1254 : "rb");
1255 25 : if (fp == nullptr)
1256 0 : continue;
1257 25 : poDS->m_papoLayers = static_cast<OGRLayer **>(
1258 50 : CPLRealloc(poDS->m_papoLayers,
1259 25 : sizeof(OGRLayer *) * (poDS->m_nLayerCount + 1)));
1260 50 : poDS->m_papoLayers[poDS->m_nLayerCount] =
1261 50 : new OGRVDVLayer(poDS, CPLGetBasenameSafe(*papszIter).c_str(),
1262 25 : fp, true, false, 0);
1263 25 : poDS->m_nLayerCount++;
1264 : }
1265 8 : CSLDestroy(papszFiles);
1266 :
1267 8 : if (poDS->m_nLayerCount == 0)
1268 : {
1269 0 : delete poDS;
1270 0 : poDS = nullptr;
1271 : }
1272 8 : return poDS;
1273 : }
1274 :
1275 73 : VSILFILE *fpL = poOpenInfo->fpL;
1276 73 : poOpenInfo->fpL = nullptr;
1277 73 : const char *pszHeader = (const char *)poOpenInfo->pabyHeader;
1278 73 : if (strstr(pszHeader, "tbl;Node\r\natr;NODE_ID;") != nullptr ||
1279 65 : strstr(pszHeader, "tbl;Node\natr;NODE_ID;") != nullptr ||
1280 65 : strstr(pszHeader, "tbl;Link\r\natr;LINK_ID;") != nullptr ||
1281 65 : strstr(pszHeader, "tbl;Link\natr;LINK_ID;") != nullptr ||
1282 65 : strstr(pszHeader, "tbl;LinkCoordinate\r\natr;LINK_ID;") != nullptr ||
1283 65 : strstr(pszHeader, "tbl;LinkCoordinate\natr;LINK_ID;") != nullptr)
1284 : {
1285 8 : return new OGRIDFDataSource(poOpenInfo->pszFilename, fpL);
1286 : }
1287 : else
1288 : {
1289 65 : return new OGRVDVDataSource(poOpenInfo->pszFilename, fpL,
1290 65 : poOpenInfo->eAccess == GA_Update,
1291 : true, /* single file */
1292 65 : false /* new */);
1293 : }
1294 : }
1295 :
1296 : /************************************************************************/
1297 : /* OGRVDVWriterLayer */
1298 : /************************************************************************/
1299 :
1300 85 : OGRVDVWriterLayer::OGRVDVWriterLayer(OGRVDVDataSource *poDS,
1301 : const char *pszName, VSILFILE *fpL,
1302 : bool bOwnFP, OGRVDV452Table *poVDV452Table,
1303 : const CPLString &osVDV452Lang,
1304 85 : bool bProfileStrict)
1305 85 : : m_poDS(poDS), m_poFeatureDefn(new OGRFeatureDefn(pszName)),
1306 : m_bWritePossible(true), m_fpL(fpL), m_bOwnFP(bOwnFP), m_nFeatureCount(-1),
1307 : m_poVDV452Table(poVDV452Table), m_osVDV452Lang(osVDV452Lang),
1308 : m_bProfileStrict(bProfileStrict), m_iLongitudeVDV452(-1),
1309 170 : m_iLatitudeVDV452(-1)
1310 : {
1311 85 : m_poFeatureDefn->SetGeomType(wkbNone);
1312 85 : m_poFeatureDefn->Reference();
1313 85 : SetDescription(pszName);
1314 85 : }
1315 :
1316 : /************************************************************************/
1317 : /* ~OGRVDVWriterLayer */
1318 : /************************************************************************/
1319 :
1320 170 : OGRVDVWriterLayer::~OGRVDVWriterLayer()
1321 : {
1322 85 : StopAsCurrentLayer();
1323 :
1324 85 : m_poFeatureDefn->Release();
1325 85 : if (m_bOwnFP)
1326 : {
1327 8 : VSIFPrintfL(m_fpL, "eof; %d\n", 1);
1328 8 : VSIFCloseL(m_fpL);
1329 : }
1330 170 : }
1331 :
1332 : /************************************************************************/
1333 : /* ResetReading() */
1334 : /************************************************************************/
1335 :
1336 17 : void OGRVDVWriterLayer::ResetReading()
1337 : {
1338 17 : }
1339 :
1340 : /************************************************************************/
1341 : /* GetNextFeature() */
1342 : /************************************************************************/
1343 :
1344 17 : OGRFeature *OGRVDVWriterLayer::GetNextFeature()
1345 : {
1346 17 : CPLError(CE_Failure, CPLE_NotSupported,
1347 : "GetNextFeature() not supported on write-only layer");
1348 17 : return nullptr;
1349 : }
1350 :
1351 : /************************************************************************/
1352 : /* OGRVDVEscapeString() */
1353 : /************************************************************************/
1354 :
1355 648 : static CPLString OGRVDVEscapeString(const char *pszValue)
1356 : {
1357 648 : CPLString osRet;
1358 4672 : for (; *pszValue != '\0'; ++pszValue)
1359 : {
1360 4024 : if (*pszValue == '"')
1361 6 : osRet += "\"\"";
1362 : else
1363 4018 : osRet += *pszValue;
1364 : }
1365 648 : return osRet;
1366 : }
1367 :
1368 : /************************************************************************/
1369 : /* WriteSchemaIfNeeded() */
1370 : /************************************************************************/
1371 :
1372 215 : bool OGRVDVWriterLayer::WriteSchemaIfNeeded()
1373 : {
1374 215 : if (m_nFeatureCount < 0)
1375 : {
1376 85 : m_nFeatureCount = 0;
1377 :
1378 : bool bOK =
1379 85 : VSIFPrintfL(m_fpL, "tbl; %s\n", m_poFeatureDefn->GetName()) > 0;
1380 85 : bOK &= VSIFPrintfL(m_fpL, "atr;") > 0;
1381 346 : for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++)
1382 : {
1383 261 : if (i > 0)
1384 208 : bOK &= VSIFPrintfL(m_fpL, ";") > 0;
1385 261 : bOK &=
1386 261 : VSIFPrintfL(m_fpL, " %s",
1387 522 : m_poFeatureDefn->GetFieldDefn(i)->GetNameRef()) > 0;
1388 : }
1389 85 : bOK &= VSIFPrintfL(m_fpL, "\n") > 0;
1390 85 : bOK &= VSIFPrintfL(m_fpL, "frm;") > 0;
1391 346 : for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++)
1392 : {
1393 261 : if (i > 0)
1394 208 : bOK &= VSIFPrintfL(m_fpL, ";") > 0;
1395 261 : bOK &= VSIFPrintfL(m_fpL, " ") > 0;
1396 261 : int nWidth = m_poFeatureDefn->GetFieldDefn(i)->GetWidth();
1397 : const OGRFieldType eType =
1398 261 : m_poFeatureDefn->GetFieldDefn(i)->GetType();
1399 261 : switch (eType)
1400 : {
1401 131 : case OFTInteger:
1402 : case OFTInteger64:
1403 131 : if (m_poFeatureDefn->GetFieldDefn(i)->GetSubType() ==
1404 : OFSTBoolean)
1405 : {
1406 6 : bOK &= VSIFPrintfL(m_fpL, "boolean") > 0;
1407 : }
1408 : else
1409 : {
1410 125 : if (nWidth == 0)
1411 : {
1412 24 : if (eType == OFTInteger)
1413 20 : nWidth = 11;
1414 : else
1415 4 : nWidth = 20;
1416 : }
1417 125 : nWidth--; /* VDV 451 is without sign */
1418 125 : bOK &= VSIFPrintfL(m_fpL, "num[%d.0]", nWidth) > 0;
1419 : }
1420 131 : break;
1421 :
1422 130 : default:
1423 130 : if (nWidth == 0)
1424 : {
1425 92 : nWidth = 80;
1426 : }
1427 130 : bOK &= VSIFPrintfL(m_fpL, "char[%d]", nWidth) > 0;
1428 130 : break;
1429 : }
1430 : }
1431 85 : bOK &= VSIFPrintfL(m_fpL, "\n") > 0;
1432 :
1433 85 : if (!bOK)
1434 0 : return false;
1435 : }
1436 :
1437 215 : return true;
1438 : }
1439 :
1440 : /************************************************************************/
1441 : /* ICreateFeature() */
1442 : /************************************************************************/
1443 :
1444 131 : OGRErr OGRVDVWriterLayer::ICreateFeature(OGRFeature *poFeature)
1445 : {
1446 131 : if (!m_bWritePossible)
1447 : {
1448 1 : CPLError(CE_Failure, CPLE_NotSupported,
1449 : "Layer %s is no longer the active layer. "
1450 : "Writing in it is no longer possible",
1451 1 : m_poFeatureDefn->GetName());
1452 1 : return OGRERR_FAILURE;
1453 : }
1454 130 : m_poDS->SetCurrentWriterLayer(this);
1455 :
1456 130 : WriteSchemaIfNeeded();
1457 :
1458 130 : bool bOK = VSIFPrintfL(m_fpL, "rec; ") > 0;
1459 638 : for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++)
1460 : {
1461 508 : if (i > 0)
1462 380 : bOK &= VSIFPrintfL(m_fpL, "; ") > 0;
1463 508 : auto poGeom = poFeature->GetGeometryRef();
1464 508 : if (poFeature->IsFieldSetAndNotNull(i))
1465 : {
1466 : const OGRFieldType eType =
1467 290 : m_poFeatureDefn->GetFieldDefn(i)->GetType();
1468 290 : if (eType == OFTInteger || eType == OFTInteger64)
1469 : {
1470 60 : bOK &= VSIFPrintfL(m_fpL, CPL_FRMT_GIB,
1471 60 : poFeature->GetFieldAsInteger64(i)) > 0;
1472 : }
1473 : else
1474 : {
1475 230 : char *pszRecoded = CPLRecode(poFeature->GetFieldAsString(i),
1476 : CPL_ENC_UTF8, CPL_ENC_ISO8859_1);
1477 230 : bOK &= VSIFPrintfL(m_fpL, "\"%s\"",
1478 460 : OGRVDVEscapeString(pszRecoded).c_str()) > 0;
1479 230 : CPLFree(pszRecoded);
1480 : }
1481 : }
1482 222 : else if (i == m_iLongitudeVDV452 && poGeom != nullptr &&
1483 4 : poGeom->getGeometryType() == wkbPoint)
1484 : {
1485 4 : OGRPoint *poPoint = poGeom->toPoint();
1486 4 : const double dfDeg = poPoint->getX();
1487 4 : const double dfAbsDeg = fabs(dfDeg);
1488 4 : const int nDeg = static_cast<int>(dfAbsDeg);
1489 4 : const int nMin = static_cast<int>((dfAbsDeg - nDeg) * 60);
1490 4 : const double dfSec = (dfAbsDeg - nDeg) * 3600 - nMin * 60;
1491 4 : const int nSec = static_cast<int>(dfSec);
1492 4 : int nMS = static_cast<int>((dfSec - nSec) * 1000 + 0.5);
1493 4 : if (nMS == 1000)
1494 0 : nMS = 999;
1495 4 : if (dfDeg < 0)
1496 4 : bOK &= VSIFPrintfL(m_fpL, "-") > 0;
1497 4 : bOK &= VSIFPrintfL(m_fpL, "%03d%02d%02d%03d", nDeg, nMin, nSec,
1498 4 : nMS) > 0;
1499 : }
1500 218 : else if (i == m_iLatitudeVDV452 && poGeom != nullptr &&
1501 4 : poGeom->getGeometryType() == wkbPoint)
1502 : {
1503 4 : OGRPoint *poPoint = poGeom->toPoint();
1504 4 : const double dfDeg = poPoint->getY();
1505 4 : const double dfAbsDeg = fabs(dfDeg);
1506 4 : const int nDeg = static_cast<int>(dfAbsDeg);
1507 4 : const int nMin = static_cast<int>((dfAbsDeg - nDeg) * 60);
1508 4 : const double dfSec = (dfAbsDeg - nDeg) * 3600 - nMin * 60;
1509 4 : const int nSec = static_cast<int>(dfSec);
1510 4 : int nMS = static_cast<int>((dfSec - nSec) * 1000 + 0.5);
1511 4 : if (nMS == 1000)
1512 0 : nMS = 999;
1513 4 : if (dfDeg < 0)
1514 4 : bOK &= VSIFPrintfL(m_fpL, "-") > 0;
1515 4 : bOK &= VSIFPrintfL(m_fpL, "%02d%02d%02d%03d", nDeg, nMin, nSec,
1516 4 : nMS) > 0;
1517 : }
1518 : else
1519 : {
1520 210 : bOK &= VSIFPrintfL(m_fpL, "NULL") > 0;
1521 : }
1522 : }
1523 130 : bOK &= VSIFPrintfL(m_fpL, "\n") > 0;
1524 :
1525 130 : if (!bOK)
1526 0 : return OGRERR_FAILURE;
1527 :
1528 130 : m_nFeatureCount++;
1529 130 : return OGRERR_NONE;
1530 : }
1531 :
1532 : /************************************************************************/
1533 : /* GetFeatureCount() */
1534 : /************************************************************************/
1535 :
1536 1 : GIntBig OGRVDVWriterLayer::GetFeatureCount(int)
1537 : {
1538 1 : return m_nFeatureCount >= 0 ? m_nFeatureCount : 0;
1539 : }
1540 :
1541 : /************************************************************************/
1542 : /* CreateField() */
1543 : /************************************************************************/
1544 :
1545 263 : OGRErr OGRVDVWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn,
1546 : int /* bApprox */)
1547 : {
1548 263 : if (m_nFeatureCount >= 0)
1549 : {
1550 1 : CPLError(CE_Failure, CPLE_NotSupported,
1551 : "Fields can no longer by added to layer %s",
1552 1 : m_poFeatureDefn->GetName());
1553 1 : return OGRERR_FAILURE;
1554 : }
1555 :
1556 262 : if (m_poVDV452Table != nullptr)
1557 : {
1558 122 : bool bFound = false;
1559 1125 : for (size_t i = 0; i < m_poVDV452Table->aosFields.size(); i++)
1560 : {
1561 1122 : const char *pszFieldName = poFieldDefn->GetNameRef();
1562 1122 : if ((m_osVDV452Lang == "en" &&
1563 646 : EQUAL(m_poVDV452Table->aosFields[i].osEnglishName,
1564 2720 : pszFieldName)) ||
1565 1054 : (m_osVDV452Lang == "de" &&
1566 476 : EQUAL(m_poVDV452Table->aosFields[i].osGermanName,
1567 : pszFieldName)))
1568 : {
1569 119 : bFound = true;
1570 119 : break;
1571 : }
1572 : }
1573 122 : if (!bFound)
1574 : {
1575 3 : CPLError(m_bProfileStrict ? CE_Failure : CE_Warning,
1576 : CPLE_AppDefined,
1577 : "Field %s is not an allowed field for table %s",
1578 3 : poFieldDefn->GetNameRef(), m_poFeatureDefn->GetName());
1579 3 : if (m_bProfileStrict)
1580 1 : return OGRERR_FAILURE;
1581 : }
1582 173 : if (EQUAL(m_poFeatureDefn->GetName(), "STOP") ||
1583 52 : EQUAL(m_poFeatureDefn->GetName(), "REC_ORT"))
1584 : {
1585 238 : if (EQUAL(poFieldDefn->GetNameRef(), "POINT_LONGITUDE") ||
1586 117 : EQUAL(poFieldDefn->GetNameRef(), "ORT_POS_LAENGE"))
1587 : {
1588 7 : m_iLongitudeVDV452 = m_poFeatureDefn->GetFieldCount();
1589 : }
1590 224 : else if (EQUAL(poFieldDefn->GetNameRef(), "POINT_LATITUDE") ||
1591 110 : EQUAL(poFieldDefn->GetNameRef(), "ORT_POS_BREITE"))
1592 : {
1593 7 : m_iLatitudeVDV452 = m_poFeatureDefn->GetFieldCount();
1594 : }
1595 : }
1596 : }
1597 :
1598 261 : m_poFeatureDefn->AddFieldDefn(poFieldDefn);
1599 261 : return OGRERR_NONE;
1600 : }
1601 :
1602 : /************************************************************************/
1603 : /* TestCapability() */
1604 : /************************************************************************/
1605 :
1606 140 : int OGRVDVWriterLayer::TestCapability(const char *pszCap) const
1607 : {
1608 140 : if (EQUAL(pszCap, OLCSequentialWrite))
1609 18 : return m_bWritePossible;
1610 122 : if (EQUAL(pszCap, OLCCreateField))
1611 18 : return m_nFeatureCount < 0;
1612 104 : return FALSE;
1613 : }
1614 :
1615 : /************************************************************************/
1616 : /* StopAsCurrentLayer() */
1617 : /************************************************************************/
1618 :
1619 133 : void OGRVDVWriterLayer::StopAsCurrentLayer()
1620 : {
1621 133 : if (m_bWritePossible)
1622 : {
1623 85 : m_bWritePossible = false;
1624 85 : if (m_fpL != nullptr)
1625 : {
1626 85 : WriteSchemaIfNeeded();
1627 85 : VSIFPrintfL(m_fpL, "end; " CPL_FRMT_GIB "\n", m_nFeatureCount);
1628 : }
1629 : }
1630 133 : }
1631 :
1632 : /************************************************************************/
1633 : /* GetDataset() */
1634 : /************************************************************************/
1635 :
1636 20 : GDALDataset *OGRVDVWriterLayer::GetDataset()
1637 : {
1638 20 : return m_poDS;
1639 : }
1640 :
1641 : /************************************************************************/
1642 : /* OGRVDVWriteHeader() */
1643 : /************************************************************************/
1644 :
1645 52 : static bool OGRVDVWriteHeader(VSILFILE *fpL, CSLConstList papszOptions)
1646 : {
1647 52 : bool bRet = true;
1648 : const bool bStandardHeader =
1649 52 : CPLFetchBool(papszOptions, "STANDARD_HEADER", true);
1650 :
1651 : struct tm tm;
1652 52 : CPLUnixTimeToYMDHMS(time(nullptr), &tm);
1653 52 : const char *pszSrc = CSLFetchNameValueDef(
1654 : papszOptions, "HEADER_SRC", (bStandardHeader) ? "UNKNOWN" : nullptr);
1655 104 : const char *pszSrcDate = CSLFetchNameValueDef(
1656 : papszOptions, "HEADER_SRC_DATE",
1657 52 : (pszSrc) ? CPLSPrintf("%02d.%02d.%04d", tm.tm_mday, tm.tm_mon + 1,
1658 52 : tm.tm_year + 1900)
1659 : : nullptr);
1660 : const char *pszSrcTime =
1661 104 : CSLFetchNameValueDef(papszOptions, "HEADER_SRC_TIME",
1662 52 : (pszSrc) ? CPLSPrintf("%02d.%02d.%02d", tm.tm_hour,
1663 : tm.tm_min, tm.tm_sec)
1664 : : nullptr);
1665 :
1666 52 : if (pszSrc && pszSrcDate && pszSrcTime)
1667 : {
1668 52 : bRet &= VSIFPrintfL(fpL, "mod; DD.MM.YYYY; HH:MM:SS; free\n") > 0;
1669 156 : bRet &= VSIFPrintfL(fpL, "src; \"%s\"; \"%s\"; \"%s\"\n",
1670 52 : OGRVDVEscapeString(pszSrc).c_str(),
1671 104 : OGRVDVEscapeString(pszSrcDate).c_str(),
1672 156 : OGRVDVEscapeString(pszSrcTime).c_str()) > 0;
1673 : }
1674 :
1675 52 : if (bStandardHeader)
1676 : {
1677 : const char *pszChs =
1678 52 : CSLFetchNameValueDef(papszOptions, "HEADER_CHS", "ISO8859-1");
1679 : const char *pszVer =
1680 52 : CSLFetchNameValueDef(papszOptions, "HEADER_VER", "1.4");
1681 : const char *pszIfv =
1682 52 : CSLFetchNameValueDef(papszOptions, "HEADER_IFV", "1.4");
1683 : const char *pszDve =
1684 52 : CSLFetchNameValueDef(papszOptions, "HEADER_DVE", "1.4");
1685 : const char *pszFft =
1686 52 : CSLFetchNameValueDef(papszOptions, "HEADER_FFT", "");
1687 :
1688 52 : bRet &= VSIFPrintfL(fpL, "chs; \"%s\"\n",
1689 104 : OGRVDVEscapeString(pszChs).c_str()) > 0;
1690 52 : bRet &= VSIFPrintfL(fpL, "ver; \"%s\"\n",
1691 104 : OGRVDVEscapeString(pszVer).c_str()) > 0;
1692 52 : bRet &= VSIFPrintfL(fpL, "ifv; \"%s\"\n",
1693 104 : OGRVDVEscapeString(pszIfv).c_str()) > 0;
1694 52 : bRet &= VSIFPrintfL(fpL, "dve; \"%s\"\n",
1695 104 : OGRVDVEscapeString(pszDve).c_str()) > 0;
1696 52 : bRet &= VSIFPrintfL(fpL, "fft; \"%s\"\n",
1697 104 : OGRVDVEscapeString(pszFft).c_str()) > 0;
1698 : }
1699 :
1700 76 : for (CSLConstList papszIter = papszOptions;
1701 76 : papszIter != nullptr && *papszIter != nullptr; papszIter++)
1702 : {
1703 24 : if (STARTS_WITH_CI(*papszIter, "HEADER_") &&
1704 6 : !STARTS_WITH_CI(*papszIter, "HEADER_SRC") &&
1705 2 : (!bStandardHeader || (!EQUAL(*papszIter, "HEADER_CHS") &&
1706 2 : !EQUAL(*papszIter, "HEADER_VER") &&
1707 2 : !EQUAL(*papszIter, "HEADER_IFV") &&
1708 2 : !EQUAL(*papszIter, "HEADER_DVE") &&
1709 2 : !EQUAL(*papszIter, "HEADER_FFT"))))
1710 : {
1711 2 : char *pszKey = nullptr;
1712 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
1713 2 : if (pszKey && strlen(pszKey) > strlen("HEADER_") && pszValue)
1714 : {
1715 2 : bRet &=
1716 2 : VSIFPrintfL(fpL, "%s; \"%s\"\n", pszKey + strlen("HEADER_"),
1717 4 : OGRVDVEscapeString(pszValue).c_str()) > 0;
1718 : }
1719 2 : CPLFree(pszKey);
1720 : }
1721 : }
1722 :
1723 52 : return bRet;
1724 : }
1725 :
1726 : /************************************************************************/
1727 : /* OGRVDVLoadVDV452Tables() */
1728 : /************************************************************************/
1729 :
1730 7 : static bool OGRVDVLoadVDV452Tables(OGRVDV452Tables &oTables)
1731 : {
1732 7 : CPLXMLNode *psRoot = nullptr;
1733 : #if defined(USE_ONLY_EMBEDDED_RESOURCE_FILES)
1734 : const char *pszXMLDescFilename = nullptr;
1735 : #else
1736 7 : const char *pszXMLDescFilename = CPLFindFile("gdal", "vdv452.xml");
1737 : #endif
1738 7 : if (pszXMLDescFilename == nullptr ||
1739 7 : EQUAL(pszXMLDescFilename, "vdv452.xml"))
1740 : {
1741 : #ifdef EMBED_RESOURCE_FILES
1742 : static const bool bOnce [[maybe_unused]] = []()
1743 : {
1744 : CPLDebug("VDV", "Using embedded vdv452.xml");
1745 : return true;
1746 : }();
1747 : psRoot = CPLParseXMLString(VDVGet452XML());
1748 : #else
1749 0 : CPLDebug("VDV", "Cannot find XML file : %s", "vdv452.xml");
1750 0 : return false;
1751 : #endif
1752 : }
1753 :
1754 : #ifdef EMBED_RESOURCE_FILES
1755 : if (!psRoot)
1756 : #endif
1757 : {
1758 7 : psRoot = CPLParseXMLFile(pszXMLDescFilename);
1759 : }
1760 7 : if (psRoot == nullptr)
1761 : {
1762 0 : return false;
1763 : }
1764 7 : CPLXMLNode *psTables = CPLGetXMLNode(psRoot, "=Layers");
1765 7 : if (psTables != nullptr)
1766 : {
1767 245 : for (CPLXMLNode *psTable = psTables->psChild; psTable != nullptr;
1768 238 : psTable = psTable->psNext)
1769 : {
1770 238 : if (psTable->eType != CXT_Element ||
1771 238 : strcmp(psTable->pszValue, "Layer") != 0)
1772 0 : continue;
1773 238 : OGRVDV452Table *poTable = new OGRVDV452Table();
1774 238 : poTable->osEnglishName = CPLGetXMLValue(psTable, "name_en", "");
1775 238 : poTable->osGermanName = CPLGetXMLValue(psTable, "name_de", "");
1776 238 : oTables.aosTables.push_back(poTable);
1777 238 : oTables.oMapEnglish[poTable->osEnglishName] = poTable;
1778 238 : oTables.oMapGerman[poTable->osGermanName] = poTable;
1779 2569 : for (CPLXMLNode *psField = psTable->psChild; psField != nullptr;
1780 2331 : psField = psField->psNext)
1781 : {
1782 2331 : if (psField->eType != CXT_Element ||
1783 1617 : strcmp(psField->pszValue, "Field") != 0)
1784 714 : continue;
1785 3234 : OGRVDV452Field oField;
1786 1617 : oField.osEnglishName = CPLGetXMLValue(psField, "name_en", "");
1787 1617 : oField.osGermanName = CPLGetXMLValue(psField, "name_de", "");
1788 1617 : oField.osType = CPLGetXMLValue(psField, "type", "");
1789 1617 : oField.nWidth = atoi(CPLGetXMLValue(psField, "width", "0"));
1790 1617 : poTable->aosFields.push_back(std::move(oField));
1791 : }
1792 : }
1793 : }
1794 :
1795 7 : CPLDestroyXMLNode(psRoot);
1796 7 : return true;
1797 : }
1798 :
1799 : /************************************************************************/
1800 : /* ICreateLayer() */
1801 : /************************************************************************/
1802 :
1803 : OGRLayer *
1804 87 : OGRVDVDataSource::ICreateLayer(const char *pszLayerName,
1805 : const OGRGeomFieldDefn *poGeomFieldDefn,
1806 : CSLConstList papszOptions)
1807 : {
1808 87 : if (!m_bUpdate)
1809 0 : return nullptr;
1810 :
1811 : const char *pszProfile =
1812 87 : CSLFetchNameValueDef(papszOptions, "PROFILE", "GENERIC");
1813 87 : if (STARTS_WITH_CI(pszProfile, "VDV-452") && !m_bVDV452Loaded)
1814 : {
1815 7 : m_bVDV452Loaded = true;
1816 7 : OGRVDVLoadVDV452Tables(m_oVDV452Tables);
1817 : }
1818 : const bool bProfileStrict =
1819 87 : CPLFetchBool(papszOptions, "PROFILE_STRICT", false);
1820 : const bool bCreateAllFields =
1821 87 : CPLFetchBool(papszOptions, "CREATE_ALL_FIELDS", true);
1822 :
1823 174 : CPLString osUpperLayerName(pszLayerName);
1824 87 : osUpperLayerName.toupper();
1825 :
1826 87 : OGRVDV452Table *poVDV452Table = nullptr;
1827 174 : CPLString osVDV452Lang;
1828 87 : bool bOKTable = true;
1829 87 : if (EQUAL(pszProfile, "VDV-452"))
1830 : {
1831 4 : if (m_oVDV452Tables.oMapEnglish.find(osUpperLayerName) !=
1832 8 : m_oVDV452Tables.oMapEnglish.end())
1833 : {
1834 2 : poVDV452Table = m_oVDV452Tables.oMapEnglish[osUpperLayerName];
1835 2 : osVDV452Lang = "en";
1836 : }
1837 2 : else if (m_oVDV452Tables.oMapGerman.find(osUpperLayerName) !=
1838 4 : m_oVDV452Tables.oMapGerman.end())
1839 : {
1840 1 : poVDV452Table = m_oVDV452Tables.oMapGerman[osUpperLayerName];
1841 1 : osVDV452Lang = "de";
1842 : }
1843 : else
1844 : {
1845 1 : bOKTable = false;
1846 : }
1847 : }
1848 83 : else if (EQUAL(pszProfile, "VDV-452-ENGLISH"))
1849 : {
1850 3 : if (m_oVDV452Tables.oMapEnglish.find(osUpperLayerName) !=
1851 6 : m_oVDV452Tables.oMapEnglish.end())
1852 : {
1853 2 : poVDV452Table = m_oVDV452Tables.oMapEnglish[osUpperLayerName];
1854 2 : osVDV452Lang = "en";
1855 : }
1856 : else
1857 : {
1858 1 : bOKTable = false;
1859 : }
1860 : }
1861 80 : else if (EQUAL(pszProfile, "VDV-452-GERMAN"))
1862 : {
1863 3 : if (m_oVDV452Tables.oMapGerman.find(osUpperLayerName) !=
1864 6 : m_oVDV452Tables.oMapGerman.end())
1865 : {
1866 2 : poVDV452Table = m_oVDV452Tables.oMapGerman[osUpperLayerName];
1867 2 : osVDV452Lang = "de";
1868 : }
1869 : else
1870 : {
1871 1 : bOKTable = false;
1872 : }
1873 : }
1874 87 : if (!bOKTable)
1875 : {
1876 3 : CPLError(bProfileStrict ? CE_Failure : CE_Warning, CPLE_AppDefined,
1877 : "%s is not a VDV-452 table", pszLayerName);
1878 3 : if (bProfileStrict)
1879 1 : return nullptr;
1880 : }
1881 :
1882 86 : VSILFILE *fpL = nullptr;
1883 86 : if (m_bSingleFile)
1884 : {
1885 77 : fpL = m_fpL;
1886 77 : if (!m_bNew && m_nLayerCount == 0)
1887 : {
1888 : // Find last non-empty line in the file
1889 4 : VSIFSeekL(fpL, 0, SEEK_END);
1890 4 : vsi_l_offset nFileSize = VSIFTellL(fpL);
1891 4 : vsi_l_offset nOffset = nFileSize;
1892 4 : bool bTerminatingEOL = true;
1893 35 : while (nOffset > 0)
1894 : {
1895 35 : VSIFSeekL(fpL, nOffset - 1, SEEK_SET);
1896 35 : char ch = '\0';
1897 35 : VSIFReadL(&ch, 1, 1, fpL);
1898 35 : if (bTerminatingEOL)
1899 : {
1900 7 : if (!(ch == '\r' || ch == '\n'))
1901 : {
1902 4 : bTerminatingEOL = false;
1903 : }
1904 : }
1905 : else
1906 : {
1907 28 : if (ch == '\r' || ch == '\n')
1908 : break;
1909 : }
1910 31 : nOffset--;
1911 : }
1912 :
1913 : // If it is "eof;..." then overwrite it with new content
1914 4 : const char *pszLine = CPLReadLineL(fpL);
1915 4 : if (pszLine != nullptr && STARTS_WITH(pszLine, "eof;"))
1916 : {
1917 3 : VSIFSeekL(fpL, nOffset, SEEK_SET);
1918 3 : VSIFTruncateL(fpL, VSIFTellL(fpL));
1919 : }
1920 1 : else if (nFileSize > 0)
1921 : {
1922 : // Otherwise make sure the file ends with an eol character
1923 1 : VSIFSeekL(fpL, nFileSize - 1, SEEK_SET);
1924 1 : char ch = '\0';
1925 1 : VSIFReadL(&ch, 1, 1, fpL);
1926 1 : VSIFSeekL(fpL, nFileSize, SEEK_SET);
1927 1 : if (!(ch == '\r' || ch == '\n'))
1928 : {
1929 0 : ch = '\n';
1930 0 : VSIFWriteL(&ch, 1, 1, fpL);
1931 : }
1932 : }
1933 : }
1934 : }
1935 : else
1936 : {
1937 9 : if (CPLLaunderForFilenameSafe(pszLayerName, nullptr) != pszLayerName)
1938 : {
1939 0 : CPLError(CE_Failure, CPLE_AppDefined,
1940 : "Illegal characters in '%s' to form a valid filename",
1941 : pszLayerName);
1942 1 : return nullptr;
1943 : }
1944 :
1945 : CPLString osExtension =
1946 9 : CSLFetchNameValueDef(papszOptions, "EXTENSION", "x10");
1947 : const CPLString osFilename =
1948 9 : CPLFormFilenameSafe(m_osFilename, pszLayerName, osExtension);
1949 9 : fpL = VSIFOpenL(osFilename, "wb");
1950 9 : if (fpL == nullptr)
1951 : {
1952 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
1953 : osFilename.c_str());
1954 1 : return nullptr;
1955 : }
1956 : }
1957 :
1958 85 : GetLayerCount();
1959 :
1960 85 : if (m_nLayerCount == 0 || !m_bSingleFile)
1961 : {
1962 52 : if (!OGRVDVWriteHeader(fpL, papszOptions))
1963 : {
1964 0 : if (!m_bSingleFile)
1965 0 : VSIFCloseL(fpL);
1966 0 : return nullptr;
1967 : }
1968 : }
1969 :
1970 85 : m_bMustWriteEof = true;
1971 :
1972 : OGRVDVWriterLayer *poLayer =
1973 85 : new OGRVDVWriterLayer(this, pszLayerName, fpL, !m_bSingleFile,
1974 85 : poVDV452Table, osVDV452Lang, bProfileStrict);
1975 85 : m_papoLayers = static_cast<OGRLayer **>(
1976 85 : CPLRealloc(m_papoLayers, sizeof(OGRLayer *) * (m_nLayerCount + 1)));
1977 85 : m_papoLayers[m_nLayerCount] = poLayer;
1978 85 : m_nLayerCount++;
1979 :
1980 85 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
1981 85 : if (eGType == wkbPoint && poVDV452Table != nullptr &&
1982 4 : (EQUAL(pszLayerName, "STOP") || EQUAL(pszLayerName, "REC_ORT")))
1983 : {
1984 4 : poLayer->GetLayerDefn()->SetGeomType(wkbPoint);
1985 : }
1986 :
1987 85 : if (bCreateAllFields && poVDV452Table != nullptr)
1988 : {
1989 126 : for (size_t i = 0; i < poVDV452Table->aosFields.size(); i++)
1990 : {
1991 : const char *pszFieldName =
1992 119 : (osVDV452Lang == "en")
1993 119 : ? poVDV452Table->aosFields[i].osEnglishName.c_str()
1994 51 : : poVDV452Table->aosFields[i].osGermanName.c_str();
1995 119 : OGRFieldType eType = OFTString;
1996 119 : int nWidth = poVDV452Table->aosFields[i].nWidth;
1997 147 : if (poVDV452Table->aosFields[i].osType == "num" ||
1998 28 : poVDV452Table->aosFields[i].osType == "boolean")
1999 91 : eType = OFTInteger;
2000 119 : if (poVDV452Table->aosFields[i].osType == "num")
2001 : {
2002 : /* VDV 451 is without sign */
2003 91 : nWidth++;
2004 91 : if (nWidth >= 10)
2005 42 : eType = OFTInteger64;
2006 : }
2007 238 : OGRFieldDefn oField(pszFieldName, eType);
2008 119 : if (poVDV452Table->aosFields[i].osType == "boolean")
2009 0 : oField.SetSubType(OFSTBoolean);
2010 119 : oField.SetWidth(nWidth);
2011 119 : poLayer->CreateField(&oField);
2012 : }
2013 : }
2014 :
2015 85 : return poLayer;
2016 : }
2017 :
2018 : /************************************************************************/
2019 : /* SetCurrentWriterLayer() */
2020 : /************************************************************************/
2021 :
2022 130 : void OGRVDVDataSource::SetCurrentWriterLayer(OGRVDVWriterLayer *poLayer)
2023 : {
2024 130 : if (!m_bSingleFile)
2025 14 : return;
2026 116 : if (m_poCurrentWriterLayer != nullptr && m_poCurrentWriterLayer != poLayer)
2027 : {
2028 21 : m_poCurrentWriterLayer->StopAsCurrentLayer();
2029 : }
2030 116 : m_poCurrentWriterLayer = poLayer;
2031 : }
2032 :
2033 : /************************************************************************/
2034 : /* TestCapability() */
2035 : /************************************************************************/
2036 :
2037 87 : int OGRVDVDataSource::TestCapability(const char *pszCap) const
2038 :
2039 : {
2040 87 : if (EQUAL(pszCap, ODsCCreateLayer))
2041 35 : return m_bUpdate;
2042 52 : else if (EQUAL(pszCap, ODsCZGeometries))
2043 16 : return true;
2044 :
2045 36 : return false;
2046 : }
2047 :
2048 : /************************************************************************/
2049 : /* Create() */
2050 : /************************************************************************/
2051 :
2052 50 : GDALDataset *OGRVDVDataSource::Create(const char *pszName, int /*nXSize*/,
2053 : int /*nYSize*/, int /*nBands*/,
2054 : GDALDataType /*eType*/,
2055 : char **papszOptions)
2056 :
2057 : {
2058 : /* -------------------------------------------------------------------- */
2059 : /* First, ensure there isn't any such file yet. */
2060 : /* -------------------------------------------------------------------- */
2061 : VSIStatBufL sStatBuf;
2062 50 : if (VSIStatL(pszName, &sStatBuf) == 0)
2063 : {
2064 1 : CPLError(CE_Failure, CPLE_AppDefined,
2065 : "It seems a file system object called '%s' already exists.",
2066 : pszName);
2067 :
2068 1 : return nullptr;
2069 : }
2070 :
2071 49 : const bool bSingleFile = CPLFetchBool(papszOptions, "SINGLE_FILE", true);
2072 49 : if (!bSingleFile)
2073 : {
2074 3 : if (VSIMkdir(pszName, 0755) != 0)
2075 : {
2076 1 : CPLError(CE_Failure, CPLE_AppDefined,
2077 : "Failed to create directory %s:\n%s", pszName,
2078 1 : VSIStrerror(errno));
2079 1 : return nullptr;
2080 : }
2081 : }
2082 :
2083 48 : VSILFILE *fpL = nullptr;
2084 48 : if (bSingleFile)
2085 : {
2086 46 : fpL = VSIFOpenL(pszName, "wb");
2087 46 : if (fpL == nullptr)
2088 : {
2089 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszName);
2090 2 : return nullptr;
2091 : }
2092 : }
2093 : OGRVDVDataSource *poDS =
2094 46 : new OGRVDVDataSource(pszName, fpL, true, bSingleFile, true /* new */);
2095 46 : return poDS;
2096 : }
2097 :
2098 : /************************************************************************/
2099 : /* RegisterOGRVDV() */
2100 : /************************************************************************/
2101 :
2102 2024 : void RegisterOGRVDV()
2103 :
2104 : {
2105 2024 : if (GDALGetDriverByName("VDV") != nullptr)
2106 283 : return;
2107 :
2108 1741 : GDALDriver *poDriver = new GDALDriver();
2109 :
2110 1741 : poDriver->SetDescription("VDV");
2111 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
2112 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
2113 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
2114 1741 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
2115 1741 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
2116 1741 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
2117 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
2118 1741 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
2119 1741 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
2120 1741 : "WidthPrecision");
2121 1741 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
2122 1741 : "Name Type WidthPrecision");
2123 1741 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
2124 :
2125 1741 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
2126 1741 : "VDV-451/VDV-452/INTREST Data Format");
2127 1741 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/vdv.html");
2128 1741 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "txt x10");
2129 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
2130 1741 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
2131 1741 : "Integer Integer64 String");
2132 :
2133 1741 : poDriver->SetMetadataItem(
2134 : GDAL_DMD_CREATIONOPTIONLIST,
2135 : "<CreationOptionList>"
2136 : " <Option name='SINGLE_FILE' type='boolean' description='Whether "
2137 : "several layers "
2138 : "should be put in the same file. If no, the name is assumed to be a "
2139 : "directory name' default='YES'/>"
2140 1741 : "</CreationOptionList>");
2141 :
2142 1741 : poDriver->SetMetadataItem(
2143 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
2144 : "<LayerCreationOptionList>"
2145 : " <Option name='EXTENSION' type='string' description='Layer file "
2146 : "extension. Only used for SINGLE_FILE=NO' default='x10'/>"
2147 : " <Option name='PROFILE' type='string-select' description='Profile' "
2148 : "default='GENERIC'>"
2149 : " <Value>GENERIC</Value>"
2150 : " <Value>VDV-452</Value>"
2151 : " <Value>VDV-452-ENGLISH</Value>"
2152 : " <Value>VDV-452-GERMAN</Value>"
2153 : " </Option>"
2154 : " <Option name='PROFILE_STRICT' type='boolean' description='Whether "
2155 : "checks of profile should be strict' default='NO'/>"
2156 : " <Option name='CREATE_ALL_FIELDS' type='boolean' description="
2157 : "'Whether all fields of predefined profiles should be created at layer "
2158 : "creation' default='YES'/>"
2159 : " <Option name='STANDARD_HEADER' type='boolean' description='Whether "
2160 : "to write standard header fields' default='YES'/>"
2161 : " <Option name='HEADER_SRC' type='string' description='Value of the "
2162 : "src header field' default='UNKNOWN'/>"
2163 : " <Option name='HEADER_SRC_DATE' type='string' description='Value of "
2164 : "the date of the src header field as DD.MM.YYYY'/>"
2165 : " <Option name='HEADER_SRC_TIME' type='string' description='Value of "
2166 : "the time of the src header field as HH.MM.SS'/>"
2167 : " <Option name='HEADER_CHS' type='string' description='Value of the "
2168 : "chs header field' default='ISO8859-1'/>"
2169 : " <Option name='HEADER_VER' type='string' description='Value of the "
2170 : "ver header field' default='1.4'/>"
2171 : " <Option name='HEADER_IFV' type='string' description='Value of the "
2172 : "ifv header field' default='1.4'/>"
2173 : " <Option name='HEADER_DVE' type='string' description='Value of the "
2174 : "dve header field' default='1.4'/>"
2175 : " <Option name='HEADER_FFT' type='string' description='Value of the "
2176 : "fft header field' default=''/>"
2177 : " <Option name='HEADER_*' type='string' description='Value of another "
2178 : "header field'/>"
2179 1741 : "</LayerCreationOptionList>");
2180 1741 : poDriver->pfnIdentify = OGRVDVDriverIdentify;
2181 1741 : poDriver->pfnOpen = OGRVDVDataSource::Open;
2182 1741 : poDriver->pfnCreate = OGRVDVDataSource::Create;
2183 :
2184 1741 : GetGDALDriverManager()->RegisterDriver(poDriver);
2185 : }
|