Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GPX Translator
4 : * Purpose: Implements OGRGPXLayer class.
5 : * Author: Even Rouault, even dot rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007-2014, 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 <cstdio>
17 : #include <cstdlib>
18 : #include <cstring>
19 :
20 : #include "cpl_conv.h"
21 : #include "cpl_error.h"
22 : #include "cpl_minixml.h"
23 : #include "cpl_string.h"
24 : #include "cpl_vsi.h"
25 : #ifdef HAVE_EXPAT
26 : #include "expat.h"
27 : #endif
28 : #include "ogr_core.h"
29 : #include "ogr_expat.h"
30 : #include "ogr_feature.h"
31 : #include "ogr_geometry.h"
32 : #include "ogr_p.h"
33 : #include "ogr_spatialref.h"
34 :
35 : constexpr int FLD_TRACK_FID = 0;
36 : constexpr int FLD_TRACK_SEG_ID = 1;
37 : #ifdef HAVE_EXPAT
38 : constexpr int FLD_TRACK_PT_ID = 2;
39 : #endif
40 : constexpr int FLD_TRACK_NAME = 3;
41 :
42 : constexpr int FLD_ROUTE_FID = 0;
43 : #ifdef HAVE_EXPAT
44 : constexpr int FLD_ROUTE_PT_ID = 1;
45 : #endif
46 : constexpr int FLD_ROUTE_NAME = 2;
47 :
48 : /************************************************************************/
49 : /* OGRGPXLayer() */
50 : /* */
51 : /* Note that the OGRGPXLayer assumes ownership of the passed */
52 : /* file pointer. */
53 : /************************************************************************/
54 :
55 165 : OGRGPXLayer::OGRGPXLayer(const char *pszFilename, const char *pszLayerName,
56 : GPXGeometryType gpxGeomTypeIn,
57 : OGRGPXDataSource *poDSIn, bool bWriteModeIn,
58 165 : CSLConstList papszOpenOptions)
59 165 : : m_poDS(poDSIn), m_gpxGeomType(gpxGeomTypeIn), m_bWriteMode(bWriteModeIn)
60 : {
61 : #ifdef HAVE_EXPAT
62 165 : const char *gpxVersion = m_poDS->GetVersion();
63 : #endif
64 :
65 165 : m_nMaxLinks =
66 165 : atoi(CSLFetchNameValueDef(papszOpenOptions, "N_MAX_LINKS",
67 : CPLGetConfigOption("GPX_N_MAX_LINKS", "2")));
68 165 : if (m_nMaxLinks < 0)
69 0 : m_nMaxLinks = 2;
70 165 : if (m_nMaxLinks > 100)
71 0 : m_nMaxLinks = 100;
72 :
73 165 : m_bEleAs25D = CPLTestBool(
74 : CSLFetchNameValueDef(papszOpenOptions, "ELE_AS_25D",
75 : CPLGetConfigOption("GPX_ELE_AS_25D", "NO")));
76 :
77 165 : const bool bShortNames = CPLTestBool(
78 : CSLFetchNameValueDef(papszOpenOptions, "SHORT_NAMES",
79 : CPLGetConfigOption("GPX_SHORT_NAMES", "NO")));
80 :
81 165 : m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
82 165 : SetDescription(m_poFeatureDefn->GetName());
83 165 : m_poFeatureDefn->Reference();
84 :
85 165 : if (m_gpxGeomType == GPX_TRACK_POINT)
86 : {
87 : /* Don't move this code. This fields must be number 0, 1 and 2 */
88 : /* in order to make OGRGPXLayer::startElementCbk work */
89 62 : OGRFieldDefn oFieldTrackFID("track_fid", OFTInteger);
90 31 : m_poFeatureDefn->AddFieldDefn(&oFieldTrackFID);
91 :
92 : OGRFieldDefn oFieldTrackSegID(
93 62 : (bShortNames) ? "trksegid" : "track_seg_id", OFTInteger);
94 31 : m_poFeatureDefn->AddFieldDefn(&oFieldTrackSegID);
95 :
96 : OGRFieldDefn oFieldTrackSegPointID(
97 62 : (bShortNames) ? "trksegptid" : "track_seg_point_id", OFTInteger);
98 31 : m_poFeatureDefn->AddFieldDefn(&oFieldTrackSegPointID);
99 :
100 31 : if (m_bWriteMode)
101 : {
102 4 : OGRFieldDefn oFieldName("track_name", OFTString);
103 2 : m_poFeatureDefn->AddFieldDefn(&oFieldName);
104 : }
105 : }
106 134 : else if (m_gpxGeomType == GPX_ROUTE_POINT)
107 : {
108 : /* Don't move this code. See above */
109 62 : OGRFieldDefn oFieldRouteFID("route_fid", OFTInteger);
110 31 : m_poFeatureDefn->AddFieldDefn(&oFieldRouteFID);
111 :
112 : OGRFieldDefn oFieldRoutePointID(
113 62 : (bShortNames) ? "rteptid" : "route_point_id", OFTInteger);
114 31 : m_poFeatureDefn->AddFieldDefn(&oFieldRoutePointID);
115 :
116 31 : if (m_bWriteMode)
117 : {
118 4 : OGRFieldDefn oFieldName("route_name", OFTString);
119 2 : m_poFeatureDefn->AddFieldDefn(&oFieldName);
120 : }
121 : }
122 :
123 165 : m_iFirstGPXField = m_poFeatureDefn->GetFieldCount();
124 :
125 165 : if (m_gpxGeomType == GPX_WPT || m_gpxGeomType == GPX_TRACK_POINT ||
126 99 : m_gpxGeomType == GPX_ROUTE_POINT)
127 : {
128 97 : m_poFeatureDefn->SetGeomType((m_bEleAs25D) ? wkbPoint25D : wkbPoint);
129 : /* Position info */
130 :
131 194 : OGRFieldDefn oFieldEle("ele", OFTReal);
132 97 : m_poFeatureDefn->AddFieldDefn(&oFieldEle);
133 :
134 194 : OGRFieldDefn oFieldTime("time", OFTDateTime);
135 97 : m_poFeatureDefn->AddFieldDefn(&oFieldTime);
136 :
137 : #ifdef HAVE_EXPAT
138 97 : if (m_gpxGeomType == GPX_TRACK_POINT && strcmp(gpxVersion, "1.0") == 0)
139 : {
140 0 : OGRFieldDefn oFieldCourse("course", OFTReal);
141 0 : m_poFeatureDefn->AddFieldDefn(&oFieldCourse);
142 :
143 0 : OGRFieldDefn oFieldSpeed("speed", OFTReal);
144 0 : m_poFeatureDefn->AddFieldDefn(&oFieldSpeed);
145 : }
146 : #endif
147 :
148 194 : OGRFieldDefn oFieldMagVar("magvar", OFTReal);
149 97 : m_poFeatureDefn->AddFieldDefn(&oFieldMagVar);
150 :
151 194 : OGRFieldDefn oFieldGeoidHeight("geoidheight", OFTReal);
152 97 : m_poFeatureDefn->AddFieldDefn(&oFieldGeoidHeight);
153 :
154 : /* Description info */
155 :
156 194 : OGRFieldDefn oFieldName("name", OFTString);
157 97 : m_poFeatureDefn->AddFieldDefn(&oFieldName);
158 :
159 194 : OGRFieldDefn oFieldCmt("cmt", OFTString);
160 97 : m_poFeatureDefn->AddFieldDefn(&oFieldCmt);
161 :
162 194 : OGRFieldDefn oFieldDesc("desc", OFTString);
163 97 : m_poFeatureDefn->AddFieldDefn(&oFieldDesc);
164 :
165 194 : OGRFieldDefn oFieldSrc("src", OFTString);
166 97 : m_poFeatureDefn->AddFieldDefn(&oFieldSrc);
167 :
168 : #ifdef HAVE_EXPAT
169 97 : if (strcmp(gpxVersion, "1.0") == 0)
170 : {
171 0 : OGRFieldDefn oFieldUrl("url", OFTString);
172 0 : m_poFeatureDefn->AddFieldDefn(&oFieldUrl);
173 :
174 0 : OGRFieldDefn oFieldUrlName("urlname", OFTString);
175 0 : m_poFeatureDefn->AddFieldDefn(&oFieldUrlName);
176 : }
177 : else
178 : #endif
179 : {
180 294 : for (int i = 1; i <= m_nMaxLinks; i++)
181 : {
182 : char szFieldName[32];
183 197 : snprintf(szFieldName, sizeof(szFieldName), "link%d_href", i);
184 394 : OGRFieldDefn oFieldLinkHref(szFieldName, OFTString);
185 197 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkHref);
186 :
187 197 : snprintf(szFieldName, sizeof(szFieldName), "link%d_text", i);
188 394 : OGRFieldDefn oFieldLinkText(szFieldName, OFTString);
189 197 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkText);
190 :
191 197 : snprintf(szFieldName, sizeof(szFieldName), "link%d_type", i);
192 394 : OGRFieldDefn oFieldLinkType(szFieldName, OFTString);
193 197 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkType);
194 : }
195 : }
196 :
197 194 : OGRFieldDefn oFieldSym("sym", OFTString);
198 97 : m_poFeatureDefn->AddFieldDefn(&oFieldSym);
199 :
200 194 : OGRFieldDefn oFieldType("type", OFTString);
201 97 : m_poFeatureDefn->AddFieldDefn(&oFieldType);
202 :
203 : /* Accuracy info */
204 :
205 194 : OGRFieldDefn oFieldFix("fix", OFTString);
206 97 : m_poFeatureDefn->AddFieldDefn(&oFieldFix);
207 :
208 194 : OGRFieldDefn oFieldSat("sat", OFTInteger);
209 97 : m_poFeatureDefn->AddFieldDefn(&oFieldSat);
210 :
211 194 : OGRFieldDefn oFieldHdop("hdop", OFTReal);
212 97 : m_poFeatureDefn->AddFieldDefn(&oFieldHdop);
213 :
214 194 : OGRFieldDefn oFieldVdop("vdop", OFTReal);
215 97 : m_poFeatureDefn->AddFieldDefn(&oFieldVdop);
216 :
217 194 : OGRFieldDefn oFieldPdop("pdop", OFTReal);
218 97 : m_poFeatureDefn->AddFieldDefn(&oFieldPdop);
219 :
220 194 : OGRFieldDefn oFieldAgeofgpsdata("ageofdgpsdata", OFTReal);
221 97 : m_poFeatureDefn->AddFieldDefn(&oFieldAgeofgpsdata);
222 :
223 194 : OGRFieldDefn oFieldDgpsid("dgpsid", OFTInteger);
224 194 : m_poFeatureDefn->AddFieldDefn(&oFieldDgpsid);
225 : }
226 : else
227 : {
228 68 : if (m_gpxGeomType == GPX_TRACK)
229 34 : m_poFeatureDefn->SetGeomType((m_bEleAs25D) ? wkbMultiLineString25D
230 34 : : wkbMultiLineString);
231 : else
232 34 : m_poFeatureDefn->SetGeomType((m_bEleAs25D) ? wkbLineString25D
233 34 : : wkbLineString);
234 :
235 136 : OGRFieldDefn oFieldName("name", OFTString);
236 68 : m_poFeatureDefn->AddFieldDefn(&oFieldName);
237 :
238 136 : OGRFieldDefn oFieldCmt("cmt", OFTString);
239 68 : m_poFeatureDefn->AddFieldDefn(&oFieldCmt);
240 :
241 136 : OGRFieldDefn oFieldDesc("desc", OFTString);
242 68 : m_poFeatureDefn->AddFieldDefn(&oFieldDesc);
243 :
244 136 : OGRFieldDefn oFieldSrc("src", OFTString);
245 68 : m_poFeatureDefn->AddFieldDefn(&oFieldSrc);
246 :
247 206 : for (int i = 1; i <= m_nMaxLinks; i++)
248 : {
249 : char szFieldName[32];
250 138 : snprintf(szFieldName, sizeof(szFieldName), "link%d_href", i);
251 276 : OGRFieldDefn oFieldLinkHref(szFieldName, OFTString);
252 138 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkHref);
253 :
254 138 : snprintf(szFieldName, sizeof(szFieldName), "link%d_text", i);
255 276 : OGRFieldDefn oFieldLinkText(szFieldName, OFTString);
256 138 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkText);
257 :
258 138 : snprintf(szFieldName, sizeof(szFieldName), "link%d_type", i);
259 276 : OGRFieldDefn oFieldLinkType(szFieldName, OFTString);
260 138 : m_poFeatureDefn->AddFieldDefn(&oFieldLinkType);
261 : }
262 :
263 136 : OGRFieldDefn oFieldNumber("number", OFTInteger);
264 68 : m_poFeatureDefn->AddFieldDefn(&oFieldNumber);
265 :
266 136 : OGRFieldDefn oFieldType("type", OFTString);
267 68 : m_poFeatureDefn->AddFieldDefn(&oFieldType);
268 : }
269 :
270 : /* Number of 'standard' GPX attributes */
271 165 : m_nGPXFields = m_poFeatureDefn->GetFieldCount();
272 :
273 165 : m_poSRS = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
274 165 : m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
275 :
276 165 : if (m_poFeatureDefn->GetGeomFieldCount() != 0)
277 165 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
278 :
279 165 : if (!m_bWriteMode)
280 : {
281 145 : m_fpGPX.reset(VSIFOpenL(pszFilename, "r"));
282 145 : if (m_fpGPX == nullptr)
283 : {
284 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
285 : pszFilename);
286 0 : return;
287 : }
288 :
289 215 : if (m_poDS->GetUseExtensions() ||
290 70 : CPLTestBool(CPLGetConfigOption("GPX_USE_EXTENSIONS", "FALSE")))
291 : {
292 75 : LoadExtensionsSchema();
293 : }
294 : }
295 :
296 165 : OGRGPXLayer::ResetReading();
297 : }
298 :
299 : /************************************************************************/
300 : /* ~OGRGPXLayer() */
301 : /************************************************************************/
302 :
303 330 : OGRGPXLayer::~OGRGPXLayer()
304 :
305 : {
306 : #ifdef HAVE_EXPAT
307 165 : if (m_oParser)
308 145 : XML_ParserFree(m_oParser);
309 : #endif
310 165 : m_poFeatureDefn->Release();
311 :
312 165 : if (m_poSRS != nullptr)
313 165 : m_poSRS->Release();
314 330 : }
315 :
316 : #ifdef HAVE_EXPAT
317 :
318 2476 : static void XMLCALL startElementCbk(void *pUserData, const char *pszName,
319 : const char **ppszAttr)
320 : {
321 2476 : static_cast<OGRGPXLayer *>(pUserData)->startElementCbk(pszName, ppszAttr);
322 2476 : }
323 :
324 2476 : static void XMLCALL endElementCbk(void *pUserData, const char *pszName)
325 : {
326 2476 : static_cast<OGRGPXLayer *>(pUserData)->endElementCbk(pszName);
327 2476 : }
328 :
329 6437 : static void XMLCALL dataHandlerCbk(void *pUserData, const char *data, int nLen)
330 : {
331 6437 : static_cast<OGRGPXLayer *>(pUserData)->dataHandlerCbk(data, nLen);
332 6437 : }
333 :
334 : #endif
335 :
336 : /************************************************************************/
337 : /* ResetReading() */
338 : /************************************************************************/
339 :
340 224 : void OGRGPXLayer::ResetReading()
341 :
342 : {
343 224 : m_nNextFID = 0;
344 224 : if (m_fpGPX)
345 : {
346 198 : m_fpGPX->Seek(0, SEEK_SET);
347 : #ifdef HAVE_EXPAT
348 198 : if (m_oParser)
349 53 : XML_ParserFree(m_oParser);
350 :
351 198 : m_oParser = OGRCreateExpatXMLParser();
352 198 : XML_SetElementHandler(m_oParser, ::startElementCbk, ::endElementCbk);
353 198 : XML_SetCharacterDataHandler(m_oParser, ::dataHandlerCbk);
354 198 : XML_SetUserData(m_oParser, this);
355 : #endif
356 : }
357 224 : m_hasFoundLat = false;
358 224 : m_hasFoundLon = false;
359 224 : m_inInterestingElement = false;
360 224 : m_osSubElementName.clear();
361 224 : m_osSubElementValue.clear();
362 :
363 224 : m_poFeature.reset();
364 224 : m_oFeatureQueue.clear();
365 224 : m_multiLineString.reset();
366 224 : m_lineString.reset();
367 :
368 224 : m_depthLevel = 0;
369 224 : m_interestingDepthLevel = 0;
370 :
371 224 : m_trkFID = 0;
372 224 : m_trkSegId = 0;
373 224 : m_trkSegPtId = 0;
374 224 : m_rteFID = 0;
375 224 : m_rtePtId = 0;
376 224 : }
377 :
378 : #ifdef HAVE_EXPAT
379 :
380 : /************************************************************************/
381 : /* startElementCbk() */
382 : /************************************************************************/
383 :
384 : /** Replace ':' from XML NS element name by '_' more OGR friendly */
385 86 : static char *OGRGPX_GetOGRCompatibleTagName(const char *pszName)
386 : {
387 86 : char *pszModName = CPLStrdup(pszName);
388 1162 : for (int i = 0; pszModName[i] != 0; i++)
389 : {
390 1076 : if (pszModName[i] == ':')
391 80 : pszModName[i] = '_';
392 : }
393 86 : return pszModName;
394 : }
395 :
396 0 : void OGRGPXLayer::AddStrToSubElementValue(const char *pszStr)
397 : {
398 : try
399 : {
400 0 : m_osSubElementValue.append(pszStr);
401 : }
402 0 : catch (const std::bad_alloc &)
403 : {
404 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
405 : "Out of memory when parsing GPX file");
406 0 : XML_StopParser(m_oParser, XML_FALSE);
407 0 : m_bStopParsing = true;
408 : }
409 0 : }
410 :
411 2476 : void OGRGPXLayer::startElementCbk(const char *pszName, const char **ppszAttr)
412 : {
413 2476 : if (m_bStopParsing)
414 0 : return;
415 :
416 2476 : m_nWithoutEventCounter = 0;
417 :
418 2476 : if ((m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0) ||
419 2438 : (m_gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0) ||
420 2426 : (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0))
421 : {
422 74 : m_interestingDepthLevel = m_depthLevel;
423 :
424 74 : m_poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
425 74 : m_inInterestingElement = true;
426 74 : m_hasFoundLat = false;
427 74 : m_hasFoundLon = false;
428 74 : m_inExtensions = false;
429 74 : m_inLink = false;
430 74 : m_iCountLink = 0;
431 :
432 222 : for (int i = 0; ppszAttr[i]; i += 2)
433 : {
434 148 : if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
435 : {
436 74 : m_hasFoundLat = true;
437 74 : m_latVal = CPLAtof(ppszAttr[i + 1]);
438 : }
439 74 : else if (strcmp(ppszAttr[i], "lon") == 0 && ppszAttr[i + 1][0])
440 : {
441 74 : m_hasFoundLon = true;
442 74 : m_lonVal = CPLAtof(ppszAttr[i + 1]);
443 : }
444 : }
445 :
446 74 : m_poFeature->SetFID(m_nNextFID++);
447 :
448 74 : if (m_hasFoundLat && m_hasFoundLon)
449 : {
450 74 : m_poFeature->SetGeometryDirectly(new OGRPoint(m_lonVal, m_latVal));
451 : }
452 : else
453 : {
454 0 : CPLDebug("GPX",
455 : "Skipping %s (FID=" CPL_FRMT_GIB
456 : ") without lat and/or lon",
457 : pszName, m_nNextFID);
458 : }
459 :
460 74 : if (m_gpxGeomType == GPX_ROUTE_POINT)
461 : {
462 12 : m_rtePtId++;
463 12 : m_poFeature->SetField(FLD_ROUTE_FID, m_rteFID - 1);
464 12 : m_poFeature->SetField(FLD_ROUTE_PT_ID, m_rtePtId - 1);
465 : }
466 62 : else if (m_gpxGeomType == GPX_TRACK_POINT)
467 : {
468 24 : m_trkSegPtId++;
469 :
470 24 : m_poFeature->SetField(FLD_TRACK_FID, m_trkFID - 1);
471 24 : m_poFeature->SetField(FLD_TRACK_SEG_ID, m_trkSegId - 1);
472 24 : m_poFeature->SetField(FLD_TRACK_PT_ID, m_trkSegPtId - 1);
473 74 : }
474 : }
475 2402 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
476 : {
477 15 : m_interestingDepthLevel = m_depthLevel;
478 :
479 15 : m_inExtensions = false;
480 15 : m_inLink = false;
481 15 : m_iCountLink = 0;
482 15 : m_poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
483 15 : m_inInterestingElement = true;
484 :
485 15 : m_multiLineString = std::make_unique<OGRMultiLineString>();
486 15 : m_lineString.reset();
487 :
488 15 : m_poFeature->SetFID(m_nNextFID++);
489 : }
490 2387 : else if (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trk") == 0)
491 : {
492 20 : m_trkFID++;
493 20 : m_trkSegId = 0;
494 : }
495 2367 : else if (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkseg") == 0)
496 : {
497 17 : m_trkSegId++;
498 17 : m_trkSegPtId = 0;
499 : }
500 2350 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
501 : {
502 10 : m_interestingDepthLevel = m_depthLevel;
503 :
504 10 : m_poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
505 10 : m_inInterestingElement = true;
506 10 : m_inExtensions = false;
507 10 : m_inLink = false;
508 10 : m_iCountLink = 0;
509 :
510 10 : m_lineString = std::make_unique<OGRLineString>();
511 10 : m_poFeature->SetFID(m_nNextFID++);
512 : }
513 2340 : else if (m_gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rte") == 0)
514 : {
515 8 : m_rteFID++;
516 8 : m_rtePtId = 0;
517 : }
518 2332 : else if (m_inInterestingElement)
519 : {
520 445 : if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trkseg") == 0 &&
521 12 : m_depthLevel == m_interestingDepthLevel + 1)
522 : {
523 12 : if (m_multiLineString)
524 : {
525 12 : m_lineString = std::make_unique<OGRLineString>();
526 : }
527 : }
528 433 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trkpt") == 0 &&
529 19 : m_depthLevel == m_interestingDepthLevel + 2)
530 : {
531 19 : if (m_lineString)
532 : {
533 19 : m_hasFoundLat = false;
534 19 : m_hasFoundLon = false;
535 57 : for (int i = 0; ppszAttr[i]; i += 2)
536 : {
537 38 : if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
538 : {
539 19 : m_hasFoundLat = true;
540 19 : m_latVal = CPLAtof(ppszAttr[i + 1]);
541 : }
542 19 : else if (strcmp(ppszAttr[i], "lon") == 0 &&
543 19 : ppszAttr[i + 1][0])
544 : {
545 19 : m_hasFoundLon = true;
546 19 : m_lonVal = CPLAtof(ppszAttr[i + 1]);
547 : }
548 : }
549 :
550 19 : if (m_hasFoundLat && m_hasFoundLon)
551 : {
552 19 : m_lineString->addPoint(m_lonVal, m_latVal);
553 : }
554 : else
555 : {
556 0 : CPLDebug("GPX", "Skipping %s without lat and/or lon",
557 : pszName);
558 : }
559 19 : }
560 : }
561 414 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rtept") == 0 &&
562 15 : m_depthLevel == m_interestingDepthLevel + 1)
563 : {
564 15 : if (m_lineString)
565 : {
566 15 : m_hasFoundLat = false;
567 15 : m_hasFoundLon = false;
568 45 : for (int i = 0; ppszAttr[i]; i += 2)
569 : {
570 30 : if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
571 : {
572 15 : m_hasFoundLat = true;
573 15 : m_latVal = CPLAtof(ppszAttr[i + 1]);
574 : }
575 15 : else if (strcmp(ppszAttr[i], "lon") == 0 &&
576 15 : ppszAttr[i + 1][0])
577 : {
578 15 : m_hasFoundLon = true;
579 15 : m_lonVal = CPLAtof(ppszAttr[i + 1]);
580 : }
581 : }
582 :
583 15 : if (m_hasFoundLat && m_hasFoundLon)
584 : {
585 15 : m_lineString->addPoint(m_lonVal, m_latVal);
586 : }
587 : else
588 : {
589 0 : CPLDebug("GPX", "Skipping %s without lat and/or lon",
590 : pszName);
591 : }
592 15 : }
593 : }
594 27 : else if (m_bEleAs25D && strcmp(pszName, "ele") == 0 &&
595 426 : m_lineString != nullptr &&
596 3 : ((m_gpxGeomType == GPX_ROUTE &&
597 3 : m_depthLevel == m_interestingDepthLevel + 2) ||
598 0 : (m_gpxGeomType == GPX_TRACK &&
599 0 : m_depthLevel == m_interestingDepthLevel + 3)))
600 : {
601 3 : m_osSubElementName = pszName;
602 : }
603 396 : else if (m_depthLevel == m_interestingDepthLevel + 1 &&
604 236 : strcmp(pszName, "extensions") == 0)
605 : {
606 20 : if (m_poDS->GetUseExtensions())
607 : {
608 20 : m_inExtensions = true;
609 : }
610 : }
611 376 : else if (m_depthLevel == m_interestingDepthLevel + 1 ||
612 160 : (m_inExtensions &&
613 26 : m_depthLevel == m_interestingDepthLevel + 2))
614 : {
615 242 : m_osSubElementName.clear();
616 242 : m_iCurrentField = -1;
617 :
618 242 : if (strcmp(pszName, "link") == 0)
619 : {
620 42 : m_iCountLink++;
621 42 : if (m_iCountLink <= m_nMaxLinks)
622 : {
623 28 : if (ppszAttr[0] && ppszAttr[1] &&
624 28 : strcmp(ppszAttr[0], "href") == 0)
625 : {
626 : char szFieldName[32];
627 28 : snprintf(szFieldName, sizeof(szFieldName),
628 : "link%d_href", m_iCountLink);
629 28 : m_iCurrentField =
630 28 : m_poFeatureDefn->GetFieldIndex(szFieldName);
631 28 : m_poFeature->SetField(m_iCurrentField, ppszAttr[1]);
632 : }
633 : }
634 : else
635 : {
636 : static int once = 1;
637 14 : if (once)
638 : {
639 1 : once = 0;
640 1 : CPLError(CE_Warning, CPLE_AppDefined,
641 : "GPX driver only reads %d links per element. "
642 : "Others will be ignored. "
643 : "This can be changed with the GPX_N_MAX_LINKS "
644 : "environment variable",
645 : m_nMaxLinks);
646 : }
647 : }
648 42 : m_inLink = true;
649 42 : m_iCurrentField = -1;
650 : }
651 : else
652 : {
653 1396 : for (int iField = 0; iField < m_poFeatureDefn->GetFieldCount();
654 : iField++)
655 : {
656 1396 : bool bMatch = false;
657 1396 : if (iField >= m_nGPXFields)
658 : {
659 : char *pszCompatibleName =
660 63 : OGRGPX_GetOGRCompatibleTagName(pszName);
661 63 : bMatch = strcmp(m_poFeatureDefn->GetFieldDefn(iField)
662 : ->GetNameRef(),
663 : pszCompatibleName) == 0;
664 63 : CPLFree(pszCompatibleName);
665 : }
666 : else
667 : {
668 1333 : bMatch = strcmp(m_poFeatureDefn->GetFieldDefn(iField)
669 : ->GetNameRef(),
670 : pszName) == 0;
671 : }
672 :
673 1396 : if (bMatch)
674 : {
675 200 : m_iCurrentField = iField;
676 200 : m_osSubElementName = pszName;
677 200 : break;
678 : }
679 : }
680 242 : }
681 : }
682 134 : else if (m_depthLevel == m_interestingDepthLevel + 2 && m_inLink)
683 : {
684 84 : m_osSubElementName.clear();
685 84 : m_iCurrentField = -1;
686 84 : if (m_iCountLink <= m_nMaxLinks)
687 : {
688 56 : if (strcmp(pszName, "type") == 0)
689 : {
690 : char szFieldName[32];
691 28 : snprintf(szFieldName, sizeof(szFieldName), "link%d_type",
692 : m_iCountLink);
693 28 : m_iCurrentField =
694 28 : m_poFeatureDefn->GetFieldIndex(szFieldName);
695 28 : m_osSubElementName = pszName;
696 : }
697 28 : else if (strcmp(pszName, "text") == 0)
698 : {
699 : char szFieldName[32];
700 28 : snprintf(szFieldName, sizeof(szFieldName), "link%d_text",
701 : m_iCountLink);
702 28 : m_iCurrentField =
703 28 : m_poFeatureDefn->GetFieldIndex(szFieldName);
704 28 : m_osSubElementName = pszName;
705 : }
706 84 : }
707 : }
708 50 : else if (m_inExtensions && m_depthLevel > m_interestingDepthLevel + 2)
709 : {
710 0 : AddStrToSubElementValue((ppszAttr[0] == nullptr)
711 0 : ? CPLSPrintf("<%s>", pszName)
712 0 : : CPLSPrintf("<%s ", pszName));
713 0 : for (int i = 0; ppszAttr[i]; i += 2)
714 : {
715 0 : AddStrToSubElementValue(
716 0 : CPLSPrintf("%s=\"%s\" ", ppszAttr[i], ppszAttr[i + 1]));
717 : }
718 0 : if (ppszAttr[0] != nullptr)
719 : {
720 0 : AddStrToSubElementValue(">");
721 : }
722 : }
723 : }
724 :
725 2476 : m_depthLevel++;
726 : }
727 :
728 : /************************************************************************/
729 : /* endElementCbk() */
730 : /************************************************************************/
731 :
732 2476 : void OGRGPXLayer::endElementCbk(const char *pszName)
733 : {
734 2476 : if (m_bStopParsing)
735 0 : return;
736 :
737 2476 : m_nWithoutEventCounter = 0;
738 :
739 2476 : m_depthLevel--;
740 :
741 2476 : if (m_inInterestingElement)
742 : {
743 544 : if ((m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0) ||
744 506 : (m_gpxGeomType == GPX_ROUTE_POINT &&
745 34 : strcmp(pszName, "rtept") == 0) ||
746 494 : (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0))
747 : {
748 74 : const bool bIsValid = (m_hasFoundLat && m_hasFoundLon);
749 74 : m_inInterestingElement = false;
750 :
751 148 : if (bIsValid &&
752 74 : (m_poFilterGeom == nullptr ||
753 148 : FilterGeometry(m_poFeature->GetGeometryRef())) &&
754 74 : (m_poAttrQuery == nullptr ||
755 0 : m_poAttrQuery->Evaluate(m_poFeature.get())))
756 : {
757 74 : if (auto poGeom = m_poFeature->GetGeometryRef())
758 : {
759 74 : poGeom->assignSpatialReference(m_poSRS);
760 :
761 74 : if (m_bEleAs25D)
762 : {
763 : const int iEleField =
764 2 : m_poFeatureDefn->GetFieldIndex("ele");
765 4 : if (iEleField >= 0 &&
766 2 : m_poFeature->IsFieldSetAndNotNull(iEleField))
767 : {
768 : const double val =
769 1 : m_poFeature->GetFieldAsDouble(iEleField);
770 1 : poGeom->toPoint()->setZ(val);
771 1 : poGeom->setCoordinateDimension(3);
772 : }
773 : }
774 : }
775 :
776 74 : m_oFeatureQueue.push_back(std::move(m_poFeature));
777 : }
778 : else
779 : {
780 0 : m_poFeature.reset();
781 74 : }
782 : }
783 470 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
784 : {
785 15 : m_poFeature->SetGeometryDirectly(m_multiLineString.release());
786 15 : m_lineString.reset();
787 15 : m_multiLineString.reset();
788 :
789 15 : m_inInterestingElement = false;
790 30 : if ((m_poFilterGeom == nullptr ||
791 30 : FilterGeometry(m_poFeature->GetGeometryRef())) &&
792 15 : (m_poAttrQuery == nullptr ||
793 0 : m_poAttrQuery->Evaluate(m_poFeature.get())))
794 : {
795 15 : if (m_poFeature->GetGeometryRef() != nullptr)
796 : {
797 15 : m_poFeature->GetGeometryRef()->assignSpatialReference(
798 15 : m_poSRS);
799 : }
800 :
801 15 : m_oFeatureQueue.push_back(std::move(m_poFeature));
802 : }
803 : else
804 : {
805 0 : m_poFeature.reset();
806 : }
807 : }
808 455 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trkseg") == 0 &&
809 12 : m_depthLevel == m_interestingDepthLevel + 1)
810 : {
811 12 : if (m_multiLineString)
812 : {
813 12 : m_multiLineString->addGeometry(std::move(m_lineString));
814 : }
815 : else
816 : {
817 0 : m_lineString.reset();
818 : }
819 : }
820 443 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
821 : {
822 10 : m_poFeature->SetGeometryDirectly(m_lineString.release());
823 10 : m_lineString.reset();
824 :
825 10 : m_inInterestingElement = false;
826 20 : if ((m_poFilterGeom == nullptr ||
827 20 : FilterGeometry(m_poFeature->GetGeometryRef())) &&
828 10 : (m_poAttrQuery == nullptr ||
829 0 : m_poAttrQuery->Evaluate(m_poFeature.get())))
830 : {
831 10 : if (m_poFeature->GetGeometryRef() != nullptr)
832 : {
833 10 : m_poFeature->GetGeometryRef()->assignSpatialReference(
834 10 : m_poSRS);
835 : }
836 :
837 10 : m_oFeatureQueue.push_back(std::move(m_poFeature));
838 : }
839 : else
840 : {
841 0 : m_poFeature.reset();
842 : }
843 : }
844 30 : else if (m_bEleAs25D && strcmp(pszName, "ele") == 0 &&
845 463 : m_lineString != nullptr &&
846 3 : ((m_gpxGeomType == GPX_ROUTE &&
847 3 : m_depthLevel == m_interestingDepthLevel + 2) ||
848 0 : (m_gpxGeomType == GPX_TRACK &&
849 0 : m_depthLevel == m_interestingDepthLevel + 3)))
850 : {
851 3 : m_lineString->setCoordinateDimension(3);
852 :
853 3 : if (!m_osSubElementValue.empty())
854 : {
855 3 : const double val = CPLAtof(m_osSubElementValue.c_str());
856 3 : const int i = m_lineString->getNumPoints() - 1;
857 3 : if (i >= 0)
858 6 : m_lineString->setPoint(i, m_lineString->getX(i),
859 3 : m_lineString->getY(i), val);
860 : }
861 :
862 3 : m_osSubElementName.clear();
863 3 : m_osSubElementValue.clear();
864 : }
865 430 : else if (m_depthLevel == m_interestingDepthLevel + 1 &&
866 251 : strcmp(pszName, "extensions") == 0)
867 : {
868 20 : m_inExtensions = false;
869 : }
870 999 : else if ((m_depthLevel == m_interestingDepthLevel + 1 ||
871 179 : (m_inExtensions &&
872 26 : m_depthLevel == m_interestingDepthLevel + 2)) &&
873 589 : !m_osSubElementName.empty() && m_osSubElementName == pszName)
874 : {
875 200 : if (m_poFeature && !m_osSubElementValue.empty())
876 : {
877 196 : if (m_osSubElementValue == "time" && m_iCurrentField >= 0 &&
878 0 : m_poFeature->GetFieldDefnRef(m_iCurrentField)->GetType() ==
879 : OFTDateTime)
880 : {
881 : OGRField sField;
882 0 : if (OGRParseXMLDateTime(m_osSubElementValue.c_str(),
883 0 : &sField))
884 : {
885 0 : m_poFeature->SetField(m_iCurrentField, &sField);
886 : }
887 : else
888 : {
889 0 : CPLError(CE_Warning, CPLE_AppDefined,
890 : "Could not parse %s as a valid dateTime",
891 : m_osSubElementValue.c_str());
892 : }
893 : }
894 : else
895 : {
896 196 : m_poFeature->SetField(m_iCurrentField,
897 : m_osSubElementValue.c_str());
898 : }
899 : }
900 200 : if (strcmp(pszName, "link") == 0)
901 0 : m_inLink = false;
902 :
903 200 : m_osSubElementName.clear();
904 200 : m_osSubElementValue.clear();
905 : }
906 210 : else if (m_inLink && m_depthLevel == m_interestingDepthLevel + 2)
907 : {
908 112 : if (m_iCurrentField != -1 && !m_osSubElementName.empty() &&
909 252 : m_osSubElementName == pszName && m_poFeature &&
910 56 : !m_osSubElementValue.empty())
911 : {
912 56 : m_poFeature->SetField(m_iCurrentField,
913 : m_osSubElementValue.c_str());
914 : }
915 :
916 84 : m_osSubElementName.clear();
917 84 : m_osSubElementValue.clear();
918 : }
919 126 : else if (m_inExtensions && m_depthLevel > m_interestingDepthLevel + 2)
920 : {
921 0 : AddStrToSubElementValue(CPLSPrintf("</%s>", pszName));
922 : }
923 : }
924 : }
925 :
926 : /************************************************************************/
927 : /* dataHandlerCbk() */
928 : /************************************************************************/
929 :
930 6437 : void OGRGPXLayer::dataHandlerCbk(const char *data, int nLen)
931 : {
932 6437 : if (m_bStopParsing)
933 0 : return;
934 :
935 6437 : m_nDataHandlerCounter++;
936 6437 : if (m_nDataHandlerCounter >= PARSER_BUF_SIZE)
937 : {
938 0 : CPLError(CE_Failure, CPLE_AppDefined,
939 : "File probably corrupted (million laugh pattern)");
940 0 : XML_StopParser(m_oParser, XML_FALSE);
941 0 : m_bStopParsing = true;
942 0 : return;
943 : }
944 :
945 6437 : m_nWithoutEventCounter = 0;
946 :
947 6437 : if (!m_osSubElementName.empty())
948 : {
949 255 : if (m_inExtensions && m_depthLevel > m_interestingDepthLevel + 2)
950 : {
951 22 : if (data[0] == '\n')
952 0 : return;
953 : }
954 : try
955 : {
956 255 : m_osSubElementValue.append(data, nLen);
957 : }
958 0 : catch (const std::bad_alloc &)
959 : {
960 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
961 : "Out of memory when parsing GPX file");
962 0 : XML_StopParser(m_oParser, XML_FALSE);
963 0 : m_bStopParsing = true;
964 0 : return;
965 : }
966 255 : if (m_osSubElementValue.size() > 100000)
967 : {
968 0 : CPLError(CE_Failure, CPLE_AppDefined,
969 : "Too much data inside one element. "
970 : "File probably corrupted");
971 0 : XML_StopParser(m_oParser, XML_FALSE);
972 0 : m_bStopParsing = true;
973 : }
974 : }
975 : }
976 : #endif
977 :
978 : /************************************************************************/
979 : /* GetNextFeature() */
980 : /************************************************************************/
981 :
982 129 : OGRFeature *OGRGPXLayer::GetNextFeature()
983 : {
984 129 : if (m_bWriteMode)
985 : {
986 6 : CPLError(CE_Failure, CPLE_NotSupported,
987 : "Cannot read features when writing a GPX file");
988 6 : return nullptr;
989 : }
990 :
991 123 : if (m_fpGPX == nullptr)
992 0 : return nullptr;
993 :
994 : #ifdef HAVE_EXPAT
995 :
996 123 : if (m_bStopParsing)
997 0 : return nullptr;
998 :
999 123 : if (!this->m_oFeatureQueue.empty())
1000 : {
1001 45 : OGRFeature *poFeature = std::move(m_oFeatureQueue.front()).release();
1002 45 : m_oFeatureQueue.pop_front();
1003 45 : return poFeature;
1004 : }
1005 :
1006 78 : if (m_fpGPX->Eof() || m_fpGPX->Error())
1007 31 : return nullptr;
1008 :
1009 94 : std::vector<char> aBuf(PARSER_BUF_SIZE);
1010 47 : m_nWithoutEventCounter = 0;
1011 :
1012 47 : int nDone = 0;
1013 0 : do
1014 : {
1015 47 : m_nDataHandlerCounter = 0;
1016 : unsigned int nLen = static_cast<unsigned int>(
1017 47 : m_fpGPX->Read(aBuf.data(), 1, aBuf.size()));
1018 47 : nDone = (nLen < aBuf.size());
1019 47 : if (XML_Parse(m_oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
1020 : {
1021 0 : CPLError(CE_Failure, CPLE_AppDefined,
1022 : "XML parsing of GPX file failed: %s",
1023 0 : OGRExpatGetParseError(m_oParser));
1024 0 : m_bStopParsing = true;
1025 0 : break;
1026 : }
1027 47 : m_nWithoutEventCounter++;
1028 47 : } while (!nDone && this->m_oFeatureQueue.empty() && !m_bStopParsing &&
1029 0 : m_nWithoutEventCounter < 10);
1030 :
1031 47 : if (m_nWithoutEventCounter == 10)
1032 : {
1033 0 : CPLError(CE_Failure, CPLE_AppDefined,
1034 : "Too much data inside one element. File probably corrupted");
1035 0 : m_bStopParsing = true;
1036 : }
1037 :
1038 47 : if (!this->m_oFeatureQueue.empty())
1039 : {
1040 41 : OGRFeature *poFeature = std::move(m_oFeatureQueue.front()).release();
1041 41 : m_oFeatureQueue.pop_front();
1042 41 : return poFeature;
1043 : }
1044 : #endif
1045 6 : return nullptr;
1046 : }
1047 :
1048 : /************************************************************************/
1049 : /* OGRGPX_GetXMLCompatibleTagName() */
1050 : /************************************************************************/
1051 :
1052 8 : static char *OGRGPX_GetXMLCompatibleTagName(const char *pszExtensionsNS,
1053 : const char *pszName)
1054 : {
1055 : /* Skip "ogr_" for example if NS is "ogr". Useful for GPX -> GPX roundtrip
1056 : */
1057 8 : if (strncmp(pszName, pszExtensionsNS, strlen(pszExtensionsNS)) == 0 &&
1058 0 : pszName[strlen(pszExtensionsNS)] == '_')
1059 : {
1060 0 : pszName += strlen(pszExtensionsNS) + 1;
1061 : }
1062 :
1063 8 : char *pszModName = CPLStrdup(pszName);
1064 74 : for (int i = 0; pszModName[i] != 0; i++)
1065 : {
1066 66 : if (pszModName[i] == ' ')
1067 6 : pszModName[i] = '_';
1068 : }
1069 8 : return pszModName;
1070 : }
1071 :
1072 : /************************************************************************/
1073 : /* OGRGPX_GetUTF8String() */
1074 : /************************************************************************/
1075 :
1076 0 : static char *OGRGPX_GetUTF8String(const char *pszString)
1077 : {
1078 0 : char *pszEscaped = nullptr;
1079 0 : if (!CPLIsUTF8(pszString, -1) &&
1080 0 : CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")))
1081 : {
1082 : static bool bFirstTime = true;
1083 0 : if (bFirstTime)
1084 : {
1085 0 : bFirstTime = false;
1086 0 : CPLError(CE_Warning, CPLE_AppDefined,
1087 : "%s is not a valid UTF-8 string. Forcing it to ASCII.\n"
1088 : "If you still want the original string and change the XML "
1089 : "file encoding\n"
1090 : "afterwards, you can define OGR_FORCE_ASCII=NO as "
1091 : "configuration option.\n"
1092 : "This warning won't be issued anymore",
1093 : pszString);
1094 : }
1095 : else
1096 : {
1097 0 : CPLDebug("OGR",
1098 : "%s is not a valid UTF-8 string. Forcing it to ASCII",
1099 : pszString);
1100 : }
1101 0 : pszEscaped = CPLForceToASCII(pszString, -1, '?');
1102 : }
1103 : else
1104 : {
1105 0 : pszEscaped = CPLStrdup(pszString);
1106 : }
1107 :
1108 0 : return pszEscaped;
1109 : }
1110 :
1111 : /************************************************************************/
1112 : /* OGRGPX_WriteXMLExtension() */
1113 : /************************************************************************/
1114 :
1115 0 : bool OGRGPXLayer::OGRGPX_WriteXMLExtension(const char *pszTagName,
1116 : const char *pszContent)
1117 : {
1118 0 : CPLXMLNode *poXML = CPLParseXMLString(pszContent);
1119 0 : if (poXML)
1120 : {
1121 0 : const char *pszUnderscore = strchr(pszTagName, '_');
1122 0 : char *pszTagNameWithNS = CPLStrdup(pszTagName);
1123 0 : if (pszUnderscore)
1124 0 : pszTagNameWithNS[pszUnderscore - pszTagName] = ':';
1125 :
1126 : /* If we detect a Garmin GPX extension, add its xmlns */
1127 0 : const char *pszXMLNS = nullptr;
1128 0 : if (strcmp(pszTagName, "gpxx_WaypointExtension") == 0)
1129 0 : pszXMLNS = " xmlns:gpxx=\"http://www.garmin.com/xmlschemas/"
1130 : "GpxExtensions/v3\"";
1131 :
1132 : /* Don't XML escape here */
1133 0 : char *pszUTF8 = OGRGPX_GetUTF8String(pszContent);
1134 0 : m_poDS->PrintLine(" <%s%s>%s</%s>", pszTagNameWithNS,
1135 : (pszXMLNS) ? pszXMLNS : "", pszUTF8,
1136 : pszTagNameWithNS);
1137 0 : CPLFree(pszUTF8);
1138 :
1139 0 : CPLFree(pszTagNameWithNS);
1140 0 : CPLDestroyXMLNode(poXML);
1141 :
1142 0 : return true;
1143 : }
1144 :
1145 0 : return false;
1146 : }
1147 :
1148 : /************************************************************************/
1149 : /* WriteFeatureAttributes() */
1150 : /************************************************************************/
1151 :
1152 32 : static void AddIdent(VSILFILE *fp, int nIdentLevel)
1153 : {
1154 86 : for (int i = 0; i < nIdentLevel; i++)
1155 54 : VSIFPrintfL(fp, " ");
1156 32 : }
1157 :
1158 39 : void OGRGPXLayer::WriteFeatureAttributes(const OGRFeature *poFeature,
1159 : int nIdentLevel)
1160 : {
1161 39 : VSILFILE *fp = m_poDS->GetOutputFP();
1162 :
1163 : /* Begin with standard GPX fields */
1164 39 : int i = m_iFirstGPXField;
1165 705 : for (; i < m_nGPXFields; i++)
1166 : {
1167 666 : const OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
1168 666 : if (poFeature->IsFieldSetAndNotNull(i))
1169 : {
1170 24 : const char *pszName = poFieldDefn->GetNameRef();
1171 24 : if (strcmp(pszName, "time") == 0)
1172 : {
1173 2 : char *pszDate = OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
1174 2 : AddIdent(fp, nIdentLevel);
1175 2 : m_poDS->PrintLine("<time>%s</time>", pszDate);
1176 2 : CPLFree(pszDate);
1177 : }
1178 22 : else if (STARTS_WITH(pszName, "link"))
1179 : {
1180 6 : if (strstr(pszName, "href"))
1181 : {
1182 2 : AddIdent(fp, nIdentLevel);
1183 2 : VSIFPrintfL(fp, "<link href=\"%s\">",
1184 : poFeature->GetFieldAsString(i));
1185 2 : if (poFeature->IsFieldSetAndNotNull(i + 1))
1186 2 : VSIFPrintfL(fp, "<text>%s</text>",
1187 : poFeature->GetFieldAsString(i + 1));
1188 2 : if (poFeature->IsFieldSetAndNotNull(i + 2))
1189 2 : VSIFPrintfL(fp, "<type>%s</type>",
1190 : poFeature->GetFieldAsString(i + 2));
1191 2 : m_poDS->PrintLine("</link>");
1192 : }
1193 : }
1194 16 : else if (poFieldDefn->GetType() == OFTReal)
1195 : {
1196 : char szValue[64];
1197 4 : OGRFormatDouble(szValue, sizeof(szValue),
1198 : poFeature->GetFieldAsDouble(i), '.');
1199 4 : AddIdent(fp, nIdentLevel);
1200 4 : m_poDS->PrintLine("<%s>%s</%s>", pszName, szValue, pszName);
1201 : }
1202 : else
1203 : {
1204 12 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1205 : poFeature->GetFieldAsString(i));
1206 12 : AddIdent(fp, nIdentLevel);
1207 12 : m_poDS->PrintLine("<%s>%s</%s>", pszName, pszValue, pszName);
1208 12 : CPLFree(pszValue);
1209 : }
1210 : }
1211 : }
1212 :
1213 : /* Write "extra" fields within the <extensions> tag */
1214 39 : const int n = m_poFeatureDefn->GetFieldCount();
1215 39 : if (i < n)
1216 : {
1217 2 : const std::string &osExtensionsNS = m_poDS->GetExtensionsNS();
1218 2 : AddIdent(fp, nIdentLevel);
1219 2 : m_poDS->PrintLine("<extensions>");
1220 10 : for (; i < n; i++)
1221 : {
1222 8 : const OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
1223 8 : if (poFeature->IsFieldSetAndNotNull(i))
1224 : {
1225 8 : char *compatibleName = OGRGPX_GetXMLCompatibleTagName(
1226 : osExtensionsNS.c_str(), poFieldDefn->GetNameRef());
1227 :
1228 8 : if (poFieldDefn->GetType() == OFTReal)
1229 : {
1230 : char szValue[64];
1231 0 : OGRFormatDouble(szValue, sizeof(szValue),
1232 : poFeature->GetFieldAsDouble(i), '.');
1233 0 : AddIdent(fp, nIdentLevel + 1);
1234 0 : m_poDS->PrintLine("<%s:%s>%s</%s:%s>",
1235 : osExtensionsNS.c_str(), compatibleName,
1236 : szValue, osExtensionsNS.c_str(),
1237 : compatibleName);
1238 : }
1239 : else
1240 : {
1241 8 : const char *pszRaw = poFeature->GetFieldAsString(i);
1242 :
1243 : /* Try to detect XML content */
1244 8 : if (pszRaw[0] == '<' && pszRaw[strlen(pszRaw) - 1] == '>')
1245 : {
1246 0 : if (OGRGPX_WriteXMLExtension(compatibleName, pszRaw))
1247 : {
1248 0 : CPLFree(compatibleName);
1249 0 : continue;
1250 : }
1251 : }
1252 :
1253 : /* Try to detect XML escaped content */
1254 8 : else if (STARTS_WITH(pszRaw, "<") &&
1255 0 : STARTS_WITH(pszRaw + strlen(pszRaw) - 4, ">"))
1256 : {
1257 : char *pszUnescapedContent =
1258 0 : CPLUnescapeString(pszRaw, nullptr, CPLES_XML);
1259 :
1260 0 : if (OGRGPX_WriteXMLExtension(compatibleName,
1261 : pszUnescapedContent))
1262 : {
1263 0 : CPLFree(pszUnescapedContent);
1264 0 : CPLFree(compatibleName);
1265 0 : continue;
1266 : }
1267 :
1268 0 : CPLFree(pszUnescapedContent);
1269 : }
1270 :
1271 : /* Remove leading spaces for a numeric field */
1272 8 : if (poFieldDefn->GetType() == OFTInteger)
1273 : {
1274 0 : while (*pszRaw == ' ')
1275 0 : pszRaw++;
1276 : }
1277 :
1278 8 : char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
1279 8 : AddIdent(fp, nIdentLevel + 1);
1280 8 : m_poDS->PrintLine("<%s:%s>%s</%s:%s>",
1281 : osExtensionsNS.c_str(), compatibleName,
1282 : pszEscaped, osExtensionsNS.c_str(),
1283 : compatibleName);
1284 8 : CPLFree(pszEscaped);
1285 : }
1286 8 : CPLFree(compatibleName);
1287 : }
1288 : }
1289 2 : AddIdent(fp, nIdentLevel);
1290 2 : m_poDS->PrintLine("</extensions>");
1291 : }
1292 39 : }
1293 :
1294 : /************************************************************************/
1295 : /* CheckAndFixCoordinatesValidity() */
1296 : /************************************************************************/
1297 :
1298 41 : OGRErr OGRGPXLayer::CheckAndFixCoordinatesValidity(double *pdfLatitude,
1299 : double *pdfLongitude)
1300 : {
1301 41 : if (pdfLatitude != nullptr && (*pdfLatitude < -90 || *pdfLatitude > 90))
1302 : {
1303 : static bool bFirstWarning = true;
1304 0 : if (bFirstWarning)
1305 : {
1306 0 : bFirstWarning = false;
1307 0 : CPLError(CE_Failure, CPLE_AppDefined,
1308 : "Latitude %f is invalid. Valid range is [-90,90]. "
1309 : "This warning will not be issued any more",
1310 : *pdfLatitude);
1311 : }
1312 0 : return OGRERR_FAILURE;
1313 : }
1314 :
1315 41 : if (pdfLongitude != nullptr &&
1316 41 : (*pdfLongitude < -180 || *pdfLongitude > 180))
1317 : {
1318 : static bool bFirstWarning = true;
1319 0 : if (bFirstWarning)
1320 : {
1321 0 : bFirstWarning = false;
1322 0 : CPLError(CE_Warning, CPLE_AppDefined,
1323 : "Longitude %f has been modified to fit into "
1324 : "range [-180,180]. This warning will not be "
1325 : "issued any more",
1326 : *pdfLongitude);
1327 : }
1328 :
1329 0 : *pdfLongitude = fmod(*pdfLongitude + 180.0, 360.0) - 180.0;
1330 0 : return OGRERR_NONE;
1331 : }
1332 :
1333 41 : return OGRERR_NONE;
1334 : }
1335 :
1336 : /************************************************************************/
1337 : /* ICreateFeature() */
1338 : /************************************************************************/
1339 :
1340 49 : OGRErr OGRGPXLayer::ICreateFeature(OGRFeature *poFeature)
1341 :
1342 : {
1343 49 : VSILFILE *fp = m_poDS->GetOutputFP();
1344 49 : if (fp == nullptr)
1345 0 : return OGRERR_FAILURE;
1346 :
1347 : char szLat[64];
1348 : char szLon[64];
1349 : char szAlt[64];
1350 :
1351 49 : const OGRGeometry *poGeom = poFeature->GetGeometryRef();
1352 :
1353 49 : if (m_gpxGeomType == GPX_WPT)
1354 : {
1355 14 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE)
1356 : {
1357 0 : CPLError(CE_Failure, CPLE_NotSupported,
1358 : "Cannot write a 'wpt' element after a 'rte' element.");
1359 6 : return OGRERR_FAILURE;
1360 : }
1361 14 : else if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK)
1362 : {
1363 0 : CPLError(CE_Failure, CPLE_NotSupported,
1364 : "Cannot write a 'wpt' element after a 'trk' element.");
1365 0 : return OGRERR_FAILURE;
1366 : }
1367 :
1368 14 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1369 :
1370 24 : if (poGeom == nullptr ||
1371 10 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1372 : {
1373 6 : CPLError(
1374 : CE_Failure, CPLE_AppDefined,
1375 : "Features without geometry or with non-ponctual geometries not "
1376 : "supported by GPX writer in waypoints layer.");
1377 6 : return OGRERR_FAILURE;
1378 : }
1379 :
1380 8 : if (poGeom->getCoordinateDimension() == 0)
1381 : {
1382 0 : CPLError(CE_Failure, CPLE_AppDefined,
1383 : "POINT EMPTY geometries not supported by GPX writer.");
1384 0 : return OGRERR_FAILURE;
1385 : }
1386 :
1387 8 : const OGRPoint *point = poGeom->toPoint();
1388 8 : double lat = point->getY();
1389 8 : double lon = point->getX();
1390 8 : CheckAndFixCoordinatesValidity(&lat, &lon);
1391 8 : m_poDS->AddCoord(lon, lat);
1392 8 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1393 8 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1394 8 : m_poDS->PrintLine("<wpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
1395 8 : WriteFeatureAttributes(poFeature);
1396 8 : m_poDS->PrintLine("</wpt>");
1397 : }
1398 35 : else if (m_gpxGeomType == GPX_ROUTE)
1399 : {
1400 24 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
1401 12 : m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
1402 : {
1403 0 : CPLError(CE_Failure, CPLE_NotSupported,
1404 : "Cannot write a 'rte' element after a 'trk' element.");
1405 0 : return OGRERR_FAILURE;
1406 : }
1407 :
1408 12 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1409 0 : m_poDS->m_nLastRteId != -1)
1410 : {
1411 0 : m_poDS->PrintLine("</rte>");
1412 0 : m_poDS->m_nLastRteId = -1;
1413 : }
1414 :
1415 12 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1416 :
1417 12 : const OGRLineString *line = nullptr;
1418 :
1419 12 : if (poGeom == nullptr)
1420 : {
1421 4 : m_poDS->PrintLine("<rte>");
1422 4 : WriteFeatureAttributes(poFeature);
1423 4 : m_poDS->PrintLine("</rte>");
1424 4 : return OGRERR_NONE;
1425 : }
1426 :
1427 8 : switch (poGeom->getGeometryType())
1428 : {
1429 6 : case wkbLineString:
1430 : case wkbLineString25D:
1431 : {
1432 6 : line = poGeom->toLineString();
1433 6 : break;
1434 : }
1435 :
1436 0 : case wkbMultiLineString:
1437 : case wkbMultiLineString25D:
1438 : {
1439 : int nGeometries =
1440 0 : poGeom->toMultiLineString()->getNumGeometries();
1441 0 : if (nGeometries == 0)
1442 : {
1443 0 : line = nullptr;
1444 : }
1445 0 : else if (nGeometries == 1)
1446 : {
1447 0 : line = poGeom->toMultiLineString()->getGeometryRef(0);
1448 : }
1449 : else
1450 : {
1451 0 : CPLError(CE_Failure, CPLE_NotSupported,
1452 : "Multiline with more than one line is not "
1453 : "supported for 'rte' element.");
1454 0 : return OGRERR_FAILURE;
1455 : }
1456 0 : break;
1457 : }
1458 :
1459 2 : default:
1460 : {
1461 2 : CPLError(
1462 : CE_Failure, CPLE_NotSupported,
1463 : "Geometry type of `%s' not supported for 'rte' element.\n",
1464 2 : OGRGeometryTypeToName(poGeom->getGeometryType()));
1465 2 : return OGRERR_FAILURE;
1466 : }
1467 : }
1468 :
1469 6 : m_poDS->PrintLine("<rte>");
1470 6 : WriteFeatureAttributes(poFeature);
1471 6 : if (line)
1472 : {
1473 6 : const int n = line->getNumPoints();
1474 17 : for (int i = 0; i < n; i++)
1475 : {
1476 11 : double lat = line->getY(i);
1477 11 : double lon = line->getX(i);
1478 11 : CheckAndFixCoordinatesValidity(&lat, &lon);
1479 11 : m_poDS->AddCoord(lon, lat);
1480 11 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1481 11 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1482 11 : m_poDS->PrintLine(" <rtept lat=\"%s\" lon=\"%s\">", szLat,
1483 : szLon);
1484 18 : if (poGeom->getGeometryType() == wkbLineString25D ||
1485 7 : poGeom->getGeometryType() == wkbMultiLineString25D)
1486 : {
1487 4 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i), '.');
1488 4 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1489 : }
1490 11 : m_poDS->PrintLine(" </rtept>");
1491 : }
1492 : }
1493 6 : m_poDS->PrintLine("</rte>");
1494 : }
1495 23 : else if (m_gpxGeomType == GPX_TRACK)
1496 : {
1497 13 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1498 0 : m_poDS->m_nLastRteId != -1)
1499 : {
1500 0 : m_poDS->PrintLine("</rte>");
1501 0 : m_poDS->m_nLastRteId = -1;
1502 : }
1503 13 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT &&
1504 0 : m_poDS->m_nLastTrkId != -1)
1505 : {
1506 0 : m_poDS->PrintLine(" </trkseg>");
1507 0 : m_poDS->PrintLine("</trk>");
1508 0 : m_poDS->m_nLastTrkId = -1;
1509 0 : m_poDS->m_nLastTrkSegId = -1;
1510 : }
1511 :
1512 13 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1513 :
1514 13 : if (poGeom == nullptr)
1515 : {
1516 4 : m_poDS->PrintLine("<trk>");
1517 4 : WriteFeatureAttributes(poFeature);
1518 4 : m_poDS->PrintLine("</trk>");
1519 4 : return OGRERR_NONE;
1520 : }
1521 :
1522 9 : switch (poGeom->getGeometryType())
1523 : {
1524 0 : case wkbLineString:
1525 : case wkbLineString25D:
1526 : {
1527 0 : const OGRLineString *line = poGeom->toLineString();
1528 0 : const int n = line->getNumPoints();
1529 0 : m_poDS->PrintLine("<trk>");
1530 0 : WriteFeatureAttributes(poFeature);
1531 0 : m_poDS->PrintLine(" <trkseg>");
1532 0 : for (int i = 0; i < n; i++)
1533 : {
1534 0 : double lat = line->getY(i);
1535 0 : double lon = line->getX(i);
1536 0 : CheckAndFixCoordinatesValidity(&lat, &lon);
1537 0 : m_poDS->AddCoord(lon, lat);
1538 0 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1539 0 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1540 0 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">",
1541 : szLat, szLon);
1542 0 : if (line->getGeometryType() == wkbLineString25D)
1543 : {
1544 0 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
1545 : '.');
1546 0 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1547 : }
1548 0 : m_poDS->PrintLine(" </trkpt>");
1549 : }
1550 0 : m_poDS->PrintLine(" </trkseg>");
1551 0 : m_poDS->PrintLine("</trk>");
1552 0 : break;
1553 : }
1554 :
1555 7 : case wkbMultiLineString:
1556 : case wkbMultiLineString25D:
1557 : {
1558 7 : m_poDS->PrintLine("<trk>");
1559 7 : WriteFeatureAttributes(poFeature);
1560 14 : for (auto &&line : poGeom->toMultiLineString())
1561 : {
1562 7 : const int n = (line) ? line->getNumPoints() : 0;
1563 7 : m_poDS->PrintLine(" <trkseg>");
1564 19 : for (int i = 0; i < n; i++)
1565 : {
1566 12 : double lat = line->getY(i);
1567 12 : double lon = line->getX(i);
1568 12 : CheckAndFixCoordinatesValidity(&lat, &lon);
1569 12 : m_poDS->AddCoord(lon, lat);
1570 12 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1571 12 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1572 12 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">",
1573 : szLat, szLon);
1574 12 : if (line->getGeometryType() == wkbLineString25D)
1575 : {
1576 4 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
1577 : '.');
1578 4 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1579 : }
1580 12 : m_poDS->PrintLine(" </trkpt>");
1581 : }
1582 7 : m_poDS->PrintLine(" </trkseg>");
1583 : }
1584 7 : m_poDS->PrintLine("</trk>");
1585 7 : break;
1586 : }
1587 :
1588 2 : default:
1589 : {
1590 2 : CPLError(
1591 : CE_Failure, CPLE_NotSupported,
1592 : "Geometry type of `%s' not supported for 'trk' element.\n",
1593 2 : OGRGeometryTypeToName(poGeom->getGeometryType()));
1594 2 : return OGRERR_FAILURE;
1595 : }
1596 : }
1597 : }
1598 10 : else if (m_gpxGeomType == GPX_ROUTE_POINT)
1599 : {
1600 10 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
1601 5 : m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
1602 : {
1603 0 : CPLError(CE_Failure, CPLE_NotSupported,
1604 : "Cannot write a 'rte' element after a 'trk' element.");
1605 0 : return OGRERR_FAILURE;
1606 : }
1607 :
1608 10 : if (poGeom == nullptr ||
1609 5 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1610 : {
1611 0 : CPLError(
1612 : CE_Failure, CPLE_AppDefined,
1613 : "Features without geometry or with non-ponctual geometries not "
1614 : "supported by GPX writer in route_points layer.");
1615 0 : return OGRERR_FAILURE;
1616 : }
1617 :
1618 5 : if (poGeom->getCoordinateDimension() == 0)
1619 : {
1620 0 : CPLError(CE_Failure, CPLE_AppDefined,
1621 : "POINT EMPTY geometries not supported by GPX writer.");
1622 0 : return OGRERR_FAILURE;
1623 : }
1624 :
1625 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_ROUTE_FID))
1626 : {
1627 0 : CPLError(
1628 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1629 0 : m_poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
1630 0 : return OGRERR_FAILURE;
1631 : }
1632 5 : if (poFeature->GetFieldAsInteger(FLD_ROUTE_FID) < 0)
1633 : {
1634 0 : CPLError(
1635 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1636 0 : m_poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
1637 0 : return OGRERR_FAILURE;
1638 : }
1639 :
1640 5 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1641 :
1642 5 : if (m_poDS->m_nLastRteId != poFeature->GetFieldAsInteger(FLD_ROUTE_FID))
1643 : {
1644 3 : if (m_poDS->m_nLastRteId != -1)
1645 : {
1646 1 : m_poDS->PrintLine("</rte>");
1647 : }
1648 3 : m_poDS->PrintLine("<rte>");
1649 3 : if (poFeature->IsFieldSetAndNotNull(FLD_ROUTE_NAME))
1650 : {
1651 3 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1652 : poFeature->GetFieldAsString(FLD_ROUTE_NAME));
1653 3 : m_poDS->PrintLine(" <%s>%s</%s>", "name", pszValue, "name");
1654 3 : CPLFree(pszValue);
1655 : }
1656 : }
1657 :
1658 5 : m_poDS->m_nLastRteId = poFeature->GetFieldAsInteger(FLD_ROUTE_FID);
1659 :
1660 5 : const OGRPoint *point = poGeom->toPoint();
1661 5 : double lat = point->getY();
1662 5 : double lon = point->getX();
1663 5 : CheckAndFixCoordinatesValidity(&lat, &lon);
1664 5 : m_poDS->AddCoord(lon, lat);
1665 5 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1666 5 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1667 5 : m_poDS->PrintLine(" <rtept lat=\"%s\" lon=\"%s\">", szLat, szLon);
1668 5 : WriteFeatureAttributes(poFeature, 2);
1669 5 : m_poDS->PrintLine(" </rtept>");
1670 : }
1671 : else
1672 : {
1673 6 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1674 1 : m_poDS->m_nLastRteId != -1)
1675 : {
1676 1 : m_poDS->PrintLine("</rte>");
1677 1 : m_poDS->m_nLastRteId = -1;
1678 : }
1679 :
1680 10 : if (poGeom == nullptr ||
1681 5 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1682 : {
1683 0 : CPLError(
1684 : CE_Failure, CPLE_AppDefined,
1685 : "Features without geometry or with non-ponctual geometries not "
1686 : "supported by GPX writer in track_points layer.");
1687 0 : return OGRERR_FAILURE;
1688 : }
1689 :
1690 5 : if (poGeom->getCoordinateDimension() == 0)
1691 : {
1692 0 : CPLError(CE_Failure, CPLE_AppDefined,
1693 : "POINT EMPTY geometries not supported by GPX writer.");
1694 0 : return OGRERR_FAILURE;
1695 : }
1696 :
1697 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_TRACK_FID))
1698 : {
1699 0 : CPLError(
1700 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1701 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
1702 0 : return OGRERR_FAILURE;
1703 : }
1704 5 : if (poFeature->GetFieldAsInteger(FLD_TRACK_FID) < 0)
1705 : {
1706 0 : CPLError(
1707 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1708 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
1709 0 : return OGRERR_FAILURE;
1710 : }
1711 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_TRACK_SEG_ID))
1712 : {
1713 0 : CPLError(
1714 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1715 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
1716 0 : return OGRERR_FAILURE;
1717 : }
1718 5 : if (poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID) < 0)
1719 : {
1720 0 : CPLError(
1721 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1722 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
1723 0 : return OGRERR_FAILURE;
1724 : }
1725 :
1726 5 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1727 :
1728 5 : if (m_poDS->m_nLastTrkId != poFeature->GetFieldAsInteger(FLD_TRACK_FID))
1729 : {
1730 3 : if (m_poDS->m_nLastTrkId != -1)
1731 : {
1732 1 : m_poDS->PrintLine(" </trkseg>");
1733 1 : m_poDS->PrintLine("</trk>");
1734 : }
1735 3 : m_poDS->PrintLine("<trk>");
1736 :
1737 3 : if (poFeature->IsFieldSetAndNotNull(FLD_TRACK_NAME))
1738 : {
1739 3 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1740 : poFeature->GetFieldAsString(FLD_TRACK_NAME));
1741 3 : m_poDS->PrintLine(" <%s>%s</%s>", "name", pszValue, "name");
1742 3 : CPLFree(pszValue);
1743 : }
1744 :
1745 3 : m_poDS->PrintLine(" <trkseg>");
1746 : }
1747 4 : else if (m_poDS->m_nLastTrkSegId !=
1748 2 : poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID))
1749 : {
1750 1 : m_poDS->PrintLine(" </trkseg>");
1751 1 : m_poDS->PrintLine(" <trkseg>");
1752 : }
1753 :
1754 5 : m_poDS->m_nLastTrkId = poFeature->GetFieldAsInteger(FLD_TRACK_FID);
1755 10 : m_poDS->m_nLastTrkSegId =
1756 5 : poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID);
1757 :
1758 5 : const OGRPoint *point = poGeom->toPoint();
1759 5 : double lat = point->getY();
1760 5 : double lon = point->getX();
1761 5 : CheckAndFixCoordinatesValidity(&lat, &lon);
1762 5 : m_poDS->AddCoord(lon, lat);
1763 5 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1764 5 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1765 5 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
1766 5 : WriteFeatureAttributes(poFeature, 3);
1767 5 : m_poDS->PrintLine(" </trkpt>");
1768 : }
1769 :
1770 31 : return OGRERR_NONE;
1771 : }
1772 :
1773 : /************************************************************************/
1774 : /* CreateField() */
1775 : /************************************************************************/
1776 :
1777 40 : OGRErr OGRGPXLayer::CreateField(const OGRFieldDefn *poField, int /*bApproxOK*/)
1778 : {
1779 702 : for (int iField = 0; iField < m_poFeatureDefn->GetFieldCount(); iField++)
1780 : {
1781 662 : if (strcmp(m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1782 662 : poField->GetNameRef()) == 0)
1783 : {
1784 0 : return OGRERR_NONE;
1785 : }
1786 : }
1787 40 : if (!m_poDS->GetUseExtensions())
1788 : {
1789 36 : CPLError(CE_Failure, CPLE_NotSupported,
1790 : "Field of name '%s' is not supported in GPX schema. "
1791 : "Use GPX_USE_EXTENSIONS creation option to allow use of the "
1792 : "<extensions> element.",
1793 : poField->GetNameRef());
1794 36 : return OGRERR_FAILURE;
1795 : }
1796 : else
1797 : {
1798 4 : m_poFeatureDefn->AddFieldDefn(poField);
1799 4 : return OGRERR_NONE;
1800 : }
1801 : }
1802 :
1803 : /************************************************************************/
1804 : /* TestCapability() */
1805 : /************************************************************************/
1806 :
1807 60 : bool OGRGPXLayer::TestCapability(const char *pszCap) const
1808 :
1809 : {
1810 60 : if (EQUAL(pszCap, OLCSequentialWrite))
1811 6 : return m_bWriteMode;
1812 54 : else if (EQUAL(pszCap, OLCCreateField))
1813 6 : return m_bWriteMode;
1814 48 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
1815 0 : return TRUE;
1816 48 : else if (EQUAL(pszCap, OLCZGeometries))
1817 1 : return TRUE;
1818 :
1819 : else
1820 47 : return FALSE;
1821 : }
1822 :
1823 : /************************************************************************/
1824 : /* LoadExtensionsSchema() */
1825 : /************************************************************************/
1826 :
1827 : #ifdef HAVE_EXPAT
1828 :
1829 4730 : static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
1830 : const char *pszName,
1831 : const char **ppszAttr)
1832 : {
1833 4730 : static_cast<OGRGPXLayer *>(pUserData)->startElementLoadSchemaCbk(pszName,
1834 : ppszAttr);
1835 4730 : }
1836 :
1837 4730 : static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
1838 : const char *pszName)
1839 : {
1840 4730 : static_cast<OGRGPXLayer *>(pUserData)->endElementLoadSchemaCbk(pszName);
1841 4730 : }
1842 :
1843 11710 : static void XMLCALL dataHandlerLoadSchemaCbk(void *pUserData, const char *data,
1844 : int nLen)
1845 : {
1846 11710 : static_cast<OGRGPXLayer *>(pUserData)->dataHandlerLoadSchemaCbk(data, nLen);
1847 11710 : }
1848 :
1849 : /** This function parses the whole file to detect the extensions fields */
1850 75 : void OGRGPXLayer::LoadExtensionsSchema()
1851 : {
1852 75 : m_oSchemaParser = OGRCreateExpatXMLParser();
1853 75 : XML_SetElementHandler(m_oSchemaParser, ::startElementLoadSchemaCbk,
1854 : ::endElementLoadSchemaCbk);
1855 75 : XML_SetCharacterDataHandler(m_oSchemaParser, ::dataHandlerLoadSchemaCbk);
1856 75 : XML_SetUserData(m_oSchemaParser, this);
1857 :
1858 75 : m_fpGPX->Seek(0, SEEK_SET);
1859 :
1860 75 : m_inInterestingElement = false;
1861 75 : m_inExtensions = false;
1862 75 : m_depthLevel = 0;
1863 75 : m_currentFieldDefn = nullptr;
1864 75 : m_osSubElementName.clear();
1865 75 : m_osSubElementValue.clear();
1866 75 : m_nWithoutEventCounter = 0;
1867 75 : m_bStopParsing = false;
1868 :
1869 75 : std::vector<char> aBuf(PARSER_BUF_SIZE);
1870 75 : int nDone = 0;
1871 0 : do
1872 : {
1873 75 : m_nDataHandlerCounter = 0;
1874 : unsigned int nLen = static_cast<unsigned int>(
1875 75 : m_fpGPX->Read(aBuf.data(), 1, aBuf.size()));
1876 75 : nDone = (nLen < aBuf.size());
1877 75 : if (XML_Parse(m_oSchemaParser, aBuf.data(), nLen, nDone) ==
1878 : XML_STATUS_ERROR)
1879 : {
1880 0 : CPLError(CE_Failure, CPLE_AppDefined,
1881 : "XML parsing of GPX file failed: %s",
1882 0 : OGRExpatGetParseError(m_oSchemaParser));
1883 0 : m_bStopParsing = true;
1884 0 : break;
1885 : }
1886 75 : m_nWithoutEventCounter++;
1887 75 : } while (!nDone && !m_bStopParsing && m_nWithoutEventCounter < 10);
1888 :
1889 75 : if (m_nWithoutEventCounter == 10)
1890 : {
1891 0 : CPLError(CE_Failure, CPLE_AppDefined,
1892 : "Too much data inside one element. File probably corrupted");
1893 0 : m_bStopParsing = true;
1894 : }
1895 :
1896 75 : XML_ParserFree(m_oSchemaParser);
1897 75 : m_oSchemaParser = nullptr;
1898 :
1899 75 : m_fpGPX->Seek(0, SEEK_SET);
1900 75 : }
1901 :
1902 : /************************************************************************/
1903 : /* startElementLoadSchemaCbk() */
1904 : /************************************************************************/
1905 :
1906 4730 : void OGRGPXLayer::startElementLoadSchemaCbk(const char *pszName,
1907 : CPL_UNUSED const char **ppszAttr)
1908 : {
1909 4730 : if (m_bStopParsing)
1910 0 : return;
1911 :
1912 4730 : m_nWithoutEventCounter = 0;
1913 :
1914 4730 : if (m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
1915 : {
1916 28 : m_inInterestingElement = true;
1917 28 : m_inExtensions = false;
1918 28 : m_interestingDepthLevel = m_depthLevel;
1919 : }
1920 4702 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
1921 : {
1922 40 : m_inInterestingElement = true;
1923 40 : m_inExtensions = false;
1924 40 : m_interestingDepthLevel = m_depthLevel;
1925 : }
1926 4662 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
1927 : {
1928 26 : m_inInterestingElement = true;
1929 26 : m_inExtensions = false;
1930 26 : m_interestingDepthLevel = m_depthLevel;
1931 : }
1932 4636 : else if (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0)
1933 : {
1934 55 : m_inInterestingElement = true;
1935 55 : m_inExtensions = false;
1936 55 : m_interestingDepthLevel = m_depthLevel;
1937 : }
1938 4581 : else if (m_gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0)
1939 : {
1940 39 : m_inInterestingElement = true;
1941 39 : m_inExtensions = false;
1942 39 : m_interestingDepthLevel = m_depthLevel;
1943 : }
1944 4542 : else if (m_inInterestingElement)
1945 : {
1946 693 : if (m_depthLevel == m_interestingDepthLevel + 1 &&
1947 414 : strcmp(pszName, "extensions") == 0)
1948 : {
1949 16 : m_inExtensions = true;
1950 16 : m_extensionsDepthLevel = m_depthLevel;
1951 : }
1952 677 : else if (m_inExtensions && m_depthLevel == m_extensionsDepthLevel + 1)
1953 : {
1954 10 : m_osSubElementName = pszName;
1955 :
1956 10 : int iField = 0; // Used after for.
1957 231 : for (; iField < m_poFeatureDefn->GetFieldCount(); iField++)
1958 : {
1959 225 : bool bMatch = false;
1960 225 : if (iField >= m_nGPXFields)
1961 : {
1962 : char *pszCompatibleName =
1963 17 : OGRGPX_GetOGRCompatibleTagName(pszName);
1964 17 : bMatch =
1965 17 : strcmp(
1966 17 : m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1967 : pszCompatibleName) == 0;
1968 17 : CPLFree(pszCompatibleName);
1969 : }
1970 : else
1971 : {
1972 208 : bMatch =
1973 208 : strcmp(
1974 208 : m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1975 : pszName) == 0;
1976 : }
1977 :
1978 225 : if (bMatch)
1979 : {
1980 4 : m_currentFieldDefn = m_poFeatureDefn->GetFieldDefn(iField);
1981 4 : break;
1982 : }
1983 : }
1984 10 : if (iField == m_poFeatureDefn->GetFieldCount())
1985 : {
1986 : char *pszCompatibleName =
1987 6 : OGRGPX_GetOGRCompatibleTagName(pszName);
1988 12 : OGRFieldDefn newFieldDefn(pszCompatibleName, OFTInteger);
1989 6 : CPLFree(pszCompatibleName);
1990 :
1991 6 : m_poFeatureDefn->AddFieldDefn(&newFieldDefn);
1992 18 : m_currentFieldDefn = m_poFeatureDefn->GetFieldDefn(
1993 6 : m_poFeatureDefn->GetFieldCount() - 1);
1994 :
1995 6 : if (m_poFeatureDefn->GetFieldCount() == 100)
1996 : {
1997 0 : CPLError(CE_Failure, CPLE_AppDefined,
1998 : "Too many fields. File probably corrupted");
1999 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2000 0 : m_bStopParsing = true;
2001 : }
2002 : }
2003 : }
2004 : }
2005 :
2006 4730 : m_depthLevel++;
2007 : }
2008 :
2009 : /************************************************************************/
2010 : /* endElementLoadSchemaCbk() */
2011 : /************************************************************************/
2012 :
2013 0 : static bool OGRGPXIsInt(const char *pszStr)
2014 : {
2015 0 : while (*pszStr == ' ')
2016 0 : pszStr++;
2017 :
2018 0 : for (int i = 0; pszStr[i]; i++)
2019 : {
2020 0 : if (pszStr[i] == '+' || pszStr[i] == '-')
2021 : {
2022 0 : if (i != 0)
2023 0 : return false;
2024 : }
2025 0 : else if (!(pszStr[i] >= '0' && pszStr[i] <= '9'))
2026 0 : return false;
2027 : }
2028 0 : return true;
2029 : }
2030 :
2031 4730 : void OGRGPXLayer::endElementLoadSchemaCbk(const char *pszName)
2032 : {
2033 4730 : if (m_bStopParsing)
2034 0 : return;
2035 :
2036 4730 : m_nWithoutEventCounter = 0;
2037 :
2038 4730 : m_depthLevel--;
2039 :
2040 4730 : if (m_inInterestingElement)
2041 : {
2042 881 : if (m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
2043 : {
2044 28 : m_inInterestingElement = false;
2045 28 : m_inExtensions = false;
2046 : }
2047 853 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
2048 : {
2049 40 : m_inInterestingElement = false;
2050 40 : m_inExtensions = false;
2051 : }
2052 813 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
2053 : {
2054 26 : m_inInterestingElement = false;
2055 26 : m_inExtensions = false;
2056 : }
2057 787 : else if (m_gpxGeomType == GPX_TRACK_POINT &&
2058 126 : strcmp(pszName, "trkpt") == 0)
2059 : {
2060 55 : m_inInterestingElement = false;
2061 55 : m_inExtensions = false;
2062 : }
2063 732 : else if (m_gpxGeomType == GPX_ROUTE_POINT &&
2064 104 : strcmp(pszName, "rtept") == 0)
2065 : {
2066 39 : m_inInterestingElement = false;
2067 39 : m_inExtensions = false;
2068 : }
2069 693 : else if (m_depthLevel == m_interestingDepthLevel + 1 &&
2070 414 : strcmp(pszName, "extensions") == 0)
2071 : {
2072 16 : m_inExtensions = false;
2073 : }
2074 10 : else if (m_inExtensions && m_depthLevel == m_extensionsDepthLevel + 1 &&
2075 687 : !m_osSubElementName.empty() && m_osSubElementName == pszName)
2076 : {
2077 10 : if (!m_osSubElementValue.empty() && m_currentFieldDefn)
2078 : {
2079 11 : if (m_currentFieldDefn->GetType() == OFTInteger ||
2080 3 : m_currentFieldDefn->GetType() == OFTReal)
2081 : {
2082 5 : char *pszRemainingStr = nullptr;
2083 5 : CPLStrtod(m_osSubElementValue.c_str(), &pszRemainingStr);
2084 5 : if (pszRemainingStr == nullptr || *pszRemainingStr == 0 ||
2085 5 : *pszRemainingStr == ' ')
2086 : {
2087 0 : if (m_currentFieldDefn->GetType() == OFTInteger)
2088 : {
2089 0 : if (!OGRGPXIsInt(m_osSubElementValue.c_str()))
2090 : {
2091 0 : m_currentFieldDefn->SetType(OFTReal);
2092 : }
2093 : }
2094 : }
2095 : else
2096 : {
2097 5 : m_currentFieldDefn->SetType(OFTString);
2098 : }
2099 : }
2100 : }
2101 :
2102 10 : m_osSubElementName.clear();
2103 10 : m_osSubElementValue.clear();
2104 10 : m_currentFieldDefn = nullptr;
2105 : }
2106 : }
2107 : }
2108 :
2109 : /************************************************************************/
2110 : /* dataHandlerLoadSchemaCbk() */
2111 : /************************************************************************/
2112 :
2113 11710 : void OGRGPXLayer::dataHandlerLoadSchemaCbk(const char *data, int nLen)
2114 : {
2115 11710 : if (m_bStopParsing)
2116 0 : return;
2117 :
2118 11710 : m_nDataHandlerCounter++;
2119 11710 : if (m_nDataHandlerCounter >= PARSER_BUF_SIZE)
2120 : {
2121 0 : CPLError(CE_Failure, CPLE_AppDefined,
2122 : "File probably corrupted (million laugh pattern)");
2123 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2124 0 : m_bStopParsing = true;
2125 0 : return;
2126 : }
2127 :
2128 11710 : m_nWithoutEventCounter = 0;
2129 :
2130 11710 : if (!m_osSubElementName.empty())
2131 : {
2132 : try
2133 : {
2134 8 : m_osSubElementValue.append(data, nLen);
2135 : }
2136 0 : catch (const std::bad_alloc &)
2137 : {
2138 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2139 : "Out of memory when parsing GPX file");
2140 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2141 0 : m_bStopParsing = true;
2142 0 : return;
2143 : }
2144 8 : if (m_osSubElementValue.size() > 100000)
2145 : {
2146 0 : CPLError(CE_Failure, CPLE_AppDefined,
2147 : "Too much data inside one element. "
2148 : "File probably corrupted");
2149 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2150 0 : m_bStopParsing = true;
2151 : }
2152 : }
2153 : }
2154 : #else
2155 : void OGRGPXLayer::LoadExtensionsSchema()
2156 : {
2157 : }
2158 : #endif
2159 :
2160 : /************************************************************************/
2161 : /* GetDataset() */
2162 : /************************************************************************/
2163 :
2164 7 : GDALDataset *OGRGPXLayer::GetDataset()
2165 : {
2166 7 : return m_poDS;
2167 : }
|