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