1 : /******************************************************************************
2 : * $Id: ogrgpxdatasource.cpp 17848 2009-10-17 12:59:16Z 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 17848 2009-10-17 12:59:16Z rouault $");
36 :
37 : #define SPACE_FOR_METADATA 160
38 :
39 : /************************************************************************/
40 : /* OGRGPXDataSource() */
41 : /************************************************************************/
42 :
43 66 : OGRGPXDataSource::OGRGPXDataSource()
44 :
45 : {
46 66 : lastGPXGeomTypeWritten = GPX_NONE;
47 66 : bUseExtensions = FALSE;
48 66 : pszExtensionsNS = NULL;
49 :
50 66 : papoLayers = NULL;
51 66 : nLayers = 0;
52 :
53 66 : fpOutput = NULL;
54 66 : nOffsetBounds = -1;
55 66 : dfMinLat = 90;
56 66 : dfMinLon = 180;
57 66 : dfMaxLat = -90;
58 66 : dfMaxLon = -180;
59 :
60 66 : pszName = NULL;
61 66 : pszVersion = NULL;
62 66 : }
63 :
64 : /************************************************************************/
65 : /* ~OGRGPXDataSource() */
66 : /************************************************************************/
67 :
68 132 : OGRGPXDataSource::~OGRGPXDataSource()
69 :
70 : {
71 66 : if ( fpOutput != NULL )
72 : {
73 2 : VSIFPrintf(fpOutput, "</gpx>\n");
74 2 : if ( fpOutput != stdout )
75 : {
76 : /* Write the <bound> element in the reserved space */
77 2 : if (dfMinLon <= dfMaxLon)
78 : {
79 : char szMetadata[SPACE_FOR_METADATA+1];
80 : sprintf(szMetadata, "<metadata><bounds minlat=\"%.15f\" minlon=\"%.15f\" maxlat=\"%.15f\" maxlon=\"%.15f\"/></metadata>",
81 2 : dfMinLat, dfMinLon, dfMaxLat, dfMaxLon);
82 2 : VSIFSeek(fpOutput, nOffsetBounds, SEEK_SET);
83 2 : VSIFWrite(szMetadata, 1, strlen(szMetadata), fpOutput);
84 : }
85 2 : VSIFClose( fpOutput);
86 : }
87 : }
88 :
89 85 : for( int i = 0; i < nLayers; i++ )
90 19 : delete papoLayers[i];
91 66 : CPLFree( papoLayers );
92 66 : CPLFree( pszExtensionsNS );
93 66 : CPLFree( pszName );
94 66 : CPLFree( pszVersion );
95 132 : }
96 :
97 : /************************************************************************/
98 : /* TestCapability() */
99 : /************************************************************************/
100 :
101 0 : int OGRGPXDataSource::TestCapability( const char * pszCap )
102 :
103 : {
104 0 : if( EQUAL(pszCap,ODsCCreateLayer) )
105 0 : return TRUE;
106 0 : else if( EQUAL(pszCap,ODsCDeleteLayer) )
107 0 : return FALSE;
108 : else
109 0 : return FALSE;
110 : }
111 :
112 : /************************************************************************/
113 : /* GetLayer() */
114 : /************************************************************************/
115 :
116 28 : OGRLayer *OGRGPXDataSource::GetLayer( int iLayer )
117 :
118 : {
119 28 : if( iLayer < 0 || iLayer >= nLayers )
120 0 : return NULL;
121 : else
122 28 : return papoLayers[iLayer];
123 : }
124 :
125 : /************************************************************************/
126 : /* CreateLayer() */
127 : /************************************************************************/
128 :
129 4 : OGRLayer * OGRGPXDataSource::CreateLayer( const char * pszLayerName,
130 : OGRSpatialReference *poSRS,
131 : OGRwkbGeometryType eType,
132 : char ** papszOptions )
133 :
134 : {
135 : GPXGeometryType gpxGeomType;
136 6 : if (eType == wkbPoint || eType == wkbPoint25D)
137 : {
138 2 : gpxGeomType = GPX_WPT;
139 : }
140 3 : else if (eType == wkbLineString || eType == wkbLineString25D)
141 : {
142 1 : const char *pszForceGPXTrack = CSLFetchNameValue( papszOptions, "FORCE_GPX_TRACK");
143 1 : if (pszForceGPXTrack && CSLTestBoolean(pszForceGPXTrack))
144 0 : gpxGeomType = GPX_TRACK;
145 : else
146 1 : gpxGeomType = GPX_ROUTE;
147 : }
148 2 : else if (eType == wkbMultiLineString || eType == wkbMultiLineString25D)
149 : {
150 1 : const char *pszForceGPXRoute = CSLFetchNameValue( papszOptions, "FORCE_GPX_ROUTE");
151 1 : if (pszForceGPXRoute && CSLTestBoolean(pszForceGPXRoute))
152 0 : gpxGeomType = GPX_ROUTE;
153 : else
154 1 : gpxGeomType = GPX_TRACK;
155 : }
156 0 : else if (eType == wkbUnknown)
157 : {
158 : CPLError(CE_Failure, CPLE_NotSupported,
159 0 : "Cannot create GPX layer %s with unknown geometry type", pszLayerName);
160 0 : return NULL;
161 : }
162 : else
163 : {
164 : CPLError( CE_Failure, CPLE_NotSupported,
165 : "Geometry type of `%s' not supported in GPX.\n",
166 0 : OGRGeometryTypeToName(eType) );
167 0 : return NULL;
168 : }
169 4 : nLayers++;
170 4 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
171 4 : papoLayers[nLayers-1] = new OGRGPXLayer( pszName, pszLayerName, gpxGeomType, this, TRUE );
172 :
173 4 : return papoLayers[nLayers-1];
174 : }
175 :
176 : #ifdef HAVE_EXPAT
177 :
178 : /************************************************************************/
179 : /* startElementValidateCbk() */
180 : /************************************************************************/
181 :
182 645 : void OGRGPXDataSource::startElementValidateCbk(const char *pszName, const char **ppszAttr)
183 : {
184 645 : if (validity == GPX_VALIDITY_UNKNOWN)
185 : {
186 20 : if (strcmp(pszName, "gpx") == 0)
187 : {
188 : int i;
189 3 : validity = GPX_VALIDITY_VALID;
190 4 : for(i=0; ppszAttr[i] != NULL; i+= 2)
191 : {
192 4 : if (strcmp(ppszAttr[i], "version") == 0)
193 : {
194 3 : pszVersion = CPLStrdup(ppszAttr[i+1]);
195 3 : break;
196 : }
197 : }
198 : }
199 : else
200 : {
201 17 : validity = GPX_VALIDITY_INVALID;
202 : }
203 : }
204 625 : else if (validity == GPX_VALIDITY_VALID)
205 : {
206 96 : if (strcmp(pszName, "extensions") == 0)
207 : {
208 3 : bUseExtensions = TRUE;
209 : }
210 96 : nElementsRead++;
211 : }
212 645 : }
213 :
214 :
215 : /************************************************************************/
216 : /* dataHandlerValidateCbk() */
217 : /************************************************************************/
218 :
219 1912 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
220 : {
221 1912 : nDataHandlerCounter ++;
222 1912 : if (nDataHandlerCounter >= BUFSIZ)
223 : {
224 0 : CPLError(CE_Failure, CPLE_AppDefined, "File probably corrupted (million laugh pattern)");
225 0 : XML_StopParser(oCurrentParser, XML_FALSE);
226 : }
227 1912 : }
228 :
229 :
230 645 : static void XMLCALL startElementValidateCbk(void *pUserData, const char *pszName, const char **ppszAttr)
231 : {
232 645 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
233 645 : poDS->startElementValidateCbk(pszName, ppszAttr);
234 645 : }
235 :
236 1912 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data, int nLen)
237 : {
238 1912 : OGRGPXDataSource* poDS = (OGRGPXDataSource*) pUserData;
239 1912 : poDS->dataHandlerValidateCbk(data, nLen);
240 1912 : }
241 : #endif
242 :
243 : /************************************************************************/
244 : /* Open() */
245 : /************************************************************************/
246 :
247 64 : int OGRGPXDataSource::Open( const char * pszFilename, int bUpdateIn)
248 :
249 : {
250 64 : if (bUpdateIn)
251 : {
252 : CPLError(CE_Failure, CPLE_NotSupported,
253 0 : "OGR/GPX driver does not support opening a file in update mode");
254 0 : return FALSE;
255 : }
256 : #ifdef HAVE_EXPAT
257 64 : pszName = CPLStrdup( pszFilename );
258 :
259 : /* -------------------------------------------------------------------- */
260 : /* Determine what sort of object this is. */
261 : /* -------------------------------------------------------------------- */
262 : VSIStatBufL sStatBuf;
263 :
264 64 : if( VSIStatL( pszFilename, &sStatBuf ) != 0 )
265 5 : return FALSE;
266 :
267 59 : if( VSI_ISDIR(sStatBuf.st_mode) )
268 1 : return FALSE;
269 :
270 58 : FILE* fp = VSIFOpenL(pszFilename, "r");
271 58 : if (fp == NULL)
272 0 : return FALSE;
273 :
274 58 : validity = GPX_VALIDITY_UNKNOWN;
275 58 : CPLFree(pszVersion);
276 58 : pszVersion = NULL;
277 58 : bUseExtensions = FALSE;
278 58 : nElementsRead = 0;
279 :
280 58 : XML_Parser oParser = OGRCreateExpatXMLParser();
281 58 : oCurrentParser = oParser;
282 58 : XML_SetUserData(oParser, this);
283 58 : XML_SetElementHandler(oParser, ::startElementValidateCbk, NULL);
284 58 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
285 :
286 : char aBuf[BUFSIZ];
287 : int nDone;
288 : unsigned int nLen;
289 58 : int nCount = 0;
290 :
291 : /* Begin to parse the file and look for the <gpx> element */
292 : /* It *MUST* be the first element of an XML file */
293 : /* So once we have read the first element, we know if we can */
294 : /* handle the file or not with that driver */
295 1 : do
296 : {
297 58 : nDataHandlerCounter = 0;
298 58 : nLen = (unsigned int) VSIFReadL( aBuf, 1, sizeof(aBuf), fp );
299 58 : nDone = VSIFEofL(fp);
300 58 : if (XML_Parse(oParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
301 : {
302 39 : if (nLen <= BUFSIZ-1)
303 29 : aBuf[nLen] = 0;
304 : else
305 10 : aBuf[BUFSIZ-1] = 0;
306 39 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
307 : {
308 : CPLError(CE_Failure, CPLE_AppDefined,
309 : "XML parsing of GPX file failed : %s at line %d, column %d",
310 : XML_ErrorString(XML_GetErrorCode(oParser)),
311 : (int)XML_GetCurrentLineNumber(oParser),
312 0 : (int)XML_GetCurrentColumnNumber(oParser));
313 : }
314 39 : validity = GPX_VALIDITY_INVALID;
315 39 : break;
316 : }
317 19 : if (validity == GPX_VALIDITY_INVALID)
318 : {
319 16 : break;
320 : }
321 3 : else if (validity == GPX_VALIDITY_VALID)
322 : {
323 : /* If we have recognized the <gpx> element, now we try */
324 : /* to recognize if they are <extensions> tags */
325 : /* But we stop to look for after an arbitrary number of tags */
326 3 : if (bUseExtensions)
327 2 : break;
328 1 : else if (nElementsRead > 200)
329 0 : break;
330 : }
331 : else
332 : {
333 : /* After reading 50 * BUFSIZE bytes, and not finding whether the file */
334 : /* is GPX or not, we give up and fail silently */
335 0 : nCount ++;
336 0 : if (nCount == 50)
337 0 : break;
338 : }
339 : } while (!nDone && nLen > 0 );
340 :
341 58 : XML_ParserFree(oParser);
342 :
343 58 : VSIFCloseL(fp);
344 :
345 58 : if (validity == GPX_VALIDITY_VALID)
346 : {
347 3 : CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
348 3 : if (bUseExtensions)
349 2 : CPLDebug("GPX", "It uses <extensions>");
350 :
351 3 : if (pszVersion == NULL)
352 : {
353 : /* Default to 1.1 */
354 : CPLError(CE_Warning, CPLE_AppDefined, "GPX schema version is unknown. "
355 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.");
356 0 : pszVersion = CPLStrdup("1.1");
357 : }
358 3 : else if (strcmp(pszVersion, "1.0") == 0 || strcmp(pszVersion, "1.1") == 0)
359 : {
360 : /* Fine */
361 : }
362 : else
363 : {
364 : CPLError(CE_Warning, CPLE_AppDefined,
365 : "GPX schema version '%s' is not handled by the driver. "
366 0 : "The driver may not be able to handle the file correctly and will behave as if it is GPX 1.1.", pszVersion);
367 : }
368 :
369 3 : nLayers = 5;
370 3 : papoLayers = (OGRGPXLayer **) CPLRealloc(papoLayers, nLayers * sizeof(OGRGPXLayer*));
371 3 : papoLayers[0] = new OGRGPXLayer( pszName, "waypoints", GPX_WPT, this, FALSE );
372 6 : papoLayers[1] = new OGRGPXLayer( pszName, "routes", GPX_ROUTE, this, FALSE );
373 6 : papoLayers[2] = new OGRGPXLayer( pszName, "tracks", GPX_TRACK, this, FALSE );
374 6 : papoLayers[3] = new OGRGPXLayer( pszName, "route_points", GPX_ROUTE_POINT, this, FALSE );
375 6 : papoLayers[4] = new OGRGPXLayer( pszName, "track_points", GPX_TRACK_POINT, this, FALSE );
376 : }
377 :
378 58 : return (validity == GPX_VALIDITY_VALID);
379 : #else
380 : char aBuf[256];
381 : FILE* fp = VSIFOpenL(pszFilename, "r");
382 : if (fp)
383 : {
384 : unsigned int nLen = (unsigned int)VSIFReadL( aBuf, 1, 255, fp );
385 : aBuf[nLen] = 0;
386 : if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
387 : {
388 : CPLError(CE_Failure, CPLE_NotSupported,
389 : "OGR/GPX driver has not been built with read support. Expat library required");
390 : }
391 : VSIFCloseL(fp);
392 : }
393 : return FALSE;
394 : #endif
395 : }
396 :
397 :
398 : /************************************************************************/
399 : /* Create() */
400 : /************************************************************************/
401 :
402 2 : int OGRGPXDataSource::Create( const char *pszFilename,
403 : char **papszOptions )
404 : {
405 2 : if( fpOutput != NULL)
406 : {
407 : CPLAssert( FALSE );
408 0 : return FALSE;
409 : }
410 :
411 : /* -------------------------------------------------------------------- */
412 : /* Do not override exiting file. */
413 : /* -------------------------------------------------------------------- */
414 : VSIStatBufL sStatBuf;
415 :
416 2 : if( VSIStatL( pszFilename, &sStatBuf ) == 0 )
417 : {
418 : CPLError(CE_Failure, CPLE_NotSupported,
419 : "You have to delete %s before being able to create it with the GPX driver",
420 0 : pszFilename);
421 0 : return FALSE;
422 : }
423 :
424 : /* -------------------------------------------------------------------- */
425 : /* Create the output file. */
426 : /* -------------------------------------------------------------------- */
427 2 : pszName = CPLStrdup( pszFilename );
428 :
429 2 : if( EQUAL(pszFilename,"stdout") )
430 0 : fpOutput = stdout;
431 : else
432 2 : fpOutput = VSIFOpen( pszFilename, "w+" );
433 2 : if( fpOutput == NULL )
434 : {
435 : CPLError( CE_Failure, CPLE_OpenFailed,
436 : "Failed to create GPX file %s.",
437 0 : pszFilename );
438 0 : return FALSE;
439 : }
440 :
441 : /* -------------------------------------------------------------------- */
442 : /* Look at use extensions options. */
443 : /* -------------------------------------------------------------------- */
444 2 : const char* pszUseExtensions = CSLFetchNameValue( papszOptions, "GPX_USE_EXTENSIONS");
445 2 : const char* pszExtensionsNSURL = NULL;
446 2 : if (pszUseExtensions && CSLTestBoolean(pszUseExtensions))
447 : {
448 1 : bUseExtensions = TRUE;
449 :
450 1 : const char* pszExtensionsNSOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS");
451 1 : const char* pszExtensionsNSURLOption = CSLFetchNameValue( papszOptions, "GPX_EXTENSIONS_NS_URL");
452 1 : if (pszExtensionsNSOption && pszExtensionsNSURLOption)
453 : {
454 0 : pszExtensionsNS = CPLStrdup(pszExtensionsNSOption);
455 0 : pszExtensionsNSURL = pszExtensionsNSURLOption;
456 : }
457 : else
458 : {
459 1 : pszExtensionsNS = CPLStrdup("ogr");
460 1 : pszExtensionsNSURL = "http://osgeo.org/gdal";
461 : }
462 : }
463 :
464 : /* -------------------------------------------------------------------- */
465 : /* Output header of GPX file. */
466 : /* -------------------------------------------------------------------- */
467 2 : VSIFPrintf(fpOutput, "<?xml version=\"1.0\"?>\n");
468 2 : VSIFPrintf(fpOutput, "<gpx version=\"1.1\" creator=\"GDAL " GDAL_RELEASE_NAME "\" ");
469 2 : VSIFPrintf(fpOutput, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
470 2 : if (bUseExtensions)
471 1 : VSIFPrintf(fpOutput, "xmlns:%s=\"%s\" ", pszExtensionsNS, pszExtensionsNSURL);
472 2 : VSIFPrintf(fpOutput, "xmlns=\"http://www.topografix.com/GPX/1/1\" ");
473 2 : VSIFPrintf(fpOutput, "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n");
474 2 : if (fpOutput != stdout)
475 : {
476 : /* Reserve space for <metadata><bounds/></metadata> */
477 : char szMetadata[SPACE_FOR_METADATA+1];
478 2 : memset(szMetadata, ' ', SPACE_FOR_METADATA);
479 2 : szMetadata[SPACE_FOR_METADATA] = '\0';
480 2 : nOffsetBounds = VSIFTell(fpOutput);
481 2 : VSIFPrintf(fpOutput, "%s\n", szMetadata);
482 : }
483 :
484 2 : return TRUE;
485 : }
486 :
487 : /************************************************************************/
488 : /* AddCoord() */
489 : /************************************************************************/
490 :
491 11 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
492 : {
493 11 : if (dfLon < dfMinLon) dfMinLon = dfLon;
494 11 : if (dfLat < dfMinLat) dfMinLat = dfLat;
495 11 : if (dfLon > dfMaxLon) dfMaxLon = dfLon;
496 11 : if (dfLat > dfMaxLat) dfMaxLat = dfLat;
497 11 : }
|