1 : /******************************************************************************
2 : * $Id: ogrgpxdatasource.cpp 19794 2010-06-02 21:40:26Z 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 19794 2010-06-02 21:40:26Z rouault $");
36 :
37 : #define SPACE_FOR_METADATA 160
38 :
39 : /************************************************************************/
40 : /* OGRGPXDataSource() */
41 : /************************************************************************/
42 :
43 105 : OGRGPXDataSource::OGRGPXDataSource()
44 :
45 : {
46 105 : lastGPXGeomTypeWritten = GPX_NONE;
47 105 : bUseExtensions = FALSE;
48 105 : pszExtensionsNS = NULL;
49 :
50 105 : papoLayers = NULL;
51 105 : nLayers = 0;
52 :
53 105 : fpOutput = NULL;
54 105 : nOffsetBounds = -1;
55 105 : dfMinLat = 90;
56 105 : dfMinLon = 180;
57 105 : dfMaxLat = -90;
58 105 : dfMaxLon = -180;
59 :
60 105 : pszName = NULL;
61 105 : pszVersion = NULL;
62 :
63 105 : bIsBackSeekable = TRUE;
64 105 : pszEOL = "\n";
65 :
66 105 : nLastRteId = -1;
67 105 : nLastTrkId = -1;
68 105 : nLastTrkSegId = -1;
69 105 : }
70 :
71 : /************************************************************************/
72 : /* ~OGRGPXDataSource() */
73 : /************************************************************************/
74 :
75 105 : OGRGPXDataSource::~OGRGPXDataSource()
76 :
77 : {
78 105 : if ( fpOutput != NULL )
79 : {
80 4 : if (nLastRteId != -1)
81 0 : PrintLine("</rte>");
82 4 : else if (nLastTrkId != -1)
83 : {
84 2 : PrintLine(" </trkseg>");
85 2 : PrintLine("</trk>");
86 : }
87 4 : PrintLine("</gpx>");
88 4 : if ( bIsBackSeekable )
89 : {
90 : /* Write the <bound> element in the reserved space */
91 4 : 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 4 : dfMinLat, dfMinLon, dfMaxLat, dfMaxLon);
97 4 : if (nRet < SPACE_FOR_METADATA)
98 : {
99 4 : VSIFSeekL(fpOutput, nOffsetBounds, SEEK_SET);
100 4 : VSIFWriteL(szMetadata, 1, strlen(szMetadata), fpOutput);
101 : }
102 : }
103 4 : VSIFCloseL( fpOutput);
104 : }
105 : }
106 :
107 142 : for( int i = 0; i < nLayers; i++ )
108 37 : delete papoLayers[i];
109 105 : CPLFree( papoLayers );
110 105 : CPLFree( pszExtensionsNS );
111 105 : CPLFree( pszName );
112 105 : CPLFree( pszVersion );
113 105 : }
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 58 : OGRLayer *OGRGPXDataSource::GetLayer( int iLayer )
135 :
136 : {
137 58 : if( iLayer < 0 || iLayer >= nLayers )
138 0 : return NULL;
139 : else
140 58 : return papoLayers[iLayer];
141 : }
142 :
143 : /************************************************************************/
144 : /* CreateLayer() */
145 : /************************************************************************/
146 :
147 : OGRLayer * OGRGPXDataSource::CreateLayer( const char * pszLayerName,
148 : OGRSpatialReference *poSRS,
149 : OGRwkbGeometryType eType,
150 7 : char ** papszOptions )
151 :
152 : {
153 : GPXGeometryType gpxGeomType;
154 12 : if (eType == wkbPoint || eType == wkbPoint25D)
155 : {
156 5 : if (EQUAL(pszLayerName, "track_points"))
157 2 : gpxGeomType = GPX_TRACK_POINT;
158 3 : else if (EQUAL(pszLayerName, "route_points"))
159 1 : gpxGeomType = GPX_ROUTE_POINT;
160 : else
161 2 : gpxGeomType = GPX_WPT;
162 : }
163 3 : else if (eType == wkbLineString || eType == wkbLineString25D)
164 : {
165 1 : const char *pszForceGPXTrack = CSLFetchNameValue( papszOptions, "FORCE_GPX_TRACK");
166 1 : if (pszForceGPXTrack && CSLTestBoolean(pszForceGPXTrack))
167 0 : gpxGeomType = GPX_TRACK;
168 : else
169 1 : gpxGeomType = GPX_ROUTE;
170 : }
171 2 : else if (eType == wkbMultiLineString || eType == wkbMultiLineString25D)
172 : {
173 1 : const char *pszForceGPXRoute = CSLFetchNameValue( papszOptions, "FORCE_GPX_ROUTE");
174 1 : if (pszForceGPXRoute && CSLTestBoolean(pszForceGPXRoute))
175 0 : gpxGeomType = GPX_ROUTE;
176 : else
177 1 : 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 7 : nLayers++;
193 7 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
194 7 : papoLayers[nLayers-1] = new OGRGPXLayer( pszName, pszLayerName, gpxGeomType, this, TRUE );
195 :
196 7 : return papoLayers[nLayers-1];
197 : }
198 :
199 : #ifdef HAVE_EXPAT
200 :
201 : /************************************************************************/
202 : /* startElementValidateCbk() */
203 : /************************************************************************/
204 :
205 1167 : void OGRGPXDataSource::startElementValidateCbk(const char *pszName, const char **ppszAttr)
206 : {
207 1167 : if (validity == GPX_VALIDITY_UNKNOWN)
208 : {
209 28 : if (strcmp(pszName, "gpx") == 0)
210 : {
211 : int i;
212 6 : validity = GPX_VALIDITY_VALID;
213 8 : for(i=0; ppszAttr[i] != NULL; i+= 2)
214 : {
215 8 : if (strcmp(ppszAttr[i], "version") == 0)
216 : {
217 6 : pszVersion = CPLStrdup(ppszAttr[i+1]);
218 6 : break;
219 : }
220 : }
221 : }
222 : else
223 : {
224 22 : validity = GPX_VALIDITY_INVALID;
225 : }
226 : }
227 1139 : else if (validity == GPX_VALIDITY_VALID)
228 : {
229 170 : if (strcmp(pszName, "extensions") == 0)
230 : {
231 4 : bUseExtensions = TRUE;
232 : }
233 170 : nElementsRead++;
234 : }
235 1167 : }
236 :
237 :
238 : /************************************************************************/
239 : /* dataHandlerValidateCbk() */
240 : /************************************************************************/
241 :
242 3424 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
243 : {
244 3424 : nDataHandlerCounter ++;
245 3424 : 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 3424 : }
251 :
252 :
253 1167 : static void XMLCALL startElementValidateCbk(void *pUserData, const char *pszName, const char **ppszAttr)
254 : {
255 1167 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
256 1167 : poDS->startElementValidateCbk(pszName, ppszAttr);
257 1167 : }
258 :
259 3424 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data, int nLen)
260 : {
261 3424 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
262 3424 : poDS->dataHandlerValidateCbk(data, nLen);
263 3424 : }
264 : #endif
265 :
266 : /************************************************************************/
267 : /* Open() */
268 : /************************************************************************/
269 :
270 101 : int OGRGPXDataSource::Open( const char * pszFilename, int bUpdateIn)
271 :
272 : {
273 101 : 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 101 : pszName = CPLStrdup( pszFilename );
281 :
282 : /* -------------------------------------------------------------------- */
283 : /* Determine what sort of object this is. */
284 : /* -------------------------------------------------------------------- */
285 : VSIStatBufL sStatBuf;
286 :
287 101 : if( VSIStatL( pszFilename, &sStatBuf ) != 0 )
288 7 : return FALSE;
289 :
290 94 : if( VSI_ISDIR(sStatBuf.st_mode) )
291 1 : return FALSE;
292 :
293 93 : FILE* fp = VSIFOpenL(pszFilename, "r");
294 93 : if (fp == NULL)
295 0 : return FALSE;
296 :
297 93 : validity = GPX_VALIDITY_UNKNOWN;
298 93 : CPLFree(pszVersion);
299 93 : pszVersion = NULL;
300 93 : bUseExtensions = FALSE;
301 93 : nElementsRead = 0;
302 :
303 93 : XML_Parser oParser = OGRCreateExpatXMLParser();
304 93 : oCurrentParser = oParser;
305 93 : XML_SetUserData(oParser, this);
306 93 : XML_SetElementHandler(oParser, ::startElementValidateCbk, NULL);
307 93 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
308 :
309 : char aBuf[BUFSIZ];
310 : int nDone;
311 : unsigned int nLen;
312 93 : int nCount = 0;
313 :
314 : /* Begin to parse the file and look for the <gpx> element */
315 : /* It *MUST* be the first element of an XML file */
316 : /* So once we have read the first element, we know if we can */
317 : /* handle the file or not with that driver */
318 3 : do
319 : {
320 93 : nDataHandlerCounter = 0;
321 93 : nLen = (unsigned int) VSIFReadL( aBuf, 1, sizeof(aBuf), fp );
322 93 : nDone = VSIFEofL(fp);
323 93 : if (XML_Parse(oParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
324 : {
325 66 : if (nLen <= BUFSIZ-1)
326 43 : aBuf[nLen] = 0;
327 : else
328 23 : aBuf[BUFSIZ-1] = 0;
329 66 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
330 : {
331 : CPLError(CE_Failure, CPLE_AppDefined,
332 : "XML parsing of GPX file failed : %s at line %d, column %d",
333 : XML_ErrorString(XML_GetErrorCode(oParser)),
334 : (int)XML_GetCurrentLineNumber(oParser),
335 0 : (int)XML_GetCurrentColumnNumber(oParser));
336 : }
337 66 : validity = GPX_VALIDITY_INVALID;
338 66 : break;
339 : }
340 27 : if (validity == GPX_VALIDITY_INVALID)
341 : {
342 21 : break;
343 : }
344 6 : else if (validity == GPX_VALIDITY_VALID)
345 : {
346 : /* If we have recognized the <gpx> element, now we try */
347 : /* to recognize if they are <extensions> tags */
348 : /* But we stop to look for after an arbitrary number of tags */
349 6 : if (bUseExtensions)
350 3 : break;
351 3 : else if (nElementsRead > 200)
352 0 : break;
353 : }
354 : else
355 : {
356 : /* After reading 50 * BUFSIZE bytes, and not finding whether the file */
357 : /* is GPX or not, we give up and fail silently */
358 0 : nCount ++;
359 0 : if (nCount == 50)
360 0 : break;
361 : }
362 : } while (!nDone && nLen > 0 );
363 :
364 93 : XML_ParserFree(oParser);
365 :
366 93 : VSIFCloseL(fp);
367 :
368 93 : if (validity == GPX_VALIDITY_VALID)
369 : {
370 6 : CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
371 6 : if (bUseExtensions)
372 3 : CPLDebug("GPX", "It uses <extensions>");
373 :
374 6 : if (pszVersion == NULL)
375 : {
376 : /* Default to 1.1 */
377 : CPLError(CE_Warning, CPLE_AppDefined, "GPX schema version is unknown. "
378 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.");
379 0 : pszVersion = CPLStrdup("1.1");
380 : }
381 6 : else if (strcmp(pszVersion, "1.0") == 0 || strcmp(pszVersion, "1.1") == 0)
382 : {
383 : /* Fine */
384 : }
385 : else
386 : {
387 : CPLError(CE_Warning, CPLE_AppDefined,
388 : "GPX schema version '%s' is not handled by the driver. "
389 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.", pszVersion);
390 : }
391 :
392 6 : nLayers = 5;
393 6 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
394 6 : papoLayers[0] = new OGRGPXLayer( pszName, "waypoints", GPX_WPT, this, FALSE );
395 12 : papoLayers[1] = new OGRGPXLayer( pszName, "routes", GPX_ROUTE, this, FALSE );
396 12 : papoLayers[2] = new OGRGPXLayer( pszName, "tracks", GPX_TRACK, this, FALSE );
397 12 : papoLayers[3] = new OGRGPXLayer( pszName, "route_points", GPX_ROUTE_POINT, this, FALSE );
398 12 : papoLayers[4] = new OGRGPXLayer( pszName, "track_points", GPX_TRACK_POINT, this, FALSE );
399 : }
400 :
401 93 : return (validity == GPX_VALIDITY_VALID);
402 : #else
403 : char aBuf[256];
404 : FILE* fp = VSIFOpenL(pszFilename, "r");
405 : if (fp)
406 : {
407 : unsigned int nLen = (unsigned int)VSIFReadL( aBuf, 1, 255, fp );
408 : aBuf[nLen] = 0;
409 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
410 : {
411 : CPLError(CE_Failure, CPLE_NotSupported,
412 : "OGR/GPX driver has not been built with read support. Expat library required");
413 : }
414 : VSIFCloseL(fp);
415 : }
416 : return FALSE;
417 : #endif
418 : }
419 :
420 :
421 : /************************************************************************/
422 : /* Create() */
423 : /************************************************************************/
424 :
425 : int OGRGPXDataSource::Create( const char *pszFilename,
426 4 : char **papszOptions )
427 : {
428 4 : if( fpOutput != NULL)
429 : {
430 0 : CPLAssert( FALSE );
431 0 : return FALSE;
432 : }
433 :
434 : /* -------------------------------------------------------------------- */
435 : /* Do not override exiting file. */
436 : /* -------------------------------------------------------------------- */
437 : VSIStatBufL sStatBuf;
438 :
439 4 : if( VSIStatL( pszFilename, &sStatBuf ) == 0 )
440 : {
441 : CPLError(CE_Failure, CPLE_NotSupported,
442 : "You have to delete %s before being able to create it with the GPX driver",
443 0 : pszFilename);
444 0 : return FALSE;
445 : }
446 :
447 : /* -------------------------------------------------------------------- */
448 : /* Create the output file. */
449 : /* -------------------------------------------------------------------- */
450 4 : pszName = CPLStrdup( pszFilename );
451 :
452 4 : if( EQUAL(pszFilename,"stdout") || EQUAL(pszFilename,"/vsistdout/"))
453 : {
454 0 : bIsBackSeekable = FALSE;
455 0 : fpOutput = VSIFOpenL( "/vsistdout/", "w" );
456 : }
457 : else
458 4 : fpOutput = VSIFOpenL( pszFilename, "w+" );
459 4 : if( fpOutput == NULL )
460 : {
461 : CPLError( CE_Failure, CPLE_OpenFailed,
462 : "Failed to create GPX file %s.",
463 0 : pszFilename );
464 0 : return FALSE;
465 : }
466 :
467 : /* -------------------------------------------------------------------- */
468 : /* End of line character. */
469 : /* -------------------------------------------------------------------- */
470 4 : const char *pszCRLFFormat = CSLFetchNameValue( papszOptions, "LINEFORMAT");
471 :
472 : int bUseCRLF;
473 4 : if( pszCRLFFormat == NULL )
474 : {
475 : #ifdef WIN32
476 : bUseCRLF = TRUE;
477 : #else
478 3 : bUseCRLF = FALSE;
479 : #endif
480 : }
481 1 : else if( EQUAL(pszCRLFFormat,"CRLF") )
482 0 : bUseCRLF = TRUE;
483 1 : else if( EQUAL(pszCRLFFormat,"LF") )
484 1 : bUseCRLF = FALSE;
485 : else
486 : {
487 : CPLError( CE_Warning, CPLE_AppDefined,
488 : "LINEFORMAT=%s not understood, use one of CRLF or LF.",
489 0 : pszCRLFFormat );
490 : #ifdef WIN32
491 : bUseCRLF = TRUE;
492 : #else
493 0 : bUseCRLF = FALSE;
494 : #endif
495 : }
496 4 : pszEOL = (bUseCRLF) ? "\r\n" : "\n";
497 :
498 : /* -------------------------------------------------------------------- */
499 : /* Look at use extensions options. */
500 : /* -------------------------------------------------------------------- */
501 4 : const char* pszUseExtensions = CSLFetchNameValue( papszOptions, "GPX_USE_EXTENSIONS");
502 4 : const char* pszExtensionsNSURL = NULL;
503 4 : if (pszUseExtensions && CSLTestBoolean(pszUseExtensions))
504 : {
505 1 : bUseExtensions = TRUE;
506 :
507 1 : const char* pszExtensionsNSOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS");
508 1 : const char* pszExtensionsNSURLOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS_URL");
509 1 : if (pszExtensionsNSOption && pszExtensionsNSURLOption)
510 : {
511 0 : pszExtensionsNS = CPLStrdup(pszExtensionsNSOption);
512 0 : pszExtensionsNSURL = pszExtensionsNSURLOption;
513 : }
514 : else
515 : {
516 1 : pszExtensionsNS = CPLStrdup("ogr");
517 1 : pszExtensionsNSURL = "http://osgeo.org/gdal";
518 : }
519 : }
520 :
521 : /* -------------------------------------------------------------------- */
522 : /* Output header of GPX file. */
523 : /* -------------------------------------------------------------------- */
524 4 : PrintLine("<?xml version=\"1.0\"?>");
525 4 : VSIFPrintfL(fpOutput, "<gpx version=\"1.1\" creator=\"GDAL " GDAL_RELEASE_NAME "\" ");
526 4 : VSIFPrintfL(fpOutput, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
527 4 : if (bUseExtensions)
528 1 : VSIFPrintfL(fpOutput, "xmlns:%s=\"%s\" ", pszExtensionsNS, pszExtensionsNSURL);
529 4 : VSIFPrintfL(fpOutput, "xmlns=\"http://www.topografix.com/GPX/1/1\" ");
530 4 : PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
531 4 : if (fpOutput != stdout)
532 : {
533 : /* Reserve space for <metadata><bounds/></metadata> */
534 : char szMetadata[SPACE_FOR_METADATA+1];
535 4 : memset(szMetadata, ' ', SPACE_FOR_METADATA);
536 4 : szMetadata[SPACE_FOR_METADATA] = '\0';
537 4 : nOffsetBounds = VSIFTellL(fpOutput);
538 4 : PrintLine("%s", szMetadata);
539 : }
540 :
541 4 : return TRUE;
542 : }
543 :
544 : /************************************************************************/
545 : /* AddCoord() */
546 : /************************************************************************/
547 :
548 20 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
549 : {
550 20 : if (dfLon < dfMinLon) dfMinLon = dfLon;
551 20 : if (dfLat < dfMinLat) dfMinLat = dfLat;
552 20 : if (dfLon > dfMaxLon) dfMaxLon = dfLon;
553 20 : if (dfLat > dfMaxLat) dfMaxLat = dfLat;
554 20 : }
555 :
556 : /************************************************************************/
557 : /* PrintLine() */
558 : /************************************************************************/
559 :
560 125 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
561 : {
562 125 : CPLString osWork;
563 : va_list args;
564 :
565 125 : va_start( args, fmt );
566 125 : osWork.vPrintf( fmt, args );
567 125 : va_end( args );
568 :
569 125 : VSIFPrintfL(fpOutput, "%s%s", osWork.c_str(), pszEOL);
570 125 : }
|