Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GPX Translator
4 : * Purpose: Implements OGRGPXDataSource class
5 : * Author: Even Rouault, even dot rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007-2011, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "ogr_gpx.h"
15 :
16 : #include <algorithm>
17 : #include <cstdarg>
18 : #include <cstdio>
19 : #include <cstring>
20 :
21 : #include "cpl_conv.h"
22 : #include "cpl_csv.h"
23 : #include "cpl_error.h"
24 : #include "cpl_string.h"
25 : #include "cpl_vsi.h"
26 : #ifdef HAVE_EXPAT
27 : #include "expat.h"
28 : #endif
29 : #include "ogr_core.h"
30 : #include "ogr_expat.h"
31 : #include "ogr_spatialref.h"
32 : #include "ogrsf_frmts.h"
33 : #include "ogr_p.h"
34 :
35 : constexpr int SPACE_FOR_METADATA_BOUNDS = 160;
36 :
37 : /************************************************************************/
38 : /* ~OGRGPXDataSource() */
39 : /************************************************************************/
40 :
41 108 : OGRGPXDataSource::~OGRGPXDataSource()
42 :
43 : {
44 54 : if (m_fpOutput != nullptr)
45 : {
46 24 : if (m_nLastRteId != -1)
47 1 : PrintLine("</rte>");
48 23 : else if (m_nLastTrkId != -1)
49 : {
50 2 : PrintLine(" </trkseg>");
51 2 : PrintLine("</trk>");
52 : }
53 24 : PrintLine("</gpx>");
54 24 : if (m_bIsBackSeekable)
55 : {
56 : /* Write the <bounds> element in the reserved space */
57 24 : if (m_dfMinLon <= m_dfMaxLon)
58 : {
59 : char szBounds[SPACE_FOR_METADATA_BOUNDS + 1];
60 : int nRet =
61 11 : CPLsnprintf(szBounds, SPACE_FOR_METADATA_BOUNDS,
62 : "<bounds minlat=\"%.15f\" minlon=\"%.15f\""
63 : " maxlat=\"%.15f\" maxlon=\"%.15f\"/>",
64 : m_dfMinLat, m_dfMinLon, m_dfMaxLat, m_dfMaxLon);
65 11 : if (nRet < SPACE_FOR_METADATA_BOUNDS)
66 : {
67 11 : m_fpOutput->Seek(m_nOffsetBounds, SEEK_SET);
68 11 : m_fpOutput->Write(szBounds, 1, strlen(szBounds));
69 : }
70 : }
71 : }
72 : }
73 108 : }
74 :
75 : /************************************************************************/
76 : /* TestCapability() */
77 : /************************************************************************/
78 :
79 32 : int OGRGPXDataSource::TestCapability(const char *pszCap)
80 :
81 : {
82 32 : if (EQUAL(pszCap, ODsCCreateLayer))
83 23 : return TRUE;
84 9 : else if (EQUAL(pszCap, ODsCDeleteLayer))
85 6 : return FALSE;
86 3 : else if (EQUAL(pszCap, ODsCZGeometries))
87 0 : return TRUE;
88 :
89 3 : return FALSE;
90 : }
91 :
92 : /************************************************************************/
93 : /* GetLayer() */
94 : /************************************************************************/
95 :
96 149 : OGRLayer *OGRGPXDataSource::GetLayer(int iLayer)
97 :
98 : {
99 149 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
100 0 : return nullptr;
101 :
102 149 : return m_apoLayers[iLayer].get();
103 : }
104 :
105 : /************************************************************************/
106 : /* ICreateLayer() */
107 : /************************************************************************/
108 :
109 : OGRLayer *
110 30 : OGRGPXDataSource::ICreateLayer(const char *pszLayerName,
111 : const OGRGeomFieldDefn *poGeomFieldDefn,
112 : CSLConstList papszOptions)
113 : {
114 : GPXGeometryType gpxGeomType;
115 30 : const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
116 30 : if (eType == wkbPoint || eType == wkbPoint25D)
117 : {
118 10 : if (EQUAL(pszLayerName, "track_points"))
119 2 : gpxGeomType = GPX_TRACK_POINT;
120 8 : else if (EQUAL(pszLayerName, "route_points"))
121 2 : gpxGeomType = GPX_ROUTE_POINT;
122 : else
123 6 : gpxGeomType = GPX_WPT;
124 : }
125 20 : else if (eType == wkbLineString || eType == wkbLineString25D)
126 : {
127 : const char *pszForceGPXTrack =
128 5 : CSLFetchNameValue(papszOptions, "FORCE_GPX_TRACK");
129 5 : if (pszForceGPXTrack && CPLTestBool(pszForceGPXTrack))
130 0 : gpxGeomType = GPX_TRACK;
131 : else
132 5 : gpxGeomType = GPX_ROUTE;
133 : }
134 15 : else if (eType == wkbMultiLineString || eType == wkbMultiLineString25D)
135 : {
136 : const char *pszForceGPXRoute =
137 5 : CSLFetchNameValue(papszOptions, "FORCE_GPX_ROUTE");
138 5 : if (pszForceGPXRoute && CPLTestBool(pszForceGPXRoute))
139 0 : gpxGeomType = GPX_ROUTE;
140 : else
141 5 : gpxGeomType = GPX_TRACK;
142 : }
143 10 : else if (eType == wkbUnknown)
144 : {
145 1 : CPLError(CE_Failure, CPLE_NotSupported,
146 : "Cannot create GPX layer %s with unknown geometry type",
147 : pszLayerName);
148 1 : return nullptr;
149 : }
150 : else
151 : {
152 9 : CPLError(CE_Failure, CPLE_NotSupported,
153 : "Geometry type of `%s' not supported in GPX.\n",
154 : OGRGeometryTypeToName(eType));
155 9 : return nullptr;
156 : }
157 20 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
158 20 : GetDescription(), pszLayerName, gpxGeomType, this, true, nullptr));
159 :
160 20 : return m_apoLayers.back().get();
161 : }
162 :
163 : #ifdef HAVE_EXPAT
164 :
165 : /************************************************************************/
166 : /* startElementValidateCbk() */
167 : /************************************************************************/
168 :
169 1255 : void OGRGPXDataSource::startElementValidateCbk(const char *pszNameIn,
170 : const char **ppszAttr)
171 : {
172 1255 : if (m_validity == GPX_VALIDITY_UNKNOWN)
173 : {
174 29 : if (strcmp(pszNameIn, "gpx") == 0)
175 : {
176 29 : m_validity = GPX_VALIDITY_VALID;
177 168 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
178 : {
179 139 : if (strcmp(ppszAttr[i], "version") == 0)
180 : {
181 29 : m_osVersion = ppszAttr[i + 1];
182 : }
183 110 : else if (strcmp(ppszAttr[i], "xmlns:ogr") == 0)
184 : {
185 1 : m_bUseExtensions = true;
186 : }
187 : }
188 : }
189 : else
190 : {
191 0 : m_validity = GPX_VALIDITY_INVALID;
192 : }
193 : }
194 1226 : else if (m_validity == GPX_VALIDITY_VALID)
195 : {
196 1226 : if (m_nDepth == 1 && strcmp(pszNameIn, "metadata") == 0)
197 : {
198 29 : m_bInMetadata = true;
199 : }
200 1197 : else if (m_nDepth == 2 && m_bInMetadata)
201 : {
202 159 : if (strcmp(pszNameIn, "name") == 0)
203 : {
204 17 : m_osMetadataKey = "NAME";
205 : }
206 142 : else if (strcmp(pszNameIn, "desc") == 0)
207 : {
208 17 : m_osMetadataKey = "DESCRIPTION";
209 : }
210 125 : else if (strcmp(pszNameIn, "time") == 0)
211 : {
212 19 : m_osMetadataKey = "TIME";
213 : }
214 106 : else if (strcmp(pszNameIn, "author") == 0)
215 : {
216 14 : m_bInMetadataAuthor = true;
217 : }
218 92 : else if (strcmp(pszNameIn, "keywords") == 0)
219 : {
220 17 : m_osMetadataKey = "KEYWORDS";
221 : }
222 75 : else if (strcmp(pszNameIn, "copyright") == 0)
223 : {
224 14 : std::string osAuthor;
225 28 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
226 : {
227 14 : if (strcmp(ppszAttr[i], "author") == 0)
228 : {
229 14 : osAuthor = ppszAttr[i + 1];
230 : }
231 : }
232 14 : if (!osAuthor.empty())
233 : {
234 14 : SetMetadataItem("COPYRIGHT_AUTHOR", osAuthor.c_str());
235 : }
236 14 : m_bInMetadataCopyright = true;
237 : }
238 61 : else if (strcmp(pszNameIn, "link") == 0)
239 : {
240 34 : ++m_nMetadataLinkCounter;
241 34 : std::string osHref;
242 68 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
243 : {
244 34 : if (strcmp(ppszAttr[i], "href") == 0)
245 : {
246 34 : osHref = ppszAttr[i + 1];
247 : }
248 : }
249 34 : if (!osHref.empty())
250 : {
251 34 : SetMetadataItem(
252 : CPLSPrintf("LINK_%d_HREF", m_nMetadataLinkCounter),
253 34 : osHref.c_str());
254 : }
255 34 : m_bInMetadataLink = true;
256 159 : }
257 : }
258 1038 : else if (m_nDepth == 3 && m_bInMetadataAuthor)
259 : {
260 42 : if (strcmp(pszNameIn, "name") == 0)
261 : {
262 14 : m_osMetadataKey = "AUTHOR_NAME";
263 : }
264 28 : else if (strcmp(pszNameIn, "email") == 0)
265 : {
266 28 : std::string osId, osDomain;
267 42 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
268 : {
269 28 : if (strcmp(ppszAttr[i], "id") == 0)
270 : {
271 14 : osId = ppszAttr[i + 1];
272 : }
273 14 : else if (strcmp(ppszAttr[i], "domain") == 0)
274 : {
275 14 : osDomain = ppszAttr[i + 1];
276 : }
277 : }
278 14 : if (!osId.empty() && !osDomain.empty())
279 : {
280 14 : SetMetadataItem("AUTHOR_EMAIL",
281 14 : osId.append("@").append(osDomain).c_str());
282 : }
283 : }
284 14 : else if (strcmp(pszNameIn, "link") == 0)
285 : {
286 14 : std::string osHref;
287 28 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
288 : {
289 14 : if (strcmp(ppszAttr[i], "href") == 0)
290 : {
291 14 : osHref = ppszAttr[i + 1];
292 : }
293 : }
294 14 : if (!osHref.empty())
295 : {
296 14 : SetMetadataItem("AUTHOR_LINK_HREF", osHref.c_str());
297 : }
298 14 : m_bInMetadataAuthorLink = true;
299 42 : }
300 : }
301 996 : else if (m_nDepth == 3 && m_bInMetadataCopyright)
302 : {
303 28 : if (strcmp(pszNameIn, "year") == 0)
304 : {
305 14 : m_osMetadataKey = "COPYRIGHT_YEAR";
306 : }
307 14 : else if (strcmp(pszNameIn, "license") == 0)
308 : {
309 14 : m_osMetadataKey = "COPYRIGHT_LICENSE";
310 : }
311 : }
312 968 : else if (m_nDepth == 3 && m_bInMetadataLink)
313 : {
314 314 : if (strcmp(pszNameIn, "text") == 0)
315 : {
316 : m_osMetadataKey =
317 82 : CPLSPrintf("LINK_%d_TEXT", m_nMetadataLinkCounter);
318 : }
319 232 : else if (strcmp(pszNameIn, "type") == 0)
320 : {
321 : m_osMetadataKey =
322 82 : CPLSPrintf("LINK_%d_TYPE", m_nMetadataLinkCounter);
323 : }
324 : }
325 654 : else if (m_nDepth == 4 && m_bInMetadataAuthorLink)
326 : {
327 28 : if (strcmp(pszNameIn, "text") == 0)
328 : {
329 14 : m_osMetadataKey = "AUTHOR_LINK_TEXT";
330 : }
331 14 : else if (strcmp(pszNameIn, "type") == 0)
332 : {
333 14 : m_osMetadataKey = "AUTHOR_LINK_TYPE";
334 : }
335 : }
336 626 : else if (m_nDepth == 2 && strcmp(pszNameIn, "extensions") == 0)
337 : {
338 16 : m_bUseExtensions = true;
339 : }
340 : }
341 1255 : m_nDepth++;
342 1255 : }
343 :
344 : /************************************************************************/
345 : /* endElementValidateCbk() */
346 : /************************************************************************/
347 :
348 1255 : void OGRGPXDataSource::endElementValidateCbk(const char * /*pszName */)
349 : {
350 1255 : m_nDepth--;
351 1255 : if (m_nDepth == 4 && m_bInMetadataAuthorLink)
352 : {
353 28 : if (!m_osMetadataKey.empty())
354 : {
355 28 : SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
356 : }
357 28 : m_osMetadataKey.clear();
358 28 : m_osMetadataValue.clear();
359 : }
360 1227 : else if (m_nDepth == 3 && (m_bInMetadataAuthor || m_bInMetadataCopyright ||
361 340 : m_bInMetadataLink))
362 : {
363 384 : if (!m_osMetadataKey.empty())
364 : {
365 206 : SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
366 : }
367 384 : m_osMetadataKey.clear();
368 384 : m_osMetadataValue.clear();
369 384 : m_bInMetadataAuthorLink = false;
370 : }
371 843 : else if (m_nDepth == 2 && m_bInMetadata)
372 : {
373 159 : if (!m_osMetadataKey.empty())
374 : {
375 70 : SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
376 : }
377 159 : m_osMetadataKey.clear();
378 159 : m_osMetadataValue.clear();
379 159 : m_bInMetadataAuthor = false;
380 159 : m_bInMetadataCopyright = false;
381 : }
382 684 : else if (m_nDepth == 1 && m_bInMetadata)
383 : {
384 29 : m_bInMetadata = false;
385 : }
386 1255 : }
387 :
388 : /************************************************************************/
389 : /* dataHandlerValidateCbk() */
390 : /************************************************************************/
391 :
392 3301 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
393 : {
394 3301 : if (!m_osMetadataKey.empty())
395 : {
396 304 : m_osMetadataValue.append(data, nLen);
397 : }
398 :
399 3301 : m_nDataHandlerCounter++;
400 3301 : if (m_nDataHandlerCounter >= PARSER_BUF_SIZE)
401 : {
402 0 : CPLError(CE_Failure, CPLE_AppDefined,
403 : "File probably corrupted (million laugh pattern)");
404 0 : XML_StopParser(m_oCurrentParser, XML_FALSE);
405 : }
406 3301 : }
407 :
408 1255 : static void XMLCALL startElementValidateCbk(void *pUserData,
409 : const char *pszName,
410 : const char **ppszAttr)
411 : {
412 1255 : OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
413 1255 : poDS->startElementValidateCbk(pszName, ppszAttr);
414 1255 : }
415 :
416 1255 : static void XMLCALL endElementValidateCbk(void *pUserData, const char *pszName)
417 : {
418 1255 : OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
419 1255 : poDS->endElementValidateCbk(pszName);
420 1255 : }
421 :
422 3301 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data,
423 : int nLen)
424 : {
425 3301 : OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
426 3301 : poDS->dataHandlerValidateCbk(data, nLen);
427 3301 : }
428 : #endif
429 :
430 : /************************************************************************/
431 : /* Open() */
432 : /************************************************************************/
433 :
434 29 : int OGRGPXDataSource::Open(GDALOpenInfo *poOpenInfo)
435 :
436 : {
437 29 : const char *pszFilename = poOpenInfo->pszFilename;
438 29 : if (poOpenInfo->eAccess == GA_Update)
439 : {
440 0 : CPLError(CE_Failure, CPLE_NotSupported,
441 : "OGR/GPX driver does not support opening a file in "
442 : "update mode");
443 0 : return FALSE;
444 : }
445 : #ifdef HAVE_EXPAT
446 29 : SetDescription(pszFilename);
447 :
448 : /* -------------------------------------------------------------------- */
449 : /* Try to open the file. */
450 : /* -------------------------------------------------------------------- */
451 29 : VSILFILE *fp = VSIFOpenL(pszFilename, "r");
452 29 : if (fp == nullptr)
453 0 : return FALSE;
454 :
455 29 : m_validity = GPX_VALIDITY_UNKNOWN;
456 :
457 29 : XML_Parser oParser = OGRCreateExpatXMLParser();
458 29 : m_oCurrentParser = oParser;
459 29 : XML_SetUserData(oParser, this);
460 29 : XML_SetElementHandler(oParser, ::startElementValidateCbk,
461 : ::endElementValidateCbk);
462 29 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
463 :
464 29 : std::vector<char> aBuf(PARSER_BUF_SIZE);
465 29 : int nDone = 0;
466 29 : unsigned int nLen = 0;
467 29 : int nCount = 0;
468 :
469 : /* Begin to parse the file and look for the <gpx> element */
470 : /* It *MUST* be the first element of an XML file */
471 : /* So once we have read the first element, we know if we can */
472 : /* handle the file or not with that driver */
473 29 : uint64_t nTotalBytesRead = 0;
474 0 : do
475 : {
476 29 : m_nDataHandlerCounter = 0;
477 29 : nLen = static_cast<unsigned int>(
478 29 : VSIFReadL(aBuf.data(), 1, aBuf.size(), fp));
479 29 : nTotalBytesRead += nLen;
480 29 : nDone = (nLen < aBuf.size());
481 29 : if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
482 : {
483 0 : if (nLen <= PARSER_BUF_SIZE - 1)
484 0 : aBuf[nLen] = 0;
485 : else
486 0 : aBuf[PARSER_BUF_SIZE - 1] = 0;
487 0 : if (strstr(aBuf.data(), "<?xml") && strstr(aBuf.data(), "<gpx"))
488 : {
489 0 : CPLError(CE_Failure, CPLE_AppDefined,
490 : "XML parsing of GPX file failed : %s at line %d, "
491 : "column %d",
492 : XML_ErrorString(XML_GetErrorCode(oParser)),
493 0 : static_cast<int>(XML_GetCurrentLineNumber(oParser)),
494 0 : static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
495 : }
496 0 : m_validity = GPX_VALIDITY_INVALID;
497 0 : break;
498 : }
499 29 : if (m_validity == GPX_VALIDITY_INVALID)
500 : {
501 0 : break;
502 : }
503 29 : else if (m_validity == GPX_VALIDITY_VALID)
504 : {
505 : /* If we have recognized the <gpx> element, now we try */
506 : /* to recognize if they are <extensions> tags */
507 : /* But we stop to look for after an arbitrary amount of bytes */
508 29 : if (m_bUseExtensions)
509 15 : break;
510 14 : else if (nTotalBytesRead > 1024 * 1024)
511 0 : break;
512 : }
513 : else
514 : {
515 : // After reading 50 * PARSER_BUF_SIZE bytes, and not finding whether the
516 : // file is GPX or not, we give up and fail silently.
517 0 : nCount++;
518 0 : if (nCount == 50)
519 0 : break;
520 : }
521 14 : } while (!nDone && nLen > 0);
522 :
523 29 : XML_ParserFree(oParser);
524 :
525 29 : VSIFCloseL(fp);
526 :
527 29 : if (m_validity == GPX_VALIDITY_VALID)
528 : {
529 29 : CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
530 29 : if (m_bUseExtensions)
531 15 : CPLDebug("GPX", "It uses <extensions>");
532 :
533 29 : if (m_osVersion.empty())
534 : {
535 : /* Default to 1.1 */
536 0 : CPLError(CE_Warning, CPLE_AppDefined,
537 : "GPX schema version is unknown. "
538 : "The driver may not be able to handle the file correctly "
539 : "and will behave as if it is GPX 1.1.");
540 0 : m_osVersion = "1.1";
541 : }
542 29 : else if (m_osVersion == "1.0" || m_osVersion == "1.1")
543 : {
544 : /* Fine */
545 : }
546 : else
547 : {
548 0 : CPLError(CE_Warning, CPLE_AppDefined,
549 : "GPX schema version '%s' is not handled by the driver. "
550 : "The driver may not be able to handle the file correctly "
551 : "and will behave as if it is GPX 1.1.",
552 : m_osVersion.c_str());
553 : }
554 :
555 29 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
556 29 : GetDescription(), "waypoints", GPX_WPT, this, false,
557 58 : poOpenInfo->papszOpenOptions));
558 29 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
559 29 : GetDescription(), "routes", GPX_ROUTE, this, false,
560 58 : poOpenInfo->papszOpenOptions));
561 29 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
562 29 : GetDescription(), "tracks", GPX_TRACK, this, false,
563 58 : poOpenInfo->papszOpenOptions));
564 29 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
565 29 : GetDescription(), "route_points", GPX_ROUTE_POINT, this, false,
566 58 : poOpenInfo->papszOpenOptions));
567 29 : m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
568 58 : GetDescription(), "track_points", GPX_TRACK_POINT, this, false,
569 58 : poOpenInfo->papszOpenOptions));
570 : }
571 :
572 29 : return m_validity == GPX_VALIDITY_VALID;
573 : #else
574 : VSILFILE *fp = VSIFOpenL(pszFilename, "r");
575 : if (fp)
576 : {
577 : char aBuf[256];
578 : unsigned int nLen =
579 : static_cast<unsigned int>(VSIFReadL(aBuf, 1, 255, fp));
580 : aBuf[nLen] = 0;
581 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
582 : {
583 : CPLError(CE_Failure, CPLE_NotSupported,
584 : "OGR/GPX driver has not been built with read support. "
585 : "Expat library required");
586 : }
587 : VSIFCloseL(fp);
588 : }
589 : return FALSE;
590 : #endif
591 : }
592 :
593 : /************************************************************************/
594 : /* Create() */
595 : /************************************************************************/
596 :
597 25 : int OGRGPXDataSource::Create(const char *pszFilename, char **papszOptions)
598 : {
599 25 : if (strcmp(pszFilename, "/dev/stdout") == 0)
600 0 : pszFilename = "/vsistdout/";
601 :
602 : /* -------------------------------------------------------------------- */
603 : /* Do not overwrite exiting file. */
604 : /* -------------------------------------------------------------------- */
605 : VSIStatBufL sStatBuf;
606 :
607 25 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
608 : {
609 0 : CPLError(CE_Failure, CPLE_NotSupported,
610 : "You have to delete %s before being able to create it with "
611 : "the GPX driver",
612 : pszFilename);
613 0 : return FALSE;
614 : }
615 :
616 : /* -------------------------------------------------------------------- */
617 : /* Create the output file. */
618 : /* -------------------------------------------------------------------- */
619 :
620 25 : SetDescription(pszFilename);
621 :
622 25 : if (strcmp(pszFilename, "/vsistdout/") == 0)
623 : {
624 0 : m_bIsBackSeekable = false;
625 0 : m_fpOutput.reset(VSIFOpenL(pszFilename, "w"));
626 : }
627 : else
628 25 : m_fpOutput.reset(VSIFOpenL(pszFilename, "w+"));
629 25 : if (m_fpOutput == nullptr)
630 : {
631 1 : CPLError(CE_Failure, CPLE_OpenFailed, "Failed to create GPX file %s.",
632 : pszFilename);
633 1 : return FALSE;
634 : }
635 :
636 : /* -------------------------------------------------------------------- */
637 : /* End of line character. */
638 : /* -------------------------------------------------------------------- */
639 24 : const char *pszCRLFFormat = CSLFetchNameValue(papszOptions, "LINEFORMAT");
640 :
641 24 : bool bUseCRLF =
642 : #ifdef _WIN32
643 : true
644 : #else
645 : false
646 : #endif
647 : ;
648 24 : if (pszCRLFFormat == nullptr)
649 : {
650 : // Use default value for OS.
651 : }
652 1 : else if (EQUAL(pszCRLFFormat, "CRLF"))
653 0 : bUseCRLF = true;
654 1 : else if (EQUAL(pszCRLFFormat, "LF"))
655 1 : bUseCRLF = false;
656 : else
657 : {
658 0 : CPLError(CE_Warning, CPLE_AppDefined,
659 : "LINEFORMAT=%s not understood, use one of CRLF or LF.",
660 : pszCRLFFormat);
661 : // Use default value for OS.
662 : }
663 24 : m_pszEOL = (bUseCRLF) ? "\r\n" : "\n";
664 :
665 : /* -------------------------------------------------------------------- */
666 : /* Look at use extensions options. */
667 : /* -------------------------------------------------------------------- */
668 : const char *pszUseExtensions =
669 24 : CSLFetchNameValue(papszOptions, "GPX_USE_EXTENSIONS");
670 24 : const char *pszExtensionsNSURL = nullptr;
671 24 : if (pszUseExtensions && CPLTestBool(pszUseExtensions))
672 : {
673 1 : m_bUseExtensions = true;
674 :
675 : const char *pszExtensionsNSOption =
676 1 : CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS");
677 : const char *pszExtensionsNSURLOption =
678 1 : CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS_URL");
679 1 : if (pszExtensionsNSOption && pszExtensionsNSURLOption)
680 : {
681 0 : m_osExtensionsNS = pszExtensionsNSOption;
682 0 : pszExtensionsNSURL = pszExtensionsNSURLOption;
683 : }
684 : else
685 : {
686 1 : m_osExtensionsNS = "ogr";
687 1 : pszExtensionsNSURL = "http://osgeo.org/gdal";
688 : }
689 : }
690 :
691 : /* -------------------------------------------------------------------- */
692 : /* Output header of GPX file. */
693 : /* -------------------------------------------------------------------- */
694 24 : PrintLine("<?xml version=\"1.0\"?>");
695 24 : m_fpOutput->Printf("<gpx version=\"1.1\" creator=\"");
696 24 : const char *pszCreator = CSLFetchNameValue(papszOptions, "CREATOR");
697 24 : if (pszCreator)
698 : {
699 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszCreator);
700 1 : m_fpOutput->Printf("%s", pszXML);
701 1 : CPLFree(pszXML);
702 : }
703 : else
704 : {
705 23 : m_fpOutput->Printf("GDAL %s", GDALVersionInfo("RELEASE_NAME"));
706 : }
707 24 : m_fpOutput->Printf(
708 : "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
709 24 : if (m_bUseExtensions)
710 1 : m_fpOutput->Printf("xmlns:%s=\"%s\" ", m_osExtensionsNS.c_str(),
711 : pszExtensionsNSURL);
712 24 : m_fpOutput->Printf("xmlns=\"http://www.topografix.com/GPX/1/1\" ");
713 24 : PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 "
714 : "http://www.topografix.com/GPX/1/1/gpx.xsd\">");
715 24 : PrintLine("<metadata>");
716 : /*
717 : Something like:
718 : <metadata>
719 : <name>metadata name</name>
720 : <desc>metadata desc</desc>
721 : <author>
722 : <name>metadata author name</name>
723 : <email id="foo" domain="example.com"/>
724 : <link href="author_href"><text>author_text</text><type>author_type</type></link>
725 : </author>
726 : <copyright author="copyright author"><year>2023</year><license>my license</license></copyright>
727 : <link href="href"><text>text</text><type>type</type></link>
728 : <link href="href2"><text>text2</text><type>type2</type></link>
729 : <time>2007-11-25T17:58:00+01:00</time>
730 : <keywords>kw</keywords>
731 : <bounds minlat="-90" minlon="-180" maxlat="90" maxlon="179.9999999"/>
732 : </metadata>
733 : */
734 :
735 24 : if (const char *pszMetadataName =
736 24 : CSLFetchNameValue(papszOptions, "METADATA_NAME"))
737 : {
738 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataName);
739 1 : PrintLine(" <name>%s</name>", pszXML);
740 1 : CPLFree(pszXML);
741 : }
742 :
743 24 : if (const char *pszMetadataDesc =
744 24 : CSLFetchNameValue(papszOptions, "METADATA_DESCRIPTION"))
745 : {
746 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataDesc);
747 1 : PrintLine(" <desc>%s</desc>", pszXML);
748 1 : CPLFree(pszXML);
749 : }
750 :
751 : const char *pszMetadataAuthorName =
752 24 : CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_NAME");
753 : const char *pszMetadataAuthorEmail =
754 24 : CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_EMAIL");
755 : const char *pszMetadataAuthorLinkHref =
756 24 : CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_LINK_HREF");
757 24 : if (pszMetadataAuthorName || pszMetadataAuthorEmail ||
758 : pszMetadataAuthorLinkHref)
759 : {
760 1 : PrintLine(" <author>");
761 1 : if (pszMetadataAuthorName)
762 : {
763 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataAuthorName);
764 1 : PrintLine(" <name>%s</name>", pszXML);
765 1 : CPLFree(pszXML);
766 : }
767 1 : if (pszMetadataAuthorEmail)
768 : {
769 2 : std::string osEmail = pszMetadataAuthorEmail;
770 1 : auto nPos = osEmail.find('@');
771 1 : if (nPos != std::string::npos)
772 : {
773 1 : char *pszId = OGRGetXML_UTF8_EscapedString(
774 2 : osEmail.substr(0, nPos).c_str());
775 1 : char *pszDomain = OGRGetXML_UTF8_EscapedString(
776 2 : osEmail.substr(nPos + 1).c_str());
777 1 : PrintLine(" <email id=\"%s\" domain=\"%s\"/>", pszId,
778 : pszDomain);
779 1 : CPLFree(pszId);
780 1 : CPLFree(pszDomain);
781 : }
782 : }
783 1 : if (pszMetadataAuthorLinkHref)
784 : {
785 : {
786 : char *pszXML =
787 1 : OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkHref);
788 1 : PrintLine(" <link href=\"%s\">", pszXML);
789 1 : CPLFree(pszXML);
790 : }
791 1 : if (const char *pszMetadataAuthorLinkText = CSLFetchNameValue(
792 : papszOptions, "METADATA_AUTHOR_LINK_TEXT"))
793 : {
794 : char *pszXML =
795 1 : OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkText);
796 1 : PrintLine(" <text>%s</text>", pszXML);
797 1 : CPLFree(pszXML);
798 : }
799 1 : if (const char *pszMetadataAuthorLinkType = CSLFetchNameValue(
800 : papszOptions, "METADATA_AUTHOR_LINK_TYPE"))
801 : {
802 : char *pszXML =
803 1 : OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkType);
804 1 : PrintLine(" <type>%s</type>", pszXML);
805 1 : CPLFree(pszXML);
806 : }
807 1 : PrintLine(" </link>");
808 : }
809 1 : PrintLine(" </author>");
810 : }
811 :
812 24 : if (const char *pszMetadataCopyrightAuthor =
813 24 : CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_AUTHOR"))
814 : {
815 : {
816 : char *pszXML =
817 1 : OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightAuthor);
818 1 : PrintLine(" <copyright author=\"%s\">", pszXML);
819 1 : CPLFree(pszXML);
820 : }
821 1 : if (const char *pszMetadataCopyrightYear =
822 1 : CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_YEAR"))
823 : {
824 : char *pszXML =
825 1 : OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightYear);
826 1 : PrintLine(" <year>%s</year>", pszXML);
827 1 : CPLFree(pszXML);
828 : }
829 1 : if (const char *pszMetadataCopyrightLicense =
830 1 : CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_LICENSE"))
831 : {
832 : char *pszXML =
833 1 : OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightLicense);
834 1 : PrintLine(" <license>%s</license>", pszXML);
835 1 : CPLFree(pszXML);
836 : }
837 1 : PrintLine(" </copyright>");
838 : }
839 :
840 45 : for (CSLConstList papszIter = papszOptions; papszIter && *papszIter;
841 : ++papszIter)
842 : {
843 21 : if (STARTS_WITH_CI(*papszIter, "METADATA_LINK_") &&
844 6 : strstr(*papszIter, "_HREF"))
845 : {
846 2 : const int nLinkNum = atoi(*papszIter + strlen("METADATA_LINK_"));
847 2 : const char *pszVal = strchr(*papszIter, '=');
848 2 : if (pszVal)
849 : {
850 : {
851 2 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszVal + 1);
852 2 : PrintLine(" <link href=\"%s\">", pszXML);
853 2 : CPLFree(pszXML);
854 : }
855 2 : if (const char *pszText = CSLFetchNameValue(
856 : papszOptions,
857 : CPLSPrintf("METADATA_LINK_%d_TEXT", nLinkNum)))
858 : {
859 2 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszText);
860 2 : PrintLine(" <text>%s</text>", pszXML);
861 2 : CPLFree(pszXML);
862 : }
863 2 : if (const char *pszType = CSLFetchNameValue(
864 : papszOptions,
865 : CPLSPrintf("METADATA_LINK_%d_TYPE", nLinkNum)))
866 : {
867 2 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszType);
868 2 : PrintLine(" <type>%s</type>", pszXML);
869 2 : CPLFree(pszXML);
870 : }
871 2 : PrintLine(" </link>");
872 : }
873 : }
874 : }
875 :
876 24 : if (const char *pszMetadataTime =
877 24 : CSLFetchNameValue(papszOptions, "METADATA_TIME"))
878 : {
879 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataTime);
880 1 : PrintLine(" <time>%s</time>", pszXML);
881 1 : CPLFree(pszXML);
882 : }
883 :
884 24 : if (const char *pszMetadataKeywords =
885 24 : CSLFetchNameValue(papszOptions, "METADATA_KEYWORDS"))
886 : {
887 1 : char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataKeywords);
888 1 : PrintLine(" <keywords>%s</keywords>", pszXML);
889 1 : CPLFree(pszXML);
890 : }
891 :
892 24 : if (m_bIsBackSeekable)
893 : {
894 : /* Reserve space for <bounds .../> within <metadata> */
895 : char szBounds[SPACE_FOR_METADATA_BOUNDS + 1];
896 24 : memset(szBounds, ' ', SPACE_FOR_METADATA_BOUNDS);
897 24 : szBounds[SPACE_FOR_METADATA_BOUNDS] = '\0';
898 24 : m_nOffsetBounds = m_fpOutput->Tell();
899 24 : PrintLine("%s", szBounds);
900 : }
901 24 : PrintLine("</metadata>");
902 :
903 24 : return TRUE;
904 : }
905 :
906 : /************************************************************************/
907 : /* AddCoord() */
908 : /************************************************************************/
909 :
910 41 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
911 : {
912 41 : m_dfMinLon = std::min(m_dfMinLon, dfLon);
913 41 : m_dfMinLat = std::min(m_dfMinLat, dfLat);
914 41 : m_dfMaxLon = std::max(m_dfMaxLon, dfLon);
915 41 : m_dfMaxLat = std::max(m_dfMaxLat, dfLat);
916 41 : }
917 :
918 : /************************************************************************/
919 : /* PrintLine() */
920 : /************************************************************************/
921 :
922 372 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
923 : {
924 744 : CPLString osWork;
925 : va_list args;
926 :
927 372 : va_start(args, fmt);
928 372 : osWork.vPrintf(fmt, args);
929 372 : va_end(args);
930 :
931 372 : m_fpOutput->Write(osWork.c_str(), 1, osWork.size());
932 372 : m_fpOutput->Write(m_pszEOL, 1, strlen(m_pszEOL));
933 372 : }
|