1 : /******************************************************************************
2 : * $Id: ogrgpxdatasource.cpp 23557 2011-12-12 22:08:17Z rouault $
3 : *
4 : * Project: GPX Translator
5 : * Purpose: Implements OGRGPXDataSource class
6 : * Author: Even Rouault, even dot rouault at mines dash paris dot org
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2007, Even Rouault
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogr_gpx.h"
31 : #include "cpl_conv.h"
32 : #include "cpl_string.h"
33 : #include "cpl_csv.h"
34 :
35 : CPL_CVSID("$Id: ogrgpxdatasource.cpp 23557 2011-12-12 22:08:17Z rouault $");
36 :
37 : #define SPACE_FOR_METADATA 160
38 :
39 : /************************************************************************/
40 : /* OGRGPXDataSource() */
41 : /************************************************************************/
42 :
43 566 : OGRGPXDataSource::OGRGPXDataSource()
44 :
45 : {
46 566 : lastGPXGeomTypeWritten = GPX_NONE;
47 566 : bUseExtensions = FALSE;
48 566 : pszExtensionsNS = NULL;
49 :
50 566 : papoLayers = NULL;
51 566 : nLayers = 0;
52 :
53 566 : fpOutput = NULL;
54 566 : nOffsetBounds = -1;
55 566 : dfMinLat = 90;
56 566 : dfMinLon = 180;
57 566 : dfMaxLat = -90;
58 566 : dfMaxLon = -180;
59 :
60 566 : pszName = NULL;
61 566 : pszVersion = NULL;
62 :
63 566 : bIsBackSeekable = TRUE;
64 566 : pszEOL = "\n";
65 :
66 566 : nLastRteId = -1;
67 566 : nLastTrkId = -1;
68 566 : nLastTrkSegId = -1;
69 566 : }
70 :
71 : /************************************************************************/
72 : /* ~OGRGPXDataSource() */
73 : /************************************************************************/
74 :
75 566 : OGRGPXDataSource::~OGRGPXDataSource()
76 :
77 : {
78 566 : if ( fpOutput != NULL )
79 : {
80 8 : if (nLastRteId != -1)
81 0 : PrintLine("</rte>");
82 8 : else if (nLastTrkId != -1)
83 : {
84 4 : PrintLine(" </trkseg>");
85 4 : PrintLine("</trk>");
86 : }
87 8 : PrintLine("</gpx>");
88 8 : if ( bIsBackSeekable )
89 : {
90 : /* Write the <bound> element in the reserved space */
91 8 : if (dfMinLon <= dfMaxLon)
92 : {
93 : char szMetadata[SPACE_FOR_METADATA+1];
94 : int nRet = snprintf(szMetadata, SPACE_FOR_METADATA,
95 : "<metadata><bounds minlat=\"%.15f\" minlon=\"%.15f\" maxlat=\"%.15f\" maxlon=\"%.15f\"/></metadata>",
96 8 : dfMinLat, dfMinLon, dfMaxLat, dfMaxLon);
97 8 : if (nRet < SPACE_FOR_METADATA)
98 : {
99 8 : VSIFSeekL(fpOutput, nOffsetBounds, SEEK_SET);
100 8 : VSIFWriteL(szMetadata, 1, strlen(szMetadata), fpOutput);
101 : }
102 : }
103 8 : VSIFCloseL( fpOutput);
104 : }
105 : }
106 :
107 640 : for( int i = 0; i < nLayers; i++ )
108 74 : delete papoLayers[i];
109 566 : CPLFree( papoLayers );
110 566 : CPLFree( pszExtensionsNS );
111 566 : CPLFree( pszName );
112 566 : CPLFree( pszVersion );
113 566 : }
114 :
115 : /************************************************************************/
116 : /* TestCapability() */
117 : /************************************************************************/
118 :
119 0 : int OGRGPXDataSource::TestCapability( const char * pszCap )
120 :
121 : {
122 0 : if( EQUAL(pszCap,ODsCCreateLayer) )
123 0 : return TRUE;
124 0 : else if( EQUAL(pszCap,ODsCDeleteLayer) )
125 0 : return FALSE;
126 : else
127 0 : return FALSE;
128 : }
129 :
130 : /************************************************************************/
131 : /* GetLayer() */
132 : /************************************************************************/
133 :
134 116 : OGRLayer *OGRGPXDataSource::GetLayer( int iLayer )
135 :
136 : {
137 116 : if( iLayer < 0 || iLayer >= nLayers )
138 0 : return NULL;
139 : else
140 116 : return papoLayers[iLayer];
141 : }
142 :
143 : /************************************************************************/
144 : /* CreateLayer() */
145 : /************************************************************************/
146 :
147 14 : OGRLayer * OGRGPXDataSource::CreateLayer( const char * pszLayerName,
148 : OGRSpatialReference *poSRS,
149 : OGRwkbGeometryType eType,
150 : char ** papszOptions )
151 :
152 : {
153 : GPXGeometryType gpxGeomType;
154 24 : if (eType == wkbPoint || eType == wkbPoint25D)
155 : {
156 10 : if (EQUAL(pszLayerName, "track_points"))
157 4 : gpxGeomType = GPX_TRACK_POINT;
158 6 : else if (EQUAL(pszLayerName, "route_points"))
159 2 : gpxGeomType = GPX_ROUTE_POINT;
160 : else
161 4 : gpxGeomType = GPX_WPT;
162 : }
163 6 : else if (eType == wkbLineString || eType == wkbLineString25D)
164 : {
165 2 : const char *pszForceGPXTrack = CSLFetchNameValue( papszOptions, "FORCE_GPX_TRACK");
166 2 : if (pszForceGPXTrack && CSLTestBoolean(pszForceGPXTrack))
167 0 : gpxGeomType = GPX_TRACK;
168 : else
169 2 : gpxGeomType = GPX_ROUTE;
170 : }
171 4 : else if (eType == wkbMultiLineString || eType == wkbMultiLineString25D)
172 : {
173 2 : const char *pszForceGPXRoute = CSLFetchNameValue( papszOptions, "FORCE_GPX_ROUTE");
174 2 : if (pszForceGPXRoute && CSLTestBoolean(pszForceGPXRoute))
175 0 : gpxGeomType = GPX_ROUTE;
176 : else
177 2 : gpxGeomType = GPX_TRACK;
178 : }
179 0 : else if (eType == wkbUnknown)
180 : {
181 : CPLError(CE_Failure, CPLE_NotSupported,
182 0 : "Cannot create GPX layer %s with unknown geometry type", pszLayerName);
183 0 : return NULL;
184 : }
185 : else
186 : {
187 : CPLError( CE_Failure, CPLE_NotSupported,
188 : "Geometry type of `%s' not supported in GPX.\n",
189 0 : OGRGeometryTypeToName(eType) );
190 0 : return NULL;
191 : }
192 14 : nLayers++;
193 14 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
194 14 : papoLayers[nLayers-1] = new OGRGPXLayer( pszName, pszLayerName, gpxGeomType, this, TRUE );
195 :
196 14 : return papoLayers[nLayers-1];
197 : }
198 :
199 : #ifdef HAVE_EXPAT
200 :
201 : /************************************************************************/
202 : /* startElementValidateCbk() */
203 : /************************************************************************/
204 :
205 4026 : void OGRGPXDataSource::startElementValidateCbk(const char *pszName, const char **ppszAttr)
206 : {
207 4026 : if (validity == GPX_VALIDITY_UNKNOWN)
208 : {
209 80 : if (strcmp(pszName, "gpx") == 0)
210 : {
211 : int i;
212 12 : validity = GPX_VALIDITY_VALID;
213 16 : for(i=0; ppszAttr[i] != NULL; i+= 2)
214 : {
215 16 : if (strcmp(ppszAttr[i], "version") == 0)
216 : {
217 12 : pszVersion = CPLStrdup(ppszAttr[i+1]);
218 12 : break;
219 : }
220 : }
221 : }
222 : else
223 : {
224 68 : validity = GPX_VALIDITY_INVALID;
225 : }
226 : }
227 3946 : else if (validity == GPX_VALIDITY_VALID)
228 : {
229 340 : if (strcmp(pszName, "extensions") == 0)
230 : {
231 8 : bUseExtensions = TRUE;
232 : }
233 340 : nElementsRead++;
234 : }
235 4026 : }
236 :
237 :
238 : /************************************************************************/
239 : /* dataHandlerValidateCbk() */
240 : /************************************************************************/
241 :
242 13154 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
243 : {
244 13154 : nDataHandlerCounter ++;
245 13154 : if (nDataHandlerCounter >= BUFSIZ)
246 : {
247 0 : CPLError(CE_Failure, CPLE_AppDefined, "File probably corrupted (million laugh pattern)");
248 0 : XML_StopParser(oCurrentParser, XML_FALSE);
249 : }
250 13154 : }
251 :
252 :
253 4026 : static void XMLCALL startElementValidateCbk(void *pUserData, const char *pszName, const char **ppszAttr)
254 : {
255 4026 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
256 4026 : poDS->startElementValidateCbk(pszName, ppszAttr);
257 4026 : }
258 :
259 13154 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data, int nLen)
260 : {
261 13154 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
262 13154 : poDS->dataHandlerValidateCbk(data, nLen);
263 13154 : }
264 : #endif
265 :
266 : /************************************************************************/
267 : /* Open() */
268 : /************************************************************************/
269 :
270 558 : int OGRGPXDataSource::Open( const char * pszFilename, int bUpdateIn)
271 :
272 : {
273 558 : if (bUpdateIn)
274 : {
275 : CPLError(CE_Failure, CPLE_NotSupported,
276 0 : "OGR/GPX driver does not support opening a file in update mode");
277 0 : return FALSE;
278 : }
279 : #ifdef HAVE_EXPAT
280 558 : pszName = CPLStrdup( pszFilename );
281 :
282 : /* -------------------------------------------------------------------- */
283 : /* Try to open the file. */
284 : /* -------------------------------------------------------------------- */
285 558 : VSILFILE* fp = VSIFOpenL(pszFilename, "r");
286 558 : if (fp == NULL)
287 148 : return FALSE;
288 :
289 410 : validity = GPX_VALIDITY_UNKNOWN;
290 410 : CPLFree(pszVersion);
291 410 : pszVersion = NULL;
292 410 : bUseExtensions = FALSE;
293 410 : nElementsRead = 0;
294 :
295 410 : XML_Parser oParser = OGRCreateExpatXMLParser();
296 410 : oCurrentParser = oParser;
297 410 : XML_SetUserData(oParser, this);
298 410 : XML_SetElementHandler(oParser, ::startElementValidateCbk, NULL);
299 410 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
300 :
301 : char aBuf[BUFSIZ];
302 : int nDone;
303 : unsigned int nLen;
304 410 : int nCount = 0;
305 :
306 : /* Begin to parse the file and look for the <gpx> element */
307 : /* It *MUST* be the first element of an XML file */
308 : /* So once we have read the first element, we know if we can */
309 : /* handle the file or not with that driver */
310 22 : do
311 : {
312 410 : nDataHandlerCounter = 0;
313 410 : nLen = (unsigned int) VSIFReadL( aBuf, 1, sizeof(aBuf), fp );
314 410 : nDone = VSIFEofL(fp);
315 410 : if (XML_Parse(oParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
316 : {
317 316 : if (nLen <= BUFSIZ-1)
318 192 : aBuf[nLen] = 0;
319 : else
320 124 : aBuf[BUFSIZ-1] = 0;
321 316 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
322 : {
323 : CPLError(CE_Failure, CPLE_AppDefined,
324 : "XML parsing of GPX file failed : %s at line %d, column %d",
325 : XML_ErrorString(XML_GetErrorCode(oParser)),
326 : (int)XML_GetCurrentLineNumber(oParser),
327 0 : (int)XML_GetCurrentColumnNumber(oParser));
328 : }
329 316 : validity = GPX_VALIDITY_INVALID;
330 316 : break;
331 : }
332 94 : if (validity == GPX_VALIDITY_INVALID)
333 : {
334 66 : break;
335 : }
336 28 : else if (validity == GPX_VALIDITY_VALID)
337 : {
338 : /* If we have recognized the <gpx> element, now we try */
339 : /* to recognize if they are <extensions> tags */
340 : /* But we stop to look for after an arbitrary number of tags */
341 12 : if (bUseExtensions)
342 6 : break;
343 6 : else if (nElementsRead > 200)
344 0 : break;
345 : }
346 : else
347 : {
348 : /* After reading 50 * BUFSIZE bytes, and not finding whether the file */
349 : /* is GPX or not, we give up and fail silently */
350 16 : nCount ++;
351 16 : if (nCount == 50)
352 0 : break;
353 : }
354 : } while (!nDone && nLen > 0 );
355 :
356 410 : XML_ParserFree(oParser);
357 :
358 410 : VSIFCloseL(fp);
359 :
360 410 : if (validity == GPX_VALIDITY_VALID)
361 : {
362 12 : CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
363 12 : if (bUseExtensions)
364 6 : CPLDebug("GPX", "It uses <extensions>");
365 :
366 12 : if (pszVersion == NULL)
367 : {
368 : /* Default to 1.1 */
369 : CPLError(CE_Warning, CPLE_AppDefined, "GPX schema version is unknown. "
370 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.");
371 0 : pszVersion = CPLStrdup("1.1");
372 : }
373 12 : else if (strcmp(pszVersion, "1.0") == 0 || strcmp(pszVersion, "1.1") == 0)
374 : {
375 : /* Fine */
376 : }
377 : else
378 : {
379 : CPLError(CE_Warning, CPLE_AppDefined,
380 : "GPX schema version '%s' is not handled by the driver. "
381 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.", pszVersion);
382 : }
383 :
384 12 : nLayers = 5;
385 12 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
386 12 : papoLayers[0] = new OGRGPXLayer( pszName, "waypoints", GPX_WPT, this, FALSE );
387 24 : papoLayers[1] = new OGRGPXLayer( pszName, "routes", GPX_ROUTE, this, FALSE );
388 24 : papoLayers[2] = new OGRGPXLayer( pszName, "tracks", GPX_TRACK, this, FALSE );
389 24 : papoLayers[3] = new OGRGPXLayer( pszName, "route_points", GPX_ROUTE_POINT, this, FALSE );
390 24 : papoLayers[4] = new OGRGPXLayer( pszName, "track_points", GPX_TRACK_POINT, this, FALSE );
391 : }
392 :
393 410 : return (validity == GPX_VALIDITY_VALID);
394 : #else
395 : char aBuf[256];
396 : VSILFILE* fp = VSIFOpenL(pszFilename, "r");
397 : if (fp)
398 : {
399 : unsigned int nLen = (unsigned int)VSIFReadL( aBuf, 1, 255, fp );
400 : aBuf[nLen] = 0;
401 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
402 : {
403 : CPLError(CE_Failure, CPLE_NotSupported,
404 : "OGR/GPX driver has not been built with read support. Expat library required");
405 : }
406 : VSIFCloseL(fp);
407 : }
408 : return FALSE;
409 : #endif
410 : }
411 :
412 :
413 : /************************************************************************/
414 : /* Create() */
415 : /************************************************************************/
416 :
417 8 : int OGRGPXDataSource::Create( const char *pszFilename,
418 : char **papszOptions )
419 : {
420 8 : if( fpOutput != NULL)
421 : {
422 0 : CPLAssert( FALSE );
423 0 : return FALSE;
424 : }
425 :
426 8 : if (strcmp(pszFilename, "/dev/stdout") == 0)
427 0 : pszFilename = "/vsistdout/";
428 :
429 : /* -------------------------------------------------------------------- */
430 : /* Do not override exiting file. */
431 : /* -------------------------------------------------------------------- */
432 : VSIStatBufL sStatBuf;
433 :
434 8 : if( VSIStatL( pszFilename, &sStatBuf ) == 0 )
435 : {
436 : CPLError(CE_Failure, CPLE_NotSupported,
437 : "You have to delete %s before being able to create it with the GPX driver",
438 0 : pszFilename);
439 0 : return FALSE;
440 : }
441 :
442 : /* -------------------------------------------------------------------- */
443 : /* Create the output file. */
444 : /* -------------------------------------------------------------------- */
445 :
446 8 : pszName = CPLStrdup( pszFilename );
447 :
448 8 : if( strcmp(pszName, "/vsistdout/") == 0 )
449 : {
450 0 : bIsBackSeekable = FALSE;
451 0 : fpOutput = VSIFOpenL( pszFilename, "w" );
452 : }
453 : else
454 8 : fpOutput = VSIFOpenL( pszFilename, "w+" );
455 8 : if( fpOutput == NULL )
456 : {
457 : CPLError( CE_Failure, CPLE_OpenFailed,
458 : "Failed to create GPX file %s.",
459 0 : pszFilename );
460 0 : return FALSE;
461 : }
462 :
463 : /* -------------------------------------------------------------------- */
464 : /* End of line character. */
465 : /* -------------------------------------------------------------------- */
466 8 : const char *pszCRLFFormat = CSLFetchNameValue( papszOptions, "LINEFORMAT");
467 :
468 : int bUseCRLF;
469 8 : if( pszCRLFFormat == NULL )
470 : {
471 : #ifdef WIN32
472 : bUseCRLF = TRUE;
473 : #else
474 6 : bUseCRLF = FALSE;
475 : #endif
476 : }
477 2 : else if( EQUAL(pszCRLFFormat,"CRLF") )
478 0 : bUseCRLF = TRUE;
479 2 : else if( EQUAL(pszCRLFFormat,"LF") )
480 2 : bUseCRLF = FALSE;
481 : else
482 : {
483 : CPLError( CE_Warning, CPLE_AppDefined,
484 : "LINEFORMAT=%s not understood, use one of CRLF or LF.",
485 0 : pszCRLFFormat );
486 : #ifdef WIN32
487 : bUseCRLF = TRUE;
488 : #else
489 0 : bUseCRLF = FALSE;
490 : #endif
491 : }
492 8 : pszEOL = (bUseCRLF) ? "\r\n" : "\n";
493 :
494 : /* -------------------------------------------------------------------- */
495 : /* Look at use extensions options. */
496 : /* -------------------------------------------------------------------- */
497 8 : const char* pszUseExtensions = CSLFetchNameValue( papszOptions, "GPX_USE_EXTENSIONS");
498 8 : const char* pszExtensionsNSURL = NULL;
499 8 : if (pszUseExtensions && CSLTestBoolean(pszUseExtensions))
500 : {
501 2 : bUseExtensions = TRUE;
502 :
503 2 : const char* pszExtensionsNSOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS");
504 2 : const char* pszExtensionsNSURLOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS_URL");
505 2 : if (pszExtensionsNSOption && pszExtensionsNSURLOption)
506 : {
507 0 : pszExtensionsNS = CPLStrdup(pszExtensionsNSOption);
508 0 : pszExtensionsNSURL = pszExtensionsNSURLOption;
509 : }
510 : else
511 : {
512 2 : pszExtensionsNS = CPLStrdup("ogr");
513 2 : pszExtensionsNSURL = "http://osgeo.org/gdal";
514 : }
515 : }
516 :
517 : /* -------------------------------------------------------------------- */
518 : /* Output header of GPX file. */
519 : /* -------------------------------------------------------------------- */
520 8 : PrintLine("<?xml version=\"1.0\"?>");
521 8 : VSIFPrintfL(fpOutput, "<gpx version=\"1.1\" creator=\"GDAL " GDAL_RELEASE_NAME "\" ");
522 8 : VSIFPrintfL(fpOutput, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
523 8 : if (bUseExtensions)
524 2 : VSIFPrintfL(fpOutput, "xmlns:%s=\"%s\" ", pszExtensionsNS, pszExtensionsNSURL);
525 8 : VSIFPrintfL(fpOutput, "xmlns=\"http://www.topografix.com/GPX/1/1\" ");
526 8 : PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
527 8 : if (bIsBackSeekable)
528 : {
529 : /* Reserve space for <metadata><bounds/></metadata> */
530 : char szMetadata[SPACE_FOR_METADATA+1];
531 8 : memset(szMetadata, ' ', SPACE_FOR_METADATA);
532 8 : szMetadata[SPACE_FOR_METADATA] = '\0';
533 8 : nOffsetBounds = (int) VSIFTellL(fpOutput);
534 8 : PrintLine("%s", szMetadata);
535 : }
536 :
537 8 : return TRUE;
538 : }
539 :
540 : /************************************************************************/
541 : /* AddCoord() */
542 : /************************************************************************/
543 :
544 40 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
545 : {
546 40 : if (dfLon < dfMinLon) dfMinLon = dfLon;
547 40 : if (dfLat < dfMinLat) dfMinLat = dfLat;
548 40 : if (dfLon > dfMaxLon) dfMaxLon = dfLon;
549 40 : if (dfLat > dfMaxLat) dfMaxLat = dfLat;
550 40 : }
551 :
552 : /************************************************************************/
553 : /* PrintLine() */
554 : /************************************************************************/
555 :
556 250 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
557 : {
558 250 : CPLString osWork;
559 : va_list args;
560 :
561 250 : va_start( args, fmt );
562 250 : osWork.vPrintf( fmt, args );
563 250 : va_end( args );
564 :
565 250 : VSIFPrintfL(fpOutput, "%s%s", osWork.c_str(), pszEOL);
566 250 : }
|