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 (!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 : "
1023 : "%s at line %d, column %d",
1024 : XML_ErrorString(XML_GetErrorCode(m_oParser)),
1025 0 : static_cast<int>(XML_GetCurrentLineNumber(m_oParser)),
1026 0 : static_cast<int>(XML_GetCurrentColumnNumber(m_oParser)));
1027 0 : m_bStopParsing = true;
1028 0 : break;
1029 : }
1030 47 : m_nWithoutEventCounter++;
1031 47 : } while (!nDone && m_oFeatureQueue.empty() && !m_bStopParsing &&
1032 0 : m_nWithoutEventCounter < 10);
1033 :
1034 47 : if (m_nWithoutEventCounter == 10)
1035 : {
1036 0 : CPLError(CE_Failure, CPLE_AppDefined,
1037 : "Too much data inside one element. File probably corrupted");
1038 0 : m_bStopParsing = true;
1039 : }
1040 :
1041 47 : if (!m_oFeatureQueue.empty())
1042 : {
1043 41 : OGRFeature *poFeature = std::move(m_oFeatureQueue.front()).release();
1044 41 : m_oFeatureQueue.pop_front();
1045 41 : return poFeature;
1046 : }
1047 : #endif
1048 6 : return nullptr;
1049 : }
1050 :
1051 : /************************************************************************/
1052 : /* OGRGPX_GetXMLCompatibleTagName() */
1053 : /************************************************************************/
1054 :
1055 8 : static char *OGRGPX_GetXMLCompatibleTagName(const char *pszExtensionsNS,
1056 : const char *pszName)
1057 : {
1058 : /* Skip "ogr_" for example if NS is "ogr". Useful for GPX -> GPX roundtrip
1059 : */
1060 8 : if (strncmp(pszName, pszExtensionsNS, strlen(pszExtensionsNS)) == 0 &&
1061 0 : pszName[strlen(pszExtensionsNS)] == '_')
1062 : {
1063 0 : pszName += strlen(pszExtensionsNS) + 1;
1064 : }
1065 :
1066 8 : char *pszModName = CPLStrdup(pszName);
1067 74 : for (int i = 0; pszModName[i] != 0; i++)
1068 : {
1069 66 : if (pszModName[i] == ' ')
1070 6 : pszModName[i] = '_';
1071 : }
1072 8 : return pszModName;
1073 : }
1074 :
1075 : /************************************************************************/
1076 : /* OGRGPX_GetUTF8String() */
1077 : /************************************************************************/
1078 :
1079 0 : static char *OGRGPX_GetUTF8String(const char *pszString)
1080 : {
1081 0 : char *pszEscaped = nullptr;
1082 0 : if (!CPLIsUTF8(pszString, -1) &&
1083 0 : CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")))
1084 : {
1085 : static bool bFirstTime = true;
1086 0 : if (bFirstTime)
1087 : {
1088 0 : bFirstTime = false;
1089 0 : CPLError(CE_Warning, CPLE_AppDefined,
1090 : "%s is not a valid UTF-8 string. Forcing it to ASCII.\n"
1091 : "If you still want the original string and change the XML "
1092 : "file encoding\n"
1093 : "afterwards, you can define OGR_FORCE_ASCII=NO as "
1094 : "configuration option.\n"
1095 : "This warning won't be issued anymore",
1096 : pszString);
1097 : }
1098 : else
1099 : {
1100 0 : CPLDebug("OGR",
1101 : "%s is not a valid UTF-8 string. Forcing it to ASCII",
1102 : pszString);
1103 : }
1104 0 : pszEscaped = CPLForceToASCII(pszString, -1, '?');
1105 : }
1106 : else
1107 : {
1108 0 : pszEscaped = CPLStrdup(pszString);
1109 : }
1110 :
1111 0 : return pszEscaped;
1112 : }
1113 :
1114 : /************************************************************************/
1115 : /* OGRGPX_WriteXMLExtension() */
1116 : /************************************************************************/
1117 :
1118 0 : bool OGRGPXLayer::OGRGPX_WriteXMLExtension(const char *pszTagName,
1119 : const char *pszContent)
1120 : {
1121 0 : CPLXMLNode *poXML = CPLParseXMLString(pszContent);
1122 0 : if (poXML)
1123 : {
1124 0 : const char *pszUnderscore = strchr(pszTagName, '_');
1125 0 : char *pszTagNameWithNS = CPLStrdup(pszTagName);
1126 0 : if (pszUnderscore)
1127 0 : pszTagNameWithNS[pszUnderscore - pszTagName] = ':';
1128 :
1129 : /* If we detect a Garmin GPX extension, add its xmlns */
1130 0 : const char *pszXMLNS = nullptr;
1131 0 : if (strcmp(pszTagName, "gpxx_WaypointExtension") == 0)
1132 0 : pszXMLNS = " xmlns:gpxx=\"http://www.garmin.com/xmlschemas/"
1133 : "GpxExtensions/v3\"";
1134 :
1135 : /* Don't XML escape here */
1136 0 : char *pszUTF8 = OGRGPX_GetUTF8String(pszContent);
1137 0 : m_poDS->PrintLine(" <%s%s>%s</%s>", pszTagNameWithNS,
1138 : (pszXMLNS) ? pszXMLNS : "", pszUTF8,
1139 : pszTagNameWithNS);
1140 0 : CPLFree(pszUTF8);
1141 :
1142 0 : CPLFree(pszTagNameWithNS);
1143 0 : CPLDestroyXMLNode(poXML);
1144 :
1145 0 : return true;
1146 : }
1147 :
1148 0 : return false;
1149 : }
1150 :
1151 : /************************************************************************/
1152 : /* WriteFeatureAttributes() */
1153 : /************************************************************************/
1154 :
1155 32 : static void AddIdent(VSILFILE *fp, int nIdentLevel)
1156 : {
1157 86 : for (int i = 0; i < nIdentLevel; i++)
1158 54 : VSIFPrintfL(fp, " ");
1159 32 : }
1160 :
1161 39 : void OGRGPXLayer::WriteFeatureAttributes(const OGRFeature *poFeature,
1162 : int nIdentLevel)
1163 : {
1164 39 : VSILFILE *fp = m_poDS->GetOutputFP();
1165 :
1166 : /* Begin with standard GPX fields */
1167 39 : int i = m_iFirstGPXField;
1168 705 : for (; i < m_nGPXFields; i++)
1169 : {
1170 666 : const OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
1171 666 : if (poFeature->IsFieldSetAndNotNull(i))
1172 : {
1173 24 : const char *pszName = poFieldDefn->GetNameRef();
1174 24 : if (strcmp(pszName, "time") == 0)
1175 : {
1176 2 : char *pszDate = OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
1177 2 : AddIdent(fp, nIdentLevel);
1178 2 : m_poDS->PrintLine("<time>%s</time>", pszDate);
1179 2 : CPLFree(pszDate);
1180 : }
1181 22 : else if (STARTS_WITH(pszName, "link"))
1182 : {
1183 6 : if (strstr(pszName, "href"))
1184 : {
1185 2 : AddIdent(fp, nIdentLevel);
1186 2 : VSIFPrintfL(fp, "<link href=\"%s\">",
1187 : poFeature->GetFieldAsString(i));
1188 2 : if (poFeature->IsFieldSetAndNotNull(i + 1))
1189 2 : VSIFPrintfL(fp, "<text>%s</text>",
1190 : poFeature->GetFieldAsString(i + 1));
1191 2 : if (poFeature->IsFieldSetAndNotNull(i + 2))
1192 2 : VSIFPrintfL(fp, "<type>%s</type>",
1193 : poFeature->GetFieldAsString(i + 2));
1194 2 : m_poDS->PrintLine("</link>");
1195 : }
1196 : }
1197 16 : else if (poFieldDefn->GetType() == OFTReal)
1198 : {
1199 : char szValue[64];
1200 4 : OGRFormatDouble(szValue, sizeof(szValue),
1201 : poFeature->GetFieldAsDouble(i), '.');
1202 4 : AddIdent(fp, nIdentLevel);
1203 4 : m_poDS->PrintLine("<%s>%s</%s>", pszName, szValue, pszName);
1204 : }
1205 : else
1206 : {
1207 12 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1208 : poFeature->GetFieldAsString(i));
1209 12 : AddIdent(fp, nIdentLevel);
1210 12 : m_poDS->PrintLine("<%s>%s</%s>", pszName, pszValue, pszName);
1211 12 : CPLFree(pszValue);
1212 : }
1213 : }
1214 : }
1215 :
1216 : /* Write "extra" fields within the <extensions> tag */
1217 39 : const int n = m_poFeatureDefn->GetFieldCount();
1218 39 : if (i < n)
1219 : {
1220 2 : const std::string &osExtensionsNS = m_poDS->GetExtensionsNS();
1221 2 : AddIdent(fp, nIdentLevel);
1222 2 : m_poDS->PrintLine("<extensions>");
1223 10 : for (; i < n; i++)
1224 : {
1225 8 : const OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
1226 8 : if (poFeature->IsFieldSetAndNotNull(i))
1227 : {
1228 8 : char *compatibleName = OGRGPX_GetXMLCompatibleTagName(
1229 : osExtensionsNS.c_str(), poFieldDefn->GetNameRef());
1230 :
1231 8 : if (poFieldDefn->GetType() == OFTReal)
1232 : {
1233 : char szValue[64];
1234 0 : OGRFormatDouble(szValue, sizeof(szValue),
1235 : poFeature->GetFieldAsDouble(i), '.');
1236 0 : AddIdent(fp, nIdentLevel + 1);
1237 0 : m_poDS->PrintLine("<%s:%s>%s</%s:%s>",
1238 : osExtensionsNS.c_str(), compatibleName,
1239 : szValue, osExtensionsNS.c_str(),
1240 : compatibleName);
1241 : }
1242 : else
1243 : {
1244 8 : const char *pszRaw = poFeature->GetFieldAsString(i);
1245 :
1246 : /* Try to detect XML content */
1247 8 : if (pszRaw[0] == '<' && pszRaw[strlen(pszRaw) - 1] == '>')
1248 : {
1249 0 : if (OGRGPX_WriteXMLExtension(compatibleName, pszRaw))
1250 : {
1251 0 : CPLFree(compatibleName);
1252 0 : continue;
1253 : }
1254 : }
1255 :
1256 : /* Try to detect XML escaped content */
1257 8 : else if (STARTS_WITH(pszRaw, "<") &&
1258 0 : STARTS_WITH(pszRaw + strlen(pszRaw) - 4, ">"))
1259 : {
1260 : char *pszUnescapedContent =
1261 0 : CPLUnescapeString(pszRaw, nullptr, CPLES_XML);
1262 :
1263 0 : if (OGRGPX_WriteXMLExtension(compatibleName,
1264 : pszUnescapedContent))
1265 : {
1266 0 : CPLFree(pszUnescapedContent);
1267 0 : CPLFree(compatibleName);
1268 0 : continue;
1269 : }
1270 :
1271 0 : CPLFree(pszUnescapedContent);
1272 : }
1273 :
1274 : /* Remove leading spaces for a numeric field */
1275 8 : if (poFieldDefn->GetType() == OFTInteger)
1276 : {
1277 0 : while (*pszRaw == ' ')
1278 0 : pszRaw++;
1279 : }
1280 :
1281 8 : char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
1282 8 : AddIdent(fp, nIdentLevel + 1);
1283 8 : m_poDS->PrintLine("<%s:%s>%s</%s:%s>",
1284 : osExtensionsNS.c_str(), compatibleName,
1285 : pszEscaped, osExtensionsNS.c_str(),
1286 : compatibleName);
1287 8 : CPLFree(pszEscaped);
1288 : }
1289 8 : CPLFree(compatibleName);
1290 : }
1291 : }
1292 2 : AddIdent(fp, nIdentLevel);
1293 2 : m_poDS->PrintLine("</extensions>");
1294 : }
1295 39 : }
1296 :
1297 : /************************************************************************/
1298 : /* CheckAndFixCoordinatesValidity() */
1299 : /************************************************************************/
1300 :
1301 41 : OGRErr OGRGPXLayer::CheckAndFixCoordinatesValidity(double *pdfLatitude,
1302 : double *pdfLongitude)
1303 : {
1304 41 : if (pdfLatitude != nullptr && (*pdfLatitude < -90 || *pdfLatitude > 90))
1305 : {
1306 : static bool bFirstWarning = true;
1307 0 : if (bFirstWarning)
1308 : {
1309 0 : bFirstWarning = false;
1310 0 : CPLError(CE_Failure, CPLE_AppDefined,
1311 : "Latitude %f is invalid. Valid range is [-90,90]. "
1312 : "This warning will not be issued any more",
1313 : *pdfLatitude);
1314 : }
1315 0 : return OGRERR_FAILURE;
1316 : }
1317 :
1318 41 : if (pdfLongitude != nullptr &&
1319 41 : (*pdfLongitude < -180 || *pdfLongitude > 180))
1320 : {
1321 : static bool bFirstWarning = true;
1322 0 : if (bFirstWarning)
1323 : {
1324 0 : bFirstWarning = false;
1325 0 : CPLError(CE_Warning, CPLE_AppDefined,
1326 : "Longitude %f has been modified to fit into "
1327 : "range [-180,180]. This warning will not be "
1328 : "issued any more",
1329 : *pdfLongitude);
1330 : }
1331 :
1332 0 : *pdfLongitude = fmod(*pdfLongitude + 180.0, 360.0) - 180.0;
1333 0 : return OGRERR_NONE;
1334 : }
1335 :
1336 41 : return OGRERR_NONE;
1337 : }
1338 :
1339 : /************************************************************************/
1340 : /* ICreateFeature() */
1341 : /************************************************************************/
1342 :
1343 49 : OGRErr OGRGPXLayer::ICreateFeature(OGRFeature *poFeature)
1344 :
1345 : {
1346 49 : VSILFILE *fp = m_poDS->GetOutputFP();
1347 49 : if (fp == nullptr)
1348 0 : return OGRERR_FAILURE;
1349 :
1350 : char szLat[64];
1351 : char szLon[64];
1352 : char szAlt[64];
1353 :
1354 49 : const OGRGeometry *poGeom = poFeature->GetGeometryRef();
1355 :
1356 49 : if (m_gpxGeomType == GPX_WPT)
1357 : {
1358 14 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE)
1359 : {
1360 0 : CPLError(CE_Failure, CPLE_NotSupported,
1361 : "Cannot write a 'wpt' element after a 'rte' element.\n");
1362 6 : return OGRERR_FAILURE;
1363 : }
1364 14 : else if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK)
1365 : {
1366 0 : CPLError(CE_Failure, CPLE_NotSupported,
1367 : "Cannot write a 'wpt' element after a 'trk' element.\n");
1368 0 : return OGRERR_FAILURE;
1369 : }
1370 :
1371 14 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1372 :
1373 24 : if (poGeom == nullptr ||
1374 10 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1375 : {
1376 6 : CPLError(
1377 : CE_Failure, CPLE_AppDefined,
1378 : "Features without geometry or with non-ponctual geometries not "
1379 : "supported by GPX writer in waypoints layer.");
1380 6 : return OGRERR_FAILURE;
1381 : }
1382 :
1383 8 : if (poGeom->getCoordinateDimension() == 0)
1384 : {
1385 0 : CPLError(CE_Failure, CPLE_AppDefined,
1386 : "POINT EMPTY geometries not supported by GPX writer.");
1387 0 : return OGRERR_FAILURE;
1388 : }
1389 :
1390 8 : const OGRPoint *point = poGeom->toPoint();
1391 8 : double lat = point->getY();
1392 8 : double lon = point->getX();
1393 8 : CheckAndFixCoordinatesValidity(&lat, &lon);
1394 8 : m_poDS->AddCoord(lon, lat);
1395 8 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1396 8 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1397 8 : m_poDS->PrintLine("<wpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
1398 8 : WriteFeatureAttributes(poFeature);
1399 8 : m_poDS->PrintLine("</wpt>");
1400 : }
1401 35 : else if (m_gpxGeomType == GPX_ROUTE)
1402 : {
1403 24 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
1404 12 : m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
1405 : {
1406 0 : CPLError(CE_Failure, CPLE_NotSupported,
1407 : "Cannot write a 'rte' element after a 'trk' element.\n");
1408 0 : return OGRERR_FAILURE;
1409 : }
1410 :
1411 12 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1412 0 : m_poDS->m_nLastRteId != -1)
1413 : {
1414 0 : m_poDS->PrintLine("</rte>");
1415 0 : m_poDS->m_nLastRteId = -1;
1416 : }
1417 :
1418 12 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1419 :
1420 12 : const OGRLineString *line = nullptr;
1421 :
1422 12 : if (poGeom == nullptr)
1423 : {
1424 4 : m_poDS->PrintLine("<rte>");
1425 4 : WriteFeatureAttributes(poFeature);
1426 4 : m_poDS->PrintLine("</rte>");
1427 4 : return OGRERR_NONE;
1428 : }
1429 :
1430 8 : switch (poGeom->getGeometryType())
1431 : {
1432 6 : case wkbLineString:
1433 : case wkbLineString25D:
1434 : {
1435 6 : line = poGeom->toLineString();
1436 6 : break;
1437 : }
1438 :
1439 0 : case wkbMultiLineString:
1440 : case wkbMultiLineString25D:
1441 : {
1442 : int nGeometries =
1443 0 : poGeom->toMultiLineString()->getNumGeometries();
1444 0 : if (nGeometries == 0)
1445 : {
1446 0 : line = nullptr;
1447 : }
1448 0 : else if (nGeometries == 1)
1449 : {
1450 0 : line = poGeom->toMultiLineString()->getGeometryRef(0);
1451 : }
1452 : else
1453 : {
1454 0 : CPLError(CE_Failure, CPLE_NotSupported,
1455 : "Multiline with more than one line is not "
1456 : "supported for 'rte' element.");
1457 0 : return OGRERR_FAILURE;
1458 : }
1459 0 : break;
1460 : }
1461 :
1462 2 : default:
1463 : {
1464 2 : CPLError(
1465 : CE_Failure, CPLE_NotSupported,
1466 : "Geometry type of `%s' not supported for 'rte' element.\n",
1467 2 : OGRGeometryTypeToName(poGeom->getGeometryType()));
1468 2 : return OGRERR_FAILURE;
1469 : }
1470 : }
1471 :
1472 6 : m_poDS->PrintLine("<rte>");
1473 6 : WriteFeatureAttributes(poFeature);
1474 6 : if (line)
1475 : {
1476 6 : const int n = line->getNumPoints();
1477 17 : for (int i = 0; i < n; i++)
1478 : {
1479 11 : double lat = line->getY(i);
1480 11 : double lon = line->getX(i);
1481 11 : CheckAndFixCoordinatesValidity(&lat, &lon);
1482 11 : m_poDS->AddCoord(lon, lat);
1483 11 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1484 11 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1485 11 : m_poDS->PrintLine(" <rtept lat=\"%s\" lon=\"%s\">", szLat,
1486 : szLon);
1487 18 : if (poGeom->getGeometryType() == wkbLineString25D ||
1488 7 : poGeom->getGeometryType() == wkbMultiLineString25D)
1489 : {
1490 4 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i), '.');
1491 4 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1492 : }
1493 11 : m_poDS->PrintLine(" </rtept>");
1494 : }
1495 : }
1496 6 : m_poDS->PrintLine("</rte>");
1497 : }
1498 23 : else if (m_gpxGeomType == GPX_TRACK)
1499 : {
1500 13 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1501 0 : m_poDS->m_nLastRteId != -1)
1502 : {
1503 0 : m_poDS->PrintLine("</rte>");
1504 0 : m_poDS->m_nLastRteId = -1;
1505 : }
1506 13 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT &&
1507 0 : m_poDS->m_nLastTrkId != -1)
1508 : {
1509 0 : m_poDS->PrintLine(" </trkseg>");
1510 0 : m_poDS->PrintLine("</trk>");
1511 0 : m_poDS->m_nLastTrkId = -1;
1512 0 : m_poDS->m_nLastTrkSegId = -1;
1513 : }
1514 :
1515 13 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1516 :
1517 13 : if (poGeom == nullptr)
1518 : {
1519 4 : m_poDS->PrintLine("<trk>");
1520 4 : WriteFeatureAttributes(poFeature);
1521 4 : m_poDS->PrintLine("</trk>");
1522 4 : return OGRERR_NONE;
1523 : }
1524 :
1525 9 : switch (poGeom->getGeometryType())
1526 : {
1527 0 : case wkbLineString:
1528 : case wkbLineString25D:
1529 : {
1530 0 : const OGRLineString *line = poGeom->toLineString();
1531 0 : const int n = line->getNumPoints();
1532 0 : m_poDS->PrintLine("<trk>");
1533 0 : WriteFeatureAttributes(poFeature);
1534 0 : m_poDS->PrintLine(" <trkseg>");
1535 0 : for (int i = 0; i < n; i++)
1536 : {
1537 0 : double lat = line->getY(i);
1538 0 : double lon = line->getX(i);
1539 0 : CheckAndFixCoordinatesValidity(&lat, &lon);
1540 0 : m_poDS->AddCoord(lon, lat);
1541 0 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1542 0 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1543 0 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">",
1544 : szLat, szLon);
1545 0 : if (line->getGeometryType() == wkbLineString25D)
1546 : {
1547 0 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
1548 : '.');
1549 0 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1550 : }
1551 0 : m_poDS->PrintLine(" </trkpt>");
1552 : }
1553 0 : m_poDS->PrintLine(" </trkseg>");
1554 0 : m_poDS->PrintLine("</trk>");
1555 0 : break;
1556 : }
1557 :
1558 7 : case wkbMultiLineString:
1559 : case wkbMultiLineString25D:
1560 : {
1561 7 : m_poDS->PrintLine("<trk>");
1562 7 : WriteFeatureAttributes(poFeature);
1563 14 : for (auto &&line : poGeom->toMultiLineString())
1564 : {
1565 7 : const int n = (line) ? line->getNumPoints() : 0;
1566 7 : m_poDS->PrintLine(" <trkseg>");
1567 19 : for (int i = 0; i < n; i++)
1568 : {
1569 12 : double lat = line->getY(i);
1570 12 : double lon = line->getX(i);
1571 12 : CheckAndFixCoordinatesValidity(&lat, &lon);
1572 12 : m_poDS->AddCoord(lon, lat);
1573 12 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1574 12 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1575 12 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">",
1576 : szLat, szLon);
1577 12 : if (line->getGeometryType() == wkbLineString25D)
1578 : {
1579 4 : OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
1580 : '.');
1581 4 : m_poDS->PrintLine(" <ele>%s</ele>", szAlt);
1582 : }
1583 12 : m_poDS->PrintLine(" </trkpt>");
1584 : }
1585 7 : m_poDS->PrintLine(" </trkseg>");
1586 : }
1587 7 : m_poDS->PrintLine("</trk>");
1588 7 : break;
1589 : }
1590 :
1591 2 : default:
1592 : {
1593 2 : CPLError(
1594 : CE_Failure, CPLE_NotSupported,
1595 : "Geometry type of `%s' not supported for 'trk' element.\n",
1596 2 : OGRGeometryTypeToName(poGeom->getGeometryType()));
1597 2 : return OGRERR_FAILURE;
1598 : }
1599 : }
1600 : }
1601 10 : else if (m_gpxGeomType == GPX_ROUTE_POINT)
1602 : {
1603 10 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
1604 5 : m_poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
1605 : {
1606 0 : CPLError(CE_Failure, CPLE_NotSupported,
1607 : "Cannot write a 'rte' element after a 'trk' element.\n");
1608 0 : return OGRERR_FAILURE;
1609 : }
1610 :
1611 10 : if (poGeom == nullptr ||
1612 5 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1613 : {
1614 0 : CPLError(
1615 : CE_Failure, CPLE_AppDefined,
1616 : "Features without geometry or with non-ponctual geometries not "
1617 : "supported by GPX writer in route_points layer.");
1618 0 : return OGRERR_FAILURE;
1619 : }
1620 :
1621 5 : if (poGeom->getCoordinateDimension() == 0)
1622 : {
1623 0 : CPLError(CE_Failure, CPLE_AppDefined,
1624 : "POINT EMPTY geometries not supported by GPX writer.");
1625 0 : return OGRERR_FAILURE;
1626 : }
1627 :
1628 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_ROUTE_FID))
1629 : {
1630 0 : CPLError(
1631 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1632 0 : m_poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
1633 0 : return OGRERR_FAILURE;
1634 : }
1635 5 : if (poFeature->GetFieldAsInteger(FLD_ROUTE_FID) < 0)
1636 : {
1637 0 : CPLError(
1638 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1639 0 : m_poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
1640 0 : return OGRERR_FAILURE;
1641 : }
1642 :
1643 5 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1644 :
1645 5 : if (m_poDS->m_nLastRteId != poFeature->GetFieldAsInteger(FLD_ROUTE_FID))
1646 : {
1647 3 : if (m_poDS->m_nLastRteId != -1)
1648 : {
1649 1 : m_poDS->PrintLine("</rte>");
1650 : }
1651 3 : m_poDS->PrintLine("<rte>");
1652 3 : if (poFeature->IsFieldSetAndNotNull(FLD_ROUTE_NAME))
1653 : {
1654 3 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1655 : poFeature->GetFieldAsString(FLD_ROUTE_NAME));
1656 3 : m_poDS->PrintLine(" <%s>%s</%s>", "name", pszValue, "name");
1657 3 : CPLFree(pszValue);
1658 : }
1659 : }
1660 :
1661 5 : m_poDS->m_nLastRteId = poFeature->GetFieldAsInteger(FLD_ROUTE_FID);
1662 :
1663 5 : const OGRPoint *point = poGeom->toPoint();
1664 5 : double lat = point->getY();
1665 5 : double lon = point->getX();
1666 5 : CheckAndFixCoordinatesValidity(&lat, &lon);
1667 5 : m_poDS->AddCoord(lon, lat);
1668 5 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1669 5 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1670 5 : m_poDS->PrintLine(" <rtept lat=\"%s\" lon=\"%s\">", szLat, szLon);
1671 5 : WriteFeatureAttributes(poFeature, 2);
1672 5 : m_poDS->PrintLine(" </rtept>");
1673 : }
1674 : else
1675 : {
1676 6 : if (m_poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
1677 1 : m_poDS->m_nLastRteId != -1)
1678 : {
1679 1 : m_poDS->PrintLine("</rte>");
1680 1 : m_poDS->m_nLastRteId = -1;
1681 : }
1682 :
1683 10 : if (poGeom == nullptr ||
1684 5 : wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
1685 : {
1686 0 : CPLError(
1687 : CE_Failure, CPLE_AppDefined,
1688 : "Features without geometry or with non-ponctual geometries not "
1689 : "supported by GPX writer in track_points layer.");
1690 0 : return OGRERR_FAILURE;
1691 : }
1692 :
1693 5 : if (poGeom->getCoordinateDimension() == 0)
1694 : {
1695 0 : CPLError(CE_Failure, CPLE_AppDefined,
1696 : "POINT EMPTY geometries not supported by GPX writer.");
1697 0 : return OGRERR_FAILURE;
1698 : }
1699 :
1700 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_TRACK_FID))
1701 : {
1702 0 : CPLError(
1703 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1704 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
1705 0 : return OGRERR_FAILURE;
1706 : }
1707 5 : if (poFeature->GetFieldAsInteger(FLD_TRACK_FID) < 0)
1708 : {
1709 0 : CPLError(
1710 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1711 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
1712 0 : return OGRERR_FAILURE;
1713 : }
1714 5 : if (!poFeature->IsFieldSetAndNotNull(FLD_TRACK_SEG_ID))
1715 : {
1716 0 : CPLError(
1717 : CE_Failure, CPLE_AppDefined, "Field %s must be set.",
1718 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
1719 0 : return OGRERR_FAILURE;
1720 : }
1721 5 : if (poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID) < 0)
1722 : {
1723 0 : CPLError(
1724 : CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
1725 0 : m_poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
1726 0 : return OGRERR_FAILURE;
1727 : }
1728 :
1729 5 : m_poDS->SetLastGPXGeomTypeWritten(m_gpxGeomType);
1730 :
1731 5 : if (m_poDS->m_nLastTrkId != poFeature->GetFieldAsInteger(FLD_TRACK_FID))
1732 : {
1733 3 : if (m_poDS->m_nLastTrkId != -1)
1734 : {
1735 1 : m_poDS->PrintLine(" </trkseg>");
1736 1 : m_poDS->PrintLine("</trk>");
1737 : }
1738 3 : m_poDS->PrintLine("<trk>");
1739 :
1740 3 : if (poFeature->IsFieldSetAndNotNull(FLD_TRACK_NAME))
1741 : {
1742 3 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1743 : poFeature->GetFieldAsString(FLD_TRACK_NAME));
1744 3 : m_poDS->PrintLine(" <%s>%s</%s>", "name", pszValue, "name");
1745 3 : CPLFree(pszValue);
1746 : }
1747 :
1748 3 : m_poDS->PrintLine(" <trkseg>");
1749 : }
1750 4 : else if (m_poDS->m_nLastTrkSegId !=
1751 2 : poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID))
1752 : {
1753 1 : m_poDS->PrintLine(" </trkseg>");
1754 1 : m_poDS->PrintLine(" <trkseg>");
1755 : }
1756 :
1757 5 : m_poDS->m_nLastTrkId = poFeature->GetFieldAsInteger(FLD_TRACK_FID);
1758 10 : m_poDS->m_nLastTrkSegId =
1759 5 : poFeature->GetFieldAsInteger(FLD_TRACK_SEG_ID);
1760 :
1761 5 : const OGRPoint *point = poGeom->toPoint();
1762 5 : double lat = point->getY();
1763 5 : double lon = point->getX();
1764 5 : CheckAndFixCoordinatesValidity(&lat, &lon);
1765 5 : m_poDS->AddCoord(lon, lat);
1766 5 : OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
1767 5 : OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
1768 5 : m_poDS->PrintLine(" <trkpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
1769 5 : WriteFeatureAttributes(poFeature, 3);
1770 5 : m_poDS->PrintLine(" </trkpt>");
1771 : }
1772 :
1773 31 : return OGRERR_NONE;
1774 : }
1775 :
1776 : /************************************************************************/
1777 : /* CreateField() */
1778 : /************************************************************************/
1779 :
1780 40 : OGRErr OGRGPXLayer::CreateField(const OGRFieldDefn *poField, int /*bApproxOK*/)
1781 : {
1782 702 : for (int iField = 0; iField < m_poFeatureDefn->GetFieldCount(); iField++)
1783 : {
1784 662 : if (strcmp(m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1785 662 : poField->GetNameRef()) == 0)
1786 : {
1787 0 : return OGRERR_NONE;
1788 : }
1789 : }
1790 40 : if (!m_poDS->GetUseExtensions())
1791 : {
1792 36 : CPLError(CE_Failure, CPLE_NotSupported,
1793 : "Field of name '%s' is not supported in GPX schema. "
1794 : "Use GPX_USE_EXTENSIONS creation option to allow use of the "
1795 : "<extensions> element.",
1796 : poField->GetNameRef());
1797 36 : return OGRERR_FAILURE;
1798 : }
1799 : else
1800 : {
1801 4 : m_poFeatureDefn->AddFieldDefn(poField);
1802 4 : return OGRERR_NONE;
1803 : }
1804 : }
1805 :
1806 : /************************************************************************/
1807 : /* TestCapability() */
1808 : /************************************************************************/
1809 :
1810 58 : int OGRGPXLayer::TestCapability(const char *pszCap)
1811 :
1812 : {
1813 58 : if (EQUAL(pszCap, OLCSequentialWrite))
1814 6 : return m_bWriteMode;
1815 52 : else if (EQUAL(pszCap, OLCCreateField))
1816 6 : return m_bWriteMode;
1817 46 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
1818 0 : return TRUE;
1819 46 : else if (EQUAL(pszCap, OLCZGeometries))
1820 0 : return TRUE;
1821 :
1822 : else
1823 46 : return FALSE;
1824 : }
1825 :
1826 : /************************************************************************/
1827 : /* LoadExtensionsSchema() */
1828 : /************************************************************************/
1829 :
1830 : #ifdef HAVE_EXPAT
1831 :
1832 4730 : static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
1833 : const char *pszName,
1834 : const char **ppszAttr)
1835 : {
1836 4730 : static_cast<OGRGPXLayer *>(pUserData)->startElementLoadSchemaCbk(pszName,
1837 : ppszAttr);
1838 4730 : }
1839 :
1840 4730 : static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
1841 : const char *pszName)
1842 : {
1843 4730 : static_cast<OGRGPXLayer *>(pUserData)->endElementLoadSchemaCbk(pszName);
1844 4730 : }
1845 :
1846 11710 : static void XMLCALL dataHandlerLoadSchemaCbk(void *pUserData, const char *data,
1847 : int nLen)
1848 : {
1849 11710 : static_cast<OGRGPXLayer *>(pUserData)->dataHandlerLoadSchemaCbk(data, nLen);
1850 11710 : }
1851 :
1852 : /** This function parses the whole file to detect the extensions fields */
1853 75 : void OGRGPXLayer::LoadExtensionsSchema()
1854 : {
1855 75 : m_oSchemaParser = OGRCreateExpatXMLParser();
1856 75 : XML_SetElementHandler(m_oSchemaParser, ::startElementLoadSchemaCbk,
1857 : ::endElementLoadSchemaCbk);
1858 75 : XML_SetCharacterDataHandler(m_oSchemaParser, ::dataHandlerLoadSchemaCbk);
1859 75 : XML_SetUserData(m_oSchemaParser, this);
1860 :
1861 75 : m_fpGPX->Seek(0, SEEK_SET);
1862 :
1863 75 : m_inInterestingElement = false;
1864 75 : m_inExtensions = false;
1865 75 : m_depthLevel = 0;
1866 75 : m_currentFieldDefn = nullptr;
1867 75 : m_osSubElementName.clear();
1868 75 : m_osSubElementValue.clear();
1869 75 : m_nWithoutEventCounter = 0;
1870 75 : m_bStopParsing = false;
1871 :
1872 150 : std::vector<char> aBuf(PARSER_BUF_SIZE);
1873 75 : int nDone = 0;
1874 0 : do
1875 : {
1876 75 : m_nDataHandlerCounter = 0;
1877 : unsigned int nLen = static_cast<unsigned int>(
1878 75 : m_fpGPX->Read(aBuf.data(), 1, aBuf.size()));
1879 75 : nDone = (nLen < aBuf.size());
1880 75 : if (XML_Parse(m_oSchemaParser, aBuf.data(), nLen, nDone) ==
1881 : XML_STATUS_ERROR)
1882 : {
1883 0 : CPLError(
1884 : CE_Failure, CPLE_AppDefined,
1885 : "XML parsing of GPX file failed : "
1886 : "%s at line %d, column %d",
1887 : XML_ErrorString(XML_GetErrorCode(m_oSchemaParser)),
1888 0 : static_cast<int>(XML_GetCurrentLineNumber(m_oSchemaParser)),
1889 0 : static_cast<int>(XML_GetCurrentColumnNumber(m_oSchemaParser)));
1890 0 : m_bStopParsing = true;
1891 0 : break;
1892 : }
1893 75 : m_nWithoutEventCounter++;
1894 75 : } while (!nDone && !m_bStopParsing && m_nWithoutEventCounter < 10);
1895 :
1896 75 : if (m_nWithoutEventCounter == 10)
1897 : {
1898 0 : CPLError(CE_Failure, CPLE_AppDefined,
1899 : "Too much data inside one element. File probably corrupted");
1900 0 : m_bStopParsing = true;
1901 : }
1902 :
1903 75 : XML_ParserFree(m_oSchemaParser);
1904 75 : m_oSchemaParser = nullptr;
1905 :
1906 75 : m_fpGPX->Seek(0, SEEK_SET);
1907 75 : }
1908 :
1909 : /************************************************************************/
1910 : /* startElementLoadSchemaCbk() */
1911 : /************************************************************************/
1912 :
1913 4730 : void OGRGPXLayer::startElementLoadSchemaCbk(const char *pszName,
1914 : CPL_UNUSED const char **ppszAttr)
1915 : {
1916 4730 : if (m_bStopParsing)
1917 0 : return;
1918 :
1919 4730 : m_nWithoutEventCounter = 0;
1920 :
1921 4730 : if (m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
1922 : {
1923 28 : m_inInterestingElement = true;
1924 28 : m_inExtensions = false;
1925 28 : m_interestingDepthLevel = m_depthLevel;
1926 : }
1927 4702 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
1928 : {
1929 40 : m_inInterestingElement = true;
1930 40 : m_inExtensions = false;
1931 40 : m_interestingDepthLevel = m_depthLevel;
1932 : }
1933 4662 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
1934 : {
1935 26 : m_inInterestingElement = true;
1936 26 : m_inExtensions = false;
1937 26 : m_interestingDepthLevel = m_depthLevel;
1938 : }
1939 4636 : else if (m_gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0)
1940 : {
1941 55 : m_inInterestingElement = true;
1942 55 : m_inExtensions = false;
1943 55 : m_interestingDepthLevel = m_depthLevel;
1944 : }
1945 4581 : else if (m_gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0)
1946 : {
1947 39 : m_inInterestingElement = true;
1948 39 : m_inExtensions = false;
1949 39 : m_interestingDepthLevel = m_depthLevel;
1950 : }
1951 4542 : else if (m_inInterestingElement)
1952 : {
1953 693 : if (m_depthLevel == m_interestingDepthLevel + 1 &&
1954 414 : strcmp(pszName, "extensions") == 0)
1955 : {
1956 16 : m_inExtensions = true;
1957 16 : m_extensionsDepthLevel = m_depthLevel;
1958 : }
1959 677 : else if (m_inExtensions && m_depthLevel == m_extensionsDepthLevel + 1)
1960 : {
1961 10 : m_osSubElementName = pszName;
1962 :
1963 10 : int iField = 0; // Used after for.
1964 231 : for (; iField < m_poFeatureDefn->GetFieldCount(); iField++)
1965 : {
1966 225 : bool bMatch = false;
1967 225 : if (iField >= m_nGPXFields)
1968 : {
1969 : char *pszCompatibleName =
1970 17 : OGRGPX_GetOGRCompatibleTagName(pszName);
1971 17 : bMatch =
1972 17 : strcmp(
1973 17 : m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1974 : pszCompatibleName) == 0;
1975 17 : CPLFree(pszCompatibleName);
1976 : }
1977 : else
1978 : {
1979 208 : bMatch =
1980 208 : strcmp(
1981 208 : m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1982 : pszName) == 0;
1983 : }
1984 :
1985 225 : if (bMatch)
1986 : {
1987 4 : m_currentFieldDefn = m_poFeatureDefn->GetFieldDefn(iField);
1988 4 : break;
1989 : }
1990 : }
1991 10 : if (iField == m_poFeatureDefn->GetFieldCount())
1992 : {
1993 : char *pszCompatibleName =
1994 6 : OGRGPX_GetOGRCompatibleTagName(pszName);
1995 12 : OGRFieldDefn newFieldDefn(pszCompatibleName, OFTInteger);
1996 6 : CPLFree(pszCompatibleName);
1997 :
1998 6 : m_poFeatureDefn->AddFieldDefn(&newFieldDefn);
1999 18 : m_currentFieldDefn = m_poFeatureDefn->GetFieldDefn(
2000 6 : m_poFeatureDefn->GetFieldCount() - 1);
2001 :
2002 6 : if (m_poFeatureDefn->GetFieldCount() == 100)
2003 : {
2004 0 : CPLError(CE_Failure, CPLE_AppDefined,
2005 : "Too many fields. File probably corrupted");
2006 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2007 0 : m_bStopParsing = true;
2008 : }
2009 : }
2010 : }
2011 : }
2012 :
2013 4730 : m_depthLevel++;
2014 : }
2015 :
2016 : /************************************************************************/
2017 : /* endElementLoadSchemaCbk() */
2018 : /************************************************************************/
2019 :
2020 0 : static bool OGRGPXIsInt(const char *pszStr)
2021 : {
2022 0 : while (*pszStr == ' ')
2023 0 : pszStr++;
2024 :
2025 0 : for (int i = 0; pszStr[i]; i++)
2026 : {
2027 0 : if (pszStr[i] == '+' || pszStr[i] == '-')
2028 : {
2029 0 : if (i != 0)
2030 0 : return false;
2031 : }
2032 0 : else if (!(pszStr[i] >= '0' && pszStr[i] <= '9'))
2033 0 : return false;
2034 : }
2035 0 : return true;
2036 : }
2037 :
2038 4730 : void OGRGPXLayer::endElementLoadSchemaCbk(const char *pszName)
2039 : {
2040 4730 : if (m_bStopParsing)
2041 0 : return;
2042 :
2043 4730 : m_nWithoutEventCounter = 0;
2044 :
2045 4730 : m_depthLevel--;
2046 :
2047 4730 : if (m_inInterestingElement)
2048 : {
2049 881 : if (m_gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
2050 : {
2051 28 : m_inInterestingElement = false;
2052 28 : m_inExtensions = false;
2053 : }
2054 853 : else if (m_gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
2055 : {
2056 40 : m_inInterestingElement = false;
2057 40 : m_inExtensions = false;
2058 : }
2059 813 : else if (m_gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
2060 : {
2061 26 : m_inInterestingElement = false;
2062 26 : m_inExtensions = false;
2063 : }
2064 787 : else if (m_gpxGeomType == GPX_TRACK_POINT &&
2065 126 : strcmp(pszName, "trkpt") == 0)
2066 : {
2067 55 : m_inInterestingElement = false;
2068 55 : m_inExtensions = false;
2069 : }
2070 732 : else if (m_gpxGeomType == GPX_ROUTE_POINT &&
2071 104 : strcmp(pszName, "rtept") == 0)
2072 : {
2073 39 : m_inInterestingElement = false;
2074 39 : m_inExtensions = false;
2075 : }
2076 693 : else if (m_depthLevel == m_interestingDepthLevel + 1 &&
2077 414 : strcmp(pszName, "extensions") == 0)
2078 : {
2079 16 : m_inExtensions = false;
2080 : }
2081 10 : else if (m_inExtensions && m_depthLevel == m_extensionsDepthLevel + 1 &&
2082 687 : !m_osSubElementName.empty() && m_osSubElementName == pszName)
2083 : {
2084 10 : if (!m_osSubElementValue.empty() && m_currentFieldDefn)
2085 : {
2086 11 : if (m_currentFieldDefn->GetType() == OFTInteger ||
2087 3 : m_currentFieldDefn->GetType() == OFTReal)
2088 : {
2089 5 : char *pszRemainingStr = nullptr;
2090 5 : CPLStrtod(m_osSubElementValue.c_str(), &pszRemainingStr);
2091 5 : if (pszRemainingStr == nullptr || *pszRemainingStr == 0 ||
2092 5 : *pszRemainingStr == ' ')
2093 : {
2094 0 : if (m_currentFieldDefn->GetType() == OFTInteger)
2095 : {
2096 0 : if (!OGRGPXIsInt(m_osSubElementValue.c_str()))
2097 : {
2098 0 : m_currentFieldDefn->SetType(OFTReal);
2099 : }
2100 : }
2101 : }
2102 : else
2103 : {
2104 5 : m_currentFieldDefn->SetType(OFTString);
2105 : }
2106 : }
2107 : }
2108 :
2109 10 : m_osSubElementName.clear();
2110 10 : m_osSubElementValue.clear();
2111 10 : m_currentFieldDefn = nullptr;
2112 : }
2113 : }
2114 : }
2115 :
2116 : /************************************************************************/
2117 : /* dataHandlerLoadSchemaCbk() */
2118 : /************************************************************************/
2119 :
2120 11710 : void OGRGPXLayer::dataHandlerLoadSchemaCbk(const char *data, int nLen)
2121 : {
2122 11710 : if (m_bStopParsing)
2123 0 : return;
2124 :
2125 11710 : m_nDataHandlerCounter++;
2126 11710 : if (m_nDataHandlerCounter >= PARSER_BUF_SIZE)
2127 : {
2128 0 : CPLError(CE_Failure, CPLE_AppDefined,
2129 : "File probably corrupted (million laugh pattern)");
2130 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2131 0 : m_bStopParsing = true;
2132 0 : return;
2133 : }
2134 :
2135 11710 : m_nWithoutEventCounter = 0;
2136 :
2137 11710 : if (!m_osSubElementName.empty())
2138 : {
2139 : try
2140 : {
2141 8 : m_osSubElementValue.append(data, nLen);
2142 : }
2143 0 : catch (const std::bad_alloc &)
2144 : {
2145 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2146 : "Out of memory when parsing GPX file");
2147 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2148 0 : m_bStopParsing = true;
2149 0 : return;
2150 : }
2151 8 : if (m_osSubElementValue.size() > 100000)
2152 : {
2153 0 : CPLError(CE_Failure, CPLE_AppDefined,
2154 : "Too much data inside one element. "
2155 : "File probably corrupted");
2156 0 : XML_StopParser(m_oSchemaParser, XML_FALSE);
2157 0 : m_bStopParsing = true;
2158 : }
2159 : }
2160 : }
2161 : #else
2162 : void OGRGPXLayer::LoadExtensionsSchema()
2163 : {
2164 : }
2165 : #endif
2166 :
2167 : /************************************************************************/
2168 : /* GetDataset() */
2169 : /************************************************************************/
2170 :
2171 7 : GDALDataset *OGRGPXLayer::GetDataset()
2172 : {
2173 7 : return m_poDS;
2174 : }
|