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