/*************************************************************************** qgsgpxprovider.cpp - Data provider for GPS eXchange files ------------------- begin : 2004-04-14 copyright : (C) 2004 by Lars Luthman email : larsl@users.sourceforge.net Partly based on qgsdelimitedtextprovider.cpp, (C) 2004 Gary E. Sherman ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* $Id$ */ #include #include #include #include #include // Changed #include to . Apparently some // debian distros do not include the qapp.h wrapper and the compilation // fails. [gsherman] #include #include #include #include #include #include "../../src/qgis.h" #include "../../src/qgsdataprovider.h" #include "../../src/qgsfeature.h" #include "../../src/qgsfield.h" #include "../../src/qgsrect.h" #include "qgsgpxprovider.h" #include "gpsdata.h" #ifdef WIN32 #define QGISEXTERN extern "C" __declspec( dllexport ) #else #define QGISEXTERN extern "C" #endif const char* QgsGPXProvider::attr[] = { "name", "elevation", "symbol", "number", "comment", "description", "source", "url", "url name" }; const QString GPX_KEY = "gpx"; const QString GPX_DESCRIPTION = "GPS eXchange format provider"; QgsGPXProvider::QgsGPXProvider(QString const & uri) : QgsVectorDataProvider(uri), mEditable(false), mMinMaxCacheDirty(true) { // assume that it won't work mValid = false; // we always use UTF-8 mEncoding = QTextCodec::codecForName("utf8"); // get the filename and the type parameter from the URI int fileNameEnd = uri.find('?'); if (fileNameEnd == -1 || uri.mid(fileNameEnd + 1, 5) != "type=") { std::cerr<<"Bad URI - you need to specify the feature type"< emptyList; success = getNextFeature(feature, emptyList); } if (success) return feature; delete feature; return NULL; } QgsFeature * QgsGPXProvider::getNextFeature(std::list const & attlist, int featureQueueSize) { QgsFeature* feature = new QgsFeature(-1); bool success = getNextFeature(feature, attlist); if (success) return feature; delete feature; return NULL; } bool QgsGPXProvider::getNextFeature(QgsFeature* feature, std::list const & attlist) { bool result = false; std::list::const_iterator iter; if (mFeatureType == WaypointType) { // go through the list of waypoints and return the first one that is in // the bounds rectangle for (; mWptIter != data->waypointsEnd(); ++mWptIter) { const Waypoint* wpt; wpt = &(*mWptIter); if (boundsCheck(wpt->lon, wpt->lat)) { feature->setFeatureId(wpt->id); result = true; // some wkb voodoo char* geo = new char[21]; std::memset(geo, 0, 21); geo[0] = endian(); geo[1] = 1; std::memcpy(geo+5, &wpt->lon, sizeof(double)); std::memcpy(geo+13, &wpt->lat, sizeof(double)); feature->setGeometryAndOwnership((unsigned char *)geo, sizeof(wkbPoint)); feature->setValid(true); // add attributes if they are wanted for (iter = attlist.begin(); iter != attlist.end(); ++iter) { switch (*iter) { case 0: feature->addAttribute(attr[NameAttr], wpt->name); break; case 1: if (wpt->ele == -std::numeric_limits::max()) feature->addAttribute(attr[EleAttr], ""); else feature->addAttribute(attr[EleAttr], QString("%1").arg(wpt->ele)); break; case 2: feature->addAttribute(attr[SymAttr], wpt->sym); break; case 3: feature->addAttribute(attr[CmtAttr], wpt->cmt); break; case 4: feature->addAttribute(attr[DscAttr], wpt->desc); break; case 5: feature->addAttribute(attr[SrcAttr], wpt->src); break; case 6: feature->addAttribute(attr[URLAttr], wpt->url); break; case 7: feature->addAttribute(attr[URLNameAttr], wpt->urlname); break; } } ++mWptIter; break; } } } else if (mFeatureType == RouteType) { // go through the routes and return the first one that is in the bounds // rectangle for (; mRteIter != data->routesEnd(); ++mRteIter) { const Route* rte; rte = &(*mRteIter); if (rte->points.size() == 0) continue; const QgsRect& b(*mSelectionRectangle); if ((rte->xMax >= b.xMin()) && (rte->xMin <= b.xMax()) && (rte->yMax >= b.yMin()) && (rte->yMin <= b.yMax())) { feature->setFeatureId(rte->id); result = true; // some wkb voodoo int nPoints = rte->points.size(); char* geo = new char[9 + 16 * nPoints]; std::memset(geo, 0, 9 + 16 * nPoints); geo[0] = endian(); geo[1] = 2; std::memcpy(geo + 5, &nPoints, 4); for (int i = 0; i < rte->points.size(); ++i) { std::memcpy(geo + 9 + 16 * i, &rte->points[i].lon, sizeof(double)); std::memcpy(geo + 9 + 16 * i + 8, &rte->points[i].lat, sizeof(double)); } feature->setGeometryAndOwnership((unsigned char *)geo, 9 + 16 * nPoints); feature->setValid(true); // add attributes if they are wanted for (iter = attlist.begin(); iter != attlist.end(); ++iter) { if (*iter == 0) feature->addAttribute(attr[NameAttr], rte->name); else if (*iter == 1) { if (rte->number == std::numeric_limits::max()) feature->addAttribute(attr[NumAttr], ""); else feature->addAttribute(attr[NumAttr], QString("%1").arg(rte->number)); } else if (*iter == 2) feature->addAttribute(attr[CmtAttr], rte->cmt); else if (*iter == 3) feature->addAttribute(attr[DscAttr], rte->desc); else if (*iter == 4) feature->addAttribute(attr[SrcAttr], rte->src); else if (*iter == 5) feature->addAttribute(attr[URLAttr], rte->url); else if (*iter == 6) feature->addAttribute(attr[URLNameAttr], rte->urlname); } ++mRteIter; break; } } } else if (mFeatureType == TrackType) { // go through the tracks and return the first one that is in the bounds // rectangle for (; mTrkIter != data->tracksEnd(); ++mTrkIter) { const Track* trk; trk = &(*mTrkIter); if (trk->segments.size() == 0) continue; if (trk->segments[0].points.size() == 0) continue; const QgsRect& b(*mSelectionRectangle); if ((trk->xMax >= b.xMin()) && (trk->xMin <= b.xMax()) && (trk->yMax >= b.yMin()) && (trk->yMin <= b.yMax())) { feature->setFeatureId(trk->id); result = true; // some wkb voodoo int nPoints = trk->segments[0].points.size(); char* geo = new char[9 + 16 * nPoints]; std::memset(geo, 0, 9 + 16 * nPoints); geo[0] = endian(); geo[1] = 2; std::memcpy(geo + 5, &nPoints, 4); for (int i = 0; i < nPoints; ++i) { std::memcpy(geo + 9 + 16 * i, &trk->segments[0].points[i].lon, sizeof(double)); std::memcpy(geo + 9 + 16 * i + 8, &trk->segments[0].points[i].lat, sizeof(double)); } feature->setGeometryAndOwnership((unsigned char *)geo, 9 + 16 * nPoints); feature->setValid(true); // add attributes if they are wanted for (iter = attlist.begin(); iter != attlist.end(); ++iter) { if (*iter == 0) feature->addAttribute(attr[NameAttr], trk->name); else if (*iter == 1) { if (trk->number == std::numeric_limits::max()) feature->addAttribute(attr[NumAttr], ""); else feature->addAttribute(attr[NumAttr], QString("%1").arg(trk->number)); } else if (*iter == 2) feature->addAttribute(attr[CmtAttr], trk->cmt); else if (*iter == 3) feature->addAttribute(attr[DscAttr], trk->desc); else if (*iter == 4) feature->addAttribute(attr[SrcAttr], trk->src); else if (*iter == 5) feature->addAttribute(attr[URLAttr], trk->url); else if (*iter == 6) feature->addAttribute(attr[URLNameAttr], trk->urlname); } ++mTrkIter; break; } } } return result; } /** * Select features based on a bounding rectangle. Features can be retrieved * with calls to getFirstFeature and getNextFeature. * @param mbr QgsRect containing the extent to use in selecting features */ void QgsGPXProvider::select(QgsRect *rect, bool useIntersect) { // Setting a spatial filter doesn't make much sense since we have to // compare each point against the rectangle. // We store the rect and use it in getNextFeature to determine if the // feature falls in the selection area mSelectionRectangle = new QgsRect(*rect); // Select implies an upcoming feature read so we reset the data source reset(); } /** * Identify features within the search radius specified by rect * @param rect Bounding rectangle of search radius * @return std::vector containing QgsFeature objects that intersect rect */ std::vector& QgsGPXProvider::identify(QgsRect * rect) { // reset the data source since we need to be able to read through // all features reset(); std::cerr << "Attempting to identify features falling within " << rect->stringRep().toLocal8Bit().data() << std::endl; // select the features select(rect); // temporary fix to get this to compile under windows // XXX What the heck is going on here? static std::vector features; return features; } /* unsigned char * QgsGPXProvider::getGeometryPointer(OGRFeature *fet){ unsigned char *gPtr=0; // get the wkb representation //geom->exportToWkb((OGRwkbByteOrder) endian(), gPtr); return gPtr; } */ // Return the extent of the layer QgsRect *QgsGPXProvider::extent() { return data->getExtent(); } /** * Return the feature type */ int QgsGPXProvider::geometryType() const { return mGeomType; } /** * Return the feature type */ long QgsGPXProvider::featureCount() const { if (mFeatureType == WaypointType) return data->getNumberOfWaypoints(); if (mFeatureType == RouteType) return data->getNumberOfRoutes(); if (mFeatureType == TrackType) return data->getNumberOfTracks(); return 0; } /** * Return the number of fields */ int QgsGPXProvider::fieldCount() const { return attributeFields.size(); } std::vector const & QgsGPXProvider::fields() const { return attributeFields; } void QgsGPXProvider::reset() { if (mFeatureType == WaypointType) mWptIter = data->waypointsBegin(); else if (mFeatureType == RouteType) mRteIter = data->routesBegin(); else if (mFeatureType == TrackType) mTrkIter = data->tracksBegin(); } QString QgsGPXProvider::minValue(int position) { if (position >= fieldCount()) { std::cerr<<"Warning: access requested to invalid position " <<"in QgsGPXProvider::minValue(..)"<= fieldCount()) { std::cerr<<"Warning: access requested to invalid position " <<"in QgsGPXProvider::maxValue(..)"<mMinMaxCache[i][1]) { mMinMaxCache[i][1]=value; } } } while(getNextFeature(f, true)); mMinMaxCacheDirty=false; } bool QgsGPXProvider::isValid(){ return mValid; } bool QgsGPXProvider::addFeatures(std::list flist) { // add all the features for (std::list::const_iterator iter = flist.begin(); iter != flist.end(); ++iter) { if (!addFeature(*iter)) return false; } // write back to file QFile file(mFileName); if (!file.open(QIODevice::WriteOnly)) return false; QTextStream ostr(&file); data->writeXML(ostr); return true; } bool QgsGPXProvider::addFeature(QgsFeature* f) { unsigned char* geo = f->getGeometry(); bool success = false; GPSObject* obj = NULL; const std::vector& attrs(f->attributeMap()); // is it a waypoint? if (mFeatureType == WaypointType && geo != NULL && geo[1] == 1) { // add geometry Waypoint wpt; std::memcpy(&wpt.lon, geo+5, sizeof(double)); std::memcpy(&wpt.lat, geo+13, sizeof(double)); // add waypoint-specific attributes for (int i = 0; i < attrs.size(); ++i) { if (attrs[i].fieldName() == attr[EleAttr]) { bool eleIsOK; double ele = attrs[i].fieldValue().toDouble(&eleIsOK); if (eleIsOK) wpt.ele = ele; } else if (attrs[i].fieldName() == attr[SymAttr]) { wpt.sym = attrs[i].fieldValue(); } } GPSData::WaypointIterator iter = data->addWaypoint(wpt); success = true; obj = &(*iter); } // is it a route? if (mFeatureType == RouteType && geo != NULL && geo[1] == 2) { Route rte; // reset bounds rte.xMin = std::numeric_limits::max(); rte.xMax = -std::numeric_limits::max(); rte.yMin = std::numeric_limits::max(); rte.yMax = -std::numeric_limits::max(); // add geometry int nPoints; std::memcpy(&nPoints, geo + 5, 4); for (int i = 0; i < nPoints; ++i) { double lat, lon; std::memcpy(&lon, geo + 9 + 16 * i, sizeof(double)); std::memcpy(&lat, geo + 9 + 16 * i + 8, sizeof(double)); Routepoint rtept; rtept.lat = lat; rtept.lon = lon; rte.points.push_back(rtept); rte.xMin = rte.xMin < lon ? rte.xMin : lon; rte.xMax = rte.xMax > lon ? rte.xMax : lon; rte.yMin = rte.yMin < lat ? rte.yMin : lat; rte.yMax = rte.yMax > lat ? rte.yMax : lat; } // add route-specific attributes for (int i = 0; i < attrs.size(); ++i) { if (attrs[i].fieldName() == attr[NumAttr]) { bool numIsOK; long num = attrs[i].fieldValue().toLong(&numIsOK); if (numIsOK) rte.number = num; } } GPSData::RouteIterator iter = data->addRoute(rte); success = true; obj = &(*iter); } // is it a track? if (mFeatureType == TrackType && geo != NULL && geo[1] == 2) { Track trk; TrackSegment trkseg; // reset bounds trk.xMin = std::numeric_limits::max(); trk.xMax = -std::numeric_limits::max(); trk.yMin = std::numeric_limits::max(); trk.yMax = -std::numeric_limits::max(); // add geometry int nPoints; std::memcpy(&nPoints, geo + 5, 4); for (int i = 0; i < nPoints; ++i) { double lat, lon; std::memcpy(&lon, geo + 9 + 16 * i, sizeof(double)); std::memcpy(&lat, geo + 9 + 16 * i + 8, sizeof(double)); Trackpoint trkpt; trkpt.lat = lat; trkpt.lon = lon; trkseg.points.push_back(trkpt); trk.xMin = trk.xMin < lon ? trk.xMin : lon; trk.xMax = trk.xMax > lon ? trk.xMax : lon; trk.yMin = trk.yMin < lat ? trk.yMin : lat; trk.yMax = trk.yMax > lat ? trk.yMax : lat; } // add track-specific attributes for (int i = 0; i < attrs.size(); ++i) { if (attrs[i].fieldName() == attr[NumAttr]) { bool numIsOK; long num = attrs[i].fieldValue().toLong(&numIsOK); if (numIsOK) trk.number = num; } } trk.segments.push_back(trkseg); GPSData::TrackIterator iter = data->addTrack(trk); success = true; obj = &(*iter); } // add common attributes if (obj) { for (int i = 0; i < attrs.size(); ++i) { if (attrs[i].fieldName() == attr[NameAttr]) { obj->name = attrs[i].fieldValue(); } else if (attrs[i].fieldName() == attr[CmtAttr]) { obj->cmt = attrs[i].fieldValue(); } else if (attrs[i].fieldName() == attr[DscAttr]) { obj->desc = attrs[i].fieldValue(); } else if (attrs[i].fieldName() == attr[SrcAttr]) { obj->src = attrs[i].fieldValue(); } else if (attrs[i].fieldName() == attr[URLAttr]) { obj->url = attrs[i].fieldValue(); } else if (attrs[i].fieldName() == attr[URLNameAttr]) { obj->urlname = attrs[i].fieldValue(); } } } return success; } bool QgsGPXProvider::deleteFeatures(std::list const & id) { if (mFeatureType == WaypointType) data->removeWaypoints(id); else if (mFeatureType == RouteType) data->removeRoutes(id); else if (mFeatureType == TrackType) data->removeTracks(id); // write back to file QFile file(mFileName); if (!file.open(QIODevice::WriteOnly)) return false; QTextStream ostr(&file); data->writeXML(ostr); return true; } bool QgsGPXProvider::changeAttributeValues(std::map > const& attr_map) { std::map >::const_iterator aIter = attr_map.begin(); if (mFeatureType == WaypointType) { GPSData::WaypointIterator wIter = data->waypointsBegin(); for (; wIter != data->waypointsEnd() && aIter != attr_map.end(); ++wIter) { if (wIter->id == aIter->first) { changeAttributeValues(*wIter, aIter->second); ++aIter; } } } else if (mFeatureType == RouteType) { GPSData::RouteIterator rIter = data->routesBegin(); for (; rIter != data->routesEnd() && aIter != attr_map.end(); ++rIter) { if (rIter->id == aIter->first) { changeAttributeValues(*rIter, aIter->second); ++aIter; } } } if (mFeatureType == TrackType) { GPSData::TrackIterator tIter = data->tracksBegin(); for (; tIter != data->tracksEnd() && aIter != attr_map.end(); ++tIter) { if (tIter->id == aIter->first) { changeAttributeValues(*tIter, aIter->second); ++aIter; } } } // write back to file QFile file(mFileName); if (!file.open(QIODevice::WriteOnly)) return false; QTextStream ostr(&file); data->writeXML(ostr); return true; } void QgsGPXProvider::changeAttributeValues(GPSObject& obj, const std::map& attrs) { std::map::const_iterator aIter; // common attributes if ((aIter = attrs.find(attr[NameAttr])) != attrs.end()) obj.name = aIter->second; if ((aIter = attrs.find(attr[CmtAttr])) != attrs.end()) obj.cmt = aIter->second; if ((aIter = attrs.find(attr[DscAttr])) != attrs.end()) obj.desc = aIter->second; if ((aIter = attrs.find(attr[SrcAttr])) != attrs.end()) obj.src = aIter->second; if ((aIter = attrs.find(attr[URLAttr])) != attrs.end()) obj.url = aIter->second; if ((aIter = attrs.find(attr[URLNameAttr])) != attrs.end()) obj.urlname = aIter->second; // waypoint-specific attributes Waypoint* wpt = dynamic_cast(&obj); if (wpt != NULL) { if ((aIter = attrs.find(attr[SymAttr])) != attrs.end()) wpt->sym = aIter->second; if ((aIter = attrs.find(attr[EleAttr])) != attrs.end()) { bool eleIsOK; double ele = aIter->second.toDouble(&eleIsOK); if (eleIsOK) wpt->ele = ele; } } // route- and track-specific attributes GPSExtended* ext = dynamic_cast(&obj); if (ext != NULL) { if ((aIter = attrs.find(attr[NumAttr])) != attrs.end()) { bool eleIsOK; int number = aIter->second.toInt(&eleIsOK); if (eleIsOK) ext->number = number; } } } size_t QgsGPXProvider::layerCount() const { return 1; // XXX need to calculate actual number of layers } // QgsGPXProvider::layerCount() QString QgsGPXProvider::getDefaultValue(const QString& attr, QgsFeature* f) { if (attr == "source") return "Digitized in QGIS"; return ""; } /** * Check to see if the point is within the selection rectangle */ bool QgsGPXProvider::boundsCheck(double x, double y) { bool inBounds = (((x < mSelectionRectangle->xMax()) && (x > mSelectionRectangle->xMin())) && ((y < mSelectionRectangle->yMax()) && (y > mSelectionRectangle->yMin()))); QString hit = inBounds?"true":"false"; return inBounds; } QString QgsGPXProvider::name() const { return GPX_KEY; } // QgsGPXProvider::name() QString QgsGPXProvider::description() const { return GPX_DESCRIPTION; } // QgsGPXProvider::description() /** * Class factory to return a pointer to a newly created * QgsGPXProvider object */ QGISEXTERN QgsGPXProvider * classFactory(const QString *uri) { return new QgsGPXProvider(*uri); } /** Required key function (used to map the plugin to a data store type) */ QGISEXTERN QString providerKey(){ return GPX_KEY; } /** * Required description function */ QGISEXTERN QString description(){ return GPX_DESCRIPTION; } /** * Required isProvider function. Used to determine if this shared library * is a data provider plugin */ QGISEXTERN bool isProvider(){ return true; }