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 237 : OGRGPXDataSource::OGRGPXDataSource()
44 :
45 : {
46 237 : lastGPXGeomTypeWritten = GPX_NONE;
47 237 : bUseExtensions = FALSE;
48 237 : pszExtensionsNS = NULL;
49 :
50 237 : papoLayers = NULL;
51 237 : nLayers = 0;
52 :
53 237 : fpOutput = NULL;
54 237 : nOffsetBounds = -1;
55 237 : dfMinLat = 90;
56 237 : dfMinLon = 180;
57 237 : dfMaxLat = -90;
58 237 : dfMaxLon = -180;
59 :
60 237 : pszName = NULL;
61 237 : pszVersion = NULL;
62 :
63 237 : bIsBackSeekable = TRUE;
64 237 : pszEOL = "\n";
65 :
66 237 : nLastRteId = -1;
67 237 : nLastTrkId = -1;
68 237 : nLastTrkSegId = -1;
69 237 : }
70 :
71 : /************************************************************************/
72 : /* ~OGRGPXDataSource() */
73 : /************************************************************************/
74 :
75 237 : OGRGPXDataSource::~OGRGPXDataSource()
76 :
77 : {
78 237 : 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 274 : for( int i = 0; i < nLayers; i++ )
108 37 : delete papoLayers[i];
109 237 : CPLFree( papoLayers );
110 237 : CPLFree( pszExtensionsNS );
111 237 : CPLFree( pszName );
112 237 : CPLFree( pszVersion );
113 237 : }
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 7 : OGRLayer * OGRGPXDataSource::CreateLayer( const char * pszLayerName,
148 : OGRSpatialReference *poSRS,
149 : OGRwkbGeometryType eType,
150 : 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 1989 : void OGRGPXDataSource::startElementValidateCbk(const char *pszName, const char **ppszAttr)
206 : {
207 1989 : if (validity == GPX_VALIDITY_UNKNOWN)
208 : {
209 36 : 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 30 : validity = GPX_VALIDITY_INVALID;
225 : }
226 : }
227 1953 : 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 1989 : }
236 :
237 :
238 : /************************************************************************/
239 : /* dataHandlerValidateCbk() */
240 : /************************************************************************/
241 :
242 6499 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
243 : {
244 6499 : nDataHandlerCounter ++;
245 6499 : 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 6499 : }
251 :
252 :
253 1989 : static void XMLCALL startElementValidateCbk(void *pUserData, const char *pszName, const char **ppszAttr)
254 : {
255 1989 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
256 1989 : poDS->startElementValidateCbk(pszName, ppszAttr);
257 1989 : }
258 :
259 6499 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data, int nLen)
260 : {
261 6499 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
262 6499 : poDS->dataHandlerValidateCbk(data, nLen);
263 6499 : }
264 : #endif
265 :
266 : /************************************************************************/
267 : /* Open() */
268 : /************************************************************************/
269 :
270 233 : int OGRGPXDataSource::Open( const char * pszFilename, int bUpdateIn)
271 :
272 : {
273 233 : 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 233 : pszName = CPLStrdup( pszFilename );
281 :
282 : /* -------------------------------------------------------------------- */
283 : /* Try to open the file. */
284 : /* -------------------------------------------------------------------- */
285 233 : VSILFILE* fp = VSIFOpenL(pszFilename, "r");
286 233 : if (fp == NULL)
287 67 : return FALSE;
288 :
289 166 : validity = GPX_VALIDITY_UNKNOWN;
290 166 : CPLFree(pszVersion);
291 166 : pszVersion = NULL;
292 166 : bUseExtensions = FALSE;
293 166 : nElementsRead = 0;
294 :
295 166 : XML_Parser oParser = OGRCreateExpatXMLParser();
296 166 : oCurrentParser = oParser;
297 166 : XML_SetUserData(oParser, this);
298 166 : XML_SetElementHandler(oParser, ::startElementValidateCbk, NULL);
299 166 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
300 :
301 : char aBuf[BUFSIZ];
302 : int nDone;
303 : unsigned int nLen;
304 166 : 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 7 : do
311 : {
312 166 : nDataHandlerCounter = 0;
313 166 : nLen = (unsigned int) VSIFReadL( aBuf, 1, sizeof(aBuf), fp );
314 166 : nDone = VSIFEofL(fp);
315 166 : if (XML_Parse(oParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
316 : {
317 127 : if (nLen <= BUFSIZ-1)
318 90 : aBuf[nLen] = 0;
319 : else
320 37 : aBuf[BUFSIZ-1] = 0;
321 127 : 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 127 : validity = GPX_VALIDITY_INVALID;
330 127 : break;
331 : }
332 39 : if (validity == GPX_VALIDITY_INVALID)
333 : {
334 29 : break;
335 : }
336 10 : 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 6 : if (bUseExtensions)
342 3 : break;
343 3 : 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 4 : nCount ++;
351 4 : if (nCount == 50)
352 0 : break;
353 : }
354 : } while (!nDone && nLen > 0 );
355 :
356 166 : XML_ParserFree(oParser);
357 :
358 166 : VSIFCloseL(fp);
359 :
360 166 : if (validity == GPX_VALIDITY_VALID)
361 : {
362 6 : CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
363 6 : if (bUseExtensions)
364 3 : CPLDebug("GPX", "It uses <extensions>");
365 :
366 6 : 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 6 : 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 6 : nLayers = 5;
385 6 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
386 6 : papoLayers[0] = new OGRGPXLayer( pszName, "waypoints", GPX_WPT, this, FALSE );
387 12 : papoLayers[1] = new OGRGPXLayer( pszName, "routes", GPX_ROUTE, this, FALSE );
388 12 : papoLayers[2] = new OGRGPXLayer( pszName, "tracks", GPX_TRACK, this, FALSE );
389 12 : papoLayers[3] = new OGRGPXLayer( pszName, "route_points", GPX_ROUTE_POINT, this, FALSE );
390 12 : papoLayers[4] = new OGRGPXLayer( pszName, "track_points", GPX_TRACK_POINT, this, FALSE );
391 : }
392 :
393 166 : 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 4 : int OGRGPXDataSource::Create( const char *pszFilename,
418 : char **papszOptions )
419 : {
420 4 : if( fpOutput != NULL)
421 : {
422 0 : CPLAssert( FALSE );
423 0 : return FALSE;
424 : }
425 :
426 4 : if (strcmp(pszFilename, "/dev/stdout") == 0)
427 0 : pszFilename = "/vsistdout/";
428 :
429 : /* -------------------------------------------------------------------- */
430 : /* Do not override exiting file. */
431 : /* -------------------------------------------------------------------- */
432 : VSIStatBufL sStatBuf;
433 :
434 4 : 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 4 : pszName = CPLStrdup( pszFilename );
447 :
448 4 : if( strcmp(pszName, "/vsistdout/") == 0 )
449 : {
450 0 : bIsBackSeekable = FALSE;
451 0 : fpOutput = VSIFOpenL( pszFilename, "w" );
452 : }
453 : else
454 4 : fpOutput = VSIFOpenL( pszFilename, "w+" );
455 4 : 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 4 : const char *pszCRLFFormat = CSLFetchNameValue( papszOptions, "LINEFORMAT");
467 :
468 : int bUseCRLF;
469 4 : if( pszCRLFFormat == NULL )
470 : {
471 : #ifdef WIN32
472 : bUseCRLF = TRUE;
473 : #else
474 3 : bUseCRLF = FALSE;
475 : #endif
476 : }
477 1 : else if( EQUAL(pszCRLFFormat,"CRLF") )
478 0 : bUseCRLF = TRUE;
479 1 : else if( EQUAL(pszCRLFFormat,"LF") )
480 1 : 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 4 : pszEOL = (bUseCRLF) ? "\r\n" : "\n";
493 :
494 : /* -------------------------------------------------------------------- */
495 : /* Look at use extensions options. */
496 : /* -------------------------------------------------------------------- */
497 4 : const char* pszUseExtensions = CSLFetchNameValue( papszOptions, "GPX_USE_EXTENSIONS");
498 4 : const char* pszExtensionsNSURL = NULL;
499 4 : if (pszUseExtensions && CSLTestBoolean(pszUseExtensions))
500 : {
501 1 : bUseExtensions = TRUE;
502 :
503 1 : const char* pszExtensionsNSOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS");
504 1 : const char* pszExtensionsNSURLOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS_URL");
505 1 : if (pszExtensionsNSOption && pszExtensionsNSURLOption)
506 : {
507 0 : pszExtensionsNS = CPLStrdup(pszExtensionsNSOption);
508 0 : pszExtensionsNSURL = pszExtensionsNSURLOption;
509 : }
510 : else
511 : {
512 1 : pszExtensionsNS = CPLStrdup("ogr");
513 1 : pszExtensionsNSURL = "http://osgeo.org/gdal";
514 : }
515 : }
516 :
517 : /* -------------------------------------------------------------------- */
518 : /* Output header of GPX file. */
519 : /* -------------------------------------------------------------------- */
520 4 : PrintLine("<?xml version=\"1.0\"?>");
521 4 : VSIFPrintfL(fpOutput, "<gpx version=\"1.1\" creator=\"GDAL " GDAL_RELEASE_NAME "\" ");
522 4 : VSIFPrintfL(fpOutput, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
523 4 : if (bUseExtensions)
524 1 : VSIFPrintfL(fpOutput, "xmlns:%s=\"%s\" ", pszExtensionsNS, pszExtensionsNSURL);
525 4 : VSIFPrintfL(fpOutput, "xmlns=\"http://www.topografix.com/GPX/1/1\" ");
526 4 : PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
527 4 : if (bIsBackSeekable)
528 : {
529 : /* Reserve space for <metadata><bounds/></metadata> */
530 : char szMetadata[SPACE_FOR_METADATA+1];
531 4 : memset(szMetadata, ' ', SPACE_FOR_METADATA);
532 4 : szMetadata[SPACE_FOR_METADATA] = '\0';
533 4 : nOffsetBounds = (int) VSIFTellL(fpOutput);
534 4 : PrintLine("%s", szMetadata);
535 : }
536 :
537 4 : return TRUE;
538 : }
539 :
540 : /************************************************************************/
541 : /* AddCoord() */
542 : /************************************************************************/
543 :
544 20 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
545 : {
546 20 : if (dfLon < dfMinLon) dfMinLon = dfLon;
547 20 : if (dfLat < dfMinLat) dfMinLat = dfLat;
548 20 : if (dfLon > dfMaxLon) dfMaxLon = dfLon;
549 20 : if (dfLat > dfMaxLat) dfMaxLat = dfLat;
550 20 : }
551 :
552 : /************************************************************************/
553 : /* PrintLine() */
554 : /************************************************************************/
555 :
556 125 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
557 : {
558 125 : CPLString osWork;
559 : va_list args;
560 :
561 125 : va_start( args, fmt );
562 125 : osWork.vPrintf( fmt, args );
563 125 : va_end( args );
564 :
565 125 : VSIFPrintfL(fpOutput, "%s%s", osWork.c_str(), pszEOL);
566 125 : }
|