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