1
/***************************************************************************
2
qgsdelimitedtextprovider.cpp - Data provider for delimted text
5
copyright : (C) 2004 by Gary E.Sherman
6
email : sherman at mrcc.com
7
***************************************************************************/
9
/***************************************************************************
11
* This program is free software; you can redistribute it and/or modify *
12
* it under the terms of the GNU General Public License as published by *
13
* the Free Software Foundation; either version 2 of the License, or *
14
* (at your option) any later version. *
16
***************************************************************************/
17
/* $Id: qgsdelimitedtextprovider.cpp,v 1.18.2.1 2004/12/14 20:50:06 gsherman Exp $ */
21
#include <qtextstream.h>
22
#include <qstringlist.h>
23
#include <qmessagebox.h>
24
#include <qfiledialog.h>
25
#include <qsettings.h>
27
#include <ogrsf_frmts.h>
28
#include "../../src/qgsdataprovider.h"
29
#include "../../src/qgsfeature.h"
30
#include "../../src/qgsfield.h"
31
#include "../../src/qgsrect.h"
32
#include "qgsdelimitedtextprovider.h"
36
#define QGISEXTERN extern "C" __declspec( dllexport )
38
#define QGISEXTERN extern "C"
41
QgsDelimitedTextProvider::QgsDelimitedTextProvider(QString uri)
42
:mDataSourceUri(uri), mMinMaxCacheDirty(true)
44
// Get the file name and mDelimiter out of the uri
45
mFileName = uri.left(uri.find("?"));
46
// split the string up on & to get the individual parameters
47
QStringList parameters = QStringList::split("&", uri.mid(uri.find("?")));
49
std::cerr << "Parameter count after split on &" << parameters.size() << std::endl;
51
// get the individual parameters and assign values
52
QStringList temp = parameters.grep("delimiter=");
53
mDelimiter = temp.size() ?temp[0].mid(temp[0].find("=") +1):"";
54
temp = parameters.grep("xField=");
55
mXField = temp.size() ?temp[0].mid(temp[0].find("=") +1):"";
56
temp = parameters.grep("yField=");
57
mYField = temp.size() ?temp[0].mid(temp[0].find("=") +1):"";
59
std::cerr << "Data source uri is " << uri << std::endl;
60
std::cerr << "Delimited text file is: " << mFileName << std::endl;
61
std::cerr << "Delimiter is: " << mDelimiter << std::endl;
62
std::cerr << "xField is: " << mXField << std::endl;
63
std::cerr << "yField is: " << mYField << std::endl;
65
// Set the selection rectangle to null
66
mSelectionRectangle = 0;
67
// assume the layer is invalid until proven otherwise
69
if(!mFileName.isEmpty() && !mDelimiter.isEmpty() && !mXField.isEmpty() && !mYField.isEmpty()){
70
// check to see that the file exists and perform some sanity checks
71
if(QFile::exists(mFileName)){
72
// Open the file and get number of rows, etc. We assume that the
73
// file has a header row and process accordingly. Caller should make
74
// sure the the delimited file is properly formed.
75
mFile = new QFile(mFileName);
76
if ( mFile->open( IO_ReadOnly ) ) {
77
QTextStream stream( mFile );
82
// set the initial extent
83
mExtent = new QgsRect(9999999999999.0,9999999999999.0,-9999999999999.0,-9999999999999.0);
84
while ( !stream.atEnd() ) {
86
line = stream.readLine(); // line of text excluding '\n'
87
if(mNumberFeatures++ == 0){
88
// Get the fields from the header row and store them in the
91
std::cerr << "Attempting to split the input line: " << line <<
92
" using delimiter " << mDelimiter << std::endl;
94
QStringList fieldList = QStringList::split(QRegExp(mDelimiter), line, true);
96
std::cerr << "Split line into " << fieldList.size() << " parts" << std::endl;
98
// We don't know anything about a text based field other
99
// than its name. All fields are assumed to be text
101
for ( QStringList::Iterator it = fieldList.begin(); it != fieldList.end(); ++it )
104
if(field.length() > 0)
106
attributeFields.push_back(QgsField(*it, "Text"));
107
fieldPositions[*it] = fieldPos++;
108
// check to see if this field matches either the x or y field
112
std::cerr << "Found x field " << *it << std::endl;
119
std::cerr << "Found y field " << *it << std::endl;
124
std::cerr << "Adding field: " << *it << std::endl;
130
std::cerr << "Field count for the delimited text file is " << attributeFields.size() << std::endl;
134
// examine the x,y and update extents
135
// std::cout << line << std::endl;
136
// split the line on the delimiter
137
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
138
//if(parts.size() == attributeFields.size())
140
// // we can populate attributes if required
141
// fieldsMatch = true;
144
// fieldsMatch = false;
147
std::cout << "Record hit line " << lineNumber << ": " <<
148
parts[fieldPositions[mXField]] << ", " <<
149
parts[fieldPositions[mYField]] << std::endl;
151
// Get the x and y values, first checking to make sure they
153
QString sX = parts[fieldPositions[mXField]];
154
QString sY = parts[fieldPositions[mYField]];
155
//std::cout << "x ,y " << sX << ", " << sY << std::endl;
158
double x = sX.toDouble(&xOk);
159
double y = sY.toDouble(&yOk);
162
if(x > mExtent->xMax())
166
if(x < mExtent->xMin())
170
if(y > mExtent->yMax())
174
if(y < mExtent->yMin())
187
std::cerr << "Data store is valid" << std::endl;
188
std::cerr << "Number of features " << mNumberFeatures << std::endl;
189
std::cerr << "Extents " << mExtent->stringRep() << std::endl;
194
std::cerr << "Data store is invalid. Specified x,y fields do not match\n"
195
<< "those in the database (xyCount=" << xyCount << ")" << std::endl;
200
std::cerr << "Done checking validity\n";
203
//resize the cache matrix
204
mMinMaxCache=new double*[attributeFields.size()];
205
for(int i=0;i<attributeFields.size();i++)
207
mMinMaxCache[i]=new double[2];
210
// file does not exist
211
std::cerr << "Data source " << mDataSourceUri << " could not be opened" << std::endl;
215
// uri is invalid so the layer must be too...
216
std::cerr << "Data source is invalid" << std::endl;
221
QgsDelimitedTextProvider::~QgsDelimitedTextProvider()
225
for(int i=0;i<fieldCount();i++)
227
delete mMinMaxCache[i];
229
delete[] mMinMaxCache;
233
* Get the first feature resutling from a select operation
236
QgsFeature *QgsDelimitedTextProvider::getFirstFeature( bool fetchAttributes)
242
std::cerr << "getting first feature\n";
244
ogrLayer->ResetReading();
245
OGRFeature *feat = ogrLayer->GetNextFeature();
248
std::cerr << "First feature is not null\n";
252
std::cerr << "First feature is null\n";
255
f = new QgsFeature(feat->GetFID());
256
f->setGeometry(getGeometryPointer(feat));
258
getFeatureAttributes(feat, f);
267
* Get the next feature resulting from a select operation
268
* Return 0 if there are no features in the selection set
271
bool QgsDelimitedTextProvider::getNextFeature(QgsFeature &feature, bool fetchAttributes)
273
// We must manually check each point to see if it is within the
274
// selection rectangle
280
QTextStream stream( mFile );
282
if ( !stream.atEnd() ) {
284
std::cerr << "Stream read" << std::endl;
286
line = stream.readLine(); // line of text excluding '\n'
287
// create the geometry from the x, y fields
288
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
289
// Get the x and y values, first checking to make sure they
291
QString sX = parts[fieldPositions[mXField]];
292
QString sY = parts[fieldPositions[mYField]];
293
std::cerr << "x ,y " << sX << ", " << sY << std::endl;
296
double x = sX.toDouble(&xOk);
297
double y = sY.toDouble(&yOk);
300
if(mSelectionRectangle == 0)
302
// no selection in place
306
// check to see if point is in bounds
307
processPoint = boundsCheck(x, y);
311
std::cerr << "Processing " << x << ", " << y << std::endl;
313
wkbPoint *geometry = new wkbPoint;
314
geometry->byteOrder = endian();
315
geometry->wkbType = 1;
318
feature.setGeometry((unsigned char *)geometry, sizeof(wkbPoint));
319
feature.setValid(true);
320
// get the attributes if requested
322
for(int fi =0; fi < attributeFields.size(); fi++)
324
feature.addAttribute(attributeFields.at(fi).name(), parts[fi]);
328
QString sX = parts[fieldPositions[mXField]];
332
feature.setValid(false);
336
// Return true since the read was successful. The feature itself
337
// may be invalid for various reasons
342
std::cerr << "Stream is at end" << std::endl;
344
// Return false since read of next feature failed
346
// Set the feature to invalid
347
feature.setValid(false);
352
QString sReturn = returnValue?"true":"false" ;
353
std::cerr << "Returning " << sReturn << " from getNextFeature" << std::endl;
359
* Get the next feature resulting from a select operation
360
* Return 0 if there are no features in the selection set
363
QgsFeature *QgsDelimitedTextProvider::getNextFeature(bool fetchAttributes)
365
// We must manually check each point to see if it is within the
366
// selection rectangle
371
QTextStream stream( mFile );
373
if ( !stream.atEnd() ) {
374
line = stream.readLine(); // line of text excluding '\n'
375
// create the geometry from the x, y fields
376
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
377
// Get the x and y values, first checking to make sure they
379
QString sX = parts[fieldPositions[mXField]];
380
QString sY = parts[fieldPositions[mYField]];
381
//std::cout << "x ,y " << sX << ", " << sY << std::endl;
384
double x = sX.toDouble(&xOk);
385
double y = sY.toDouble(&yOk);
388
if(mSelectionRectangle == 0)
390
// no selection in place
394
// check to see if point is in bounds
395
processPoint = boundsCheck(x, y);
398
// we need to continue to read until we get a hit in the
399
// selection rectangle or the EOF is reached
400
while(!stream.atEnd() && !processPoint)
403
line = stream.readLine();
405
// create the geometry from the x, y fields
406
parts = QStringList::split(QRegExp(mDelimiter), line, true);
407
// Get the x and y values, first checking to make sure they
409
sX = parts[fieldPositions[mXField]];
410
sY = parts[fieldPositions[mYField]];
411
//std::cout << "x ,y " << sX << ", " << sY << std::endl;
414
x = sX.toDouble(&xOk);
415
y = sY.toDouble(&yOk);
418
processPoint = boundsCheck(x, y);
426
//std::cout << "Processing " << x << ", " << y << std::endl;
429
unsigned char * geometry = new unsigned char[sizeof(wkbPt)];
430
geometry[0] = endian();
432
void *ptr = geometry+1;
433
memcpy((void*)(geometry +1), &type, 4);
434
memcpy((void*)(geometry +5), &x, sizeof(x));
435
memcpy((void*)(geometry +13), &y, sizeof(y));
437
geometry->byteOrder = endian();
438
geometry->wkbType = 1;
442
f = new QgsFeature();
443
f->setGeometry(geometry, sizeof(wkbPt));
444
//std::cerr << "Setting feature id to " << mFid << std::endl;
445
f->setFeatureId(mFid++);
446
// get the attributes if requested
448
// add the attributes to the attribute map
449
for(int fi =0; fi < attributeFields.size(); fi++)
451
f->addAttribute(attributeFields.at(fi).name(), parts[fi]);
464
QgsFeature * QgsDelimitedTextProvider::getNextFeature(std::list<int>& attlist)
466
// We must manually check each point to see if it is within the
467
// selection rectangle
472
QTextStream stream( mFile );
474
if ( !stream.atEnd() ) {
475
line = stream.readLine(); // line of text excluding '\n'
476
// create the geometry from the x, y fields
477
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
478
// Get the x and y values, first checking to make sure they
480
QString sX = parts[fieldPositions[mXField]];
481
QString sY = parts[fieldPositions[mYField]];
482
//std::cout << "x ,y " << sX << ", " << sY << std::endl;
485
double x = sX.toDouble(&xOk);
486
double y = sY.toDouble(&yOk);
489
if(mSelectionRectangle == 0)
491
// no selection in place
495
// check to see if point is in bounds
496
processPoint = boundsCheck(x, y);
499
// we need to continue to read until we get a hit in the
500
// selection rectangle or the EOF is reached
501
while(!stream.atEnd() && !processPoint)
504
line = stream.readLine();
506
// create the geometry from the x, y fields
507
parts = QStringList::split(QRegExp(mDelimiter), line, true);
508
// Get the x and y values, first checking to make sure they
510
sX = parts[fieldPositions[mXField]];
511
sY = parts[fieldPositions[mYField]];
512
//std::cout << "x ,y " << sX << ", " << sY << std::endl;
515
x = sX.toDouble(&xOk);
516
y = sY.toDouble(&yOk);
519
processPoint = boundsCheck(x, y);
527
//std::cout << "Processing " << x << ", " << y << std::endl;
530
unsigned char * geometry = new unsigned char[sizeof(wkbPt)];
531
geometry[0] = endian();
533
void *ptr = geometry+1;
534
memcpy((void*)(geometry +1), &type, 4);
535
memcpy((void*)(geometry +5), &x, sizeof(x));
536
memcpy((void*)(geometry +13), &y, sizeof(y));
538
geometry->byteOrder = endian();
539
geometry->wkbType = 1;
543
f = new QgsFeature();
544
f->setGeometry(geometry, sizeof(wkbPt));
545
//std::cerr << "Setting feature id to " << mFid << std::endl;
546
f->setFeatureId(mFid++);
548
// add the attributes to the attribute map
549
for(std::list<int>::iterator iter=attlist.begin();iter!=attlist.end();++iter)
551
f->addAttribute(attributeFields.at(*iter).name(), parts[*iter]);
562
* Select features based on a bounding rectangle. Features can be retrieved
563
* with calls to getFirstFeature and getNextFeature.
564
* @param mbr QgsRect containing the extent to use in selecting features
566
void QgsDelimitedTextProvider::select(QgsRect *rect, bool useIntersect)
569
// Setting a spatial filter doesn't make much sense since we have to
570
// compare each point against the rectangle.
571
// We store the rect and use it in getNextFeature to determine if the
572
// feature falls in the selection area
573
mSelectionRectangle = new QgsRect((*rect));
574
// Select implies an upcoming feature read so we reset the data source
576
// Reset the feature id to 0
582
* Set the data source specification. This may be a path or database
584
* @uri data source specification
586
void QgsDelimitedTextProvider::setDataSourceUri(QString uri)
588
mDataSourceUri = uri;
592
* Get the data source specification. This may be a path or database
594
* @return data source specification
596
QString QgsDelimitedTextProvider::getDataSourceUri()
598
return mDataSourceUri;
602
* Identify features within the search radius specified by rect
603
* @param rect Bounding rectangle of search radius
604
* @return std::vector containing QgsFeature objects that intersect rect
606
std::vector<QgsFeature>& QgsDelimitedTextProvider::identify(QgsRect * rect)
608
// reset the data source since we need to be able to read through
611
std::cerr << "Attempting to identify features falling within "
612
<< rect->stringRep() << std::endl;
613
// select the features
616
//TODO fix this later for win32
617
std::vector<QgsFeature> feat;
624
unsigned char * QgsDelimitedTextProvider::getGeometryPointer(OGRFeature *fet){
625
unsigned char *gPtr=0;
626
// get the wkb representation
628
//geom->exportToWkb((OGRwkbByteOrder) endian(), gPtr);
633
int QgsDelimitedTextProvider::endian()
635
char *chkEndian = new char[4];
636
memset(chkEndian, '\0', 4);
639
int *ce = (int *) chkEndian;
649
// Return the extent of the layer
650
QgsRect *QgsDelimitedTextProvider::extent()
652
return new QgsRect(mExtent->xMin(), mExtent->yMin(), mExtent->xMax(), mExtent->yMax());
656
* Return the feature type
658
int QgsDelimitedTextProvider::geometryType(){
659
return 1; // WKBPoint
662
* Return the feature type
664
long QgsDelimitedTextProvider::featureCount(){
665
return mNumberFeatures;
669
* Return the number of fields
671
int QgsDelimitedTextProvider::fieldCount(){
672
return attributeFields.size();
675
* Fetch attributes for a selected feature
677
void QgsDelimitedTextProvider::getFeatureAttributes(int key, QgsFeature *f){
678
//for (int i = 0; i < ogrFet->GetFieldCount(); i++) {
680
// // add the feature attributes to the tree
681
// OGRFieldDefn *fldDef = ogrFet->GetFieldDefnRef(i);
682
// QString fld = fldDef->GetNameRef();
683
// // OGRFieldType fldType = fldDef->GetType();
686
// val = ogrFet->GetFieldAsString(i);
687
// f->addAttribute(fld, val);
691
std::vector<QgsField>& QgsDelimitedTextProvider::fields(){
692
return attributeFields;
695
void QgsDelimitedTextProvider::reset(){
696
// Reset the file pointer to BOF
698
// Reset feature id to 0
700
// Skip ahead one line since first record is always assumed to be
702
QTextStream stream( mFile );
706
QString QgsDelimitedTextProvider::minValue(int position)
708
if(position>=fieldCount())
710
std::cerr << "Warning: access requested to invalid position "
711
<< "in QgsDelimitedTextProvider::minValue(..)" << std::endl;
713
if(mMinMaxCacheDirty)
717
return QString::number(mMinMaxCache[position][0],'f',2);
721
QString QgsDelimitedTextProvider::maxValue(int position)
723
if(position>=fieldCount())
725
std::cerr << "Warning: access requested to invalid position "
726
<< "in QgsDelimitedTextProvider::maxValue(..)" << std::endl;
728
if(mMinMaxCacheDirty)
732
return QString::number(mMinMaxCache[position][1],'f',2);
735
void QgsDelimitedTextProvider::fillMinMaxCash()
737
for(int i=0;i<fieldCount();i++)
739
mMinMaxCache[i][0]=DBL_MAX;
740
mMinMaxCache[i][1]=-DBL_MAX;
746
getNextFeature(f, true);
749
for(int i=0;i<fieldCount();i++)
751
double value=(f.attributeMap())[i].fieldValue().toDouble();
752
if(value<mMinMaxCache[i][0])
754
mMinMaxCache[i][0]=value;
756
if(value>mMinMaxCache[i][1])
758
mMinMaxCache[i][1]=value;
761
}while(getNextFeature(f, true));
763
mMinMaxCacheDirty=false;
765
//TODO - add sanity check for shape file layers, to include cheking to
766
// see if the .shp, .dbf, .shx files are all present and the layer
767
// actually has features
768
bool QgsDelimitedTextProvider::isValid(){
773
* Check to see if the point is within the selection rectangle
775
bool QgsDelimitedTextProvider::boundsCheck(double x, double y)
777
bool inBounds = (((x < mSelectionRectangle->xMax()) &&
778
(x > mSelectionRectangle->xMin())) &&
779
((y < mSelectionRectangle->yMax()) &&
780
(y > mSelectionRectangle->yMin())));
781
// QString hit = inBounds?"true":"false";
783
// std::cerr << "Checking if " << x << ", " << y << " is in " <<
784
//mSelectionRectangle->stringRep().ascii() << ": " << hit.ascii() << std::endl;
787
bool QgsDelimitedTextProvider::supportsSaveAsShapefile()
792
bool QgsDelimitedTextProvider::saveAsShapefile()
794
// save the layer as a shapefile
795
QString driverName = "ESRI Shapefile";
796
OGRSFDriver *poDriver;
798
poDriver = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName((const char *)driverName );
799
bool returnValue = true;
800
if( poDriver != NULL )
802
// get a name for the shapefile
803
// Get a file to process, starting at the current directory
804
// Set inital dir to last used in delimited text plugin
807
QString shapefileName = QFileDialog::getSaveFileName(
808
settings.readEntry("/Qgis/delimited_text_plugin/text_path","./"),
809
"Shapefiles (*.shp)",
812
"Save delimited text layer as shapefile" );
814
if(!shapefileName.isNull())
816
// add the extension if not present
817
if(shapefileName.find(".shp") == -1)
819
shapefileName += ".shp";
822
// create the data source
823
poDS = poDriver->CreateDataSource( (const char *)shapefileName, NULL );
826
std::cerr << "created datasource" << std::endl;
827
// datasource created, now create the output layer
829
poLayer = poDS->CreateLayer((const char *)shapefileName.left(shapefileName.find(".shp")), NULL, static_cast<OGRwkbGeometryType>(1), NULL );
830
if( poLayer != NULL )
832
std::cerr << "created layer" << std::endl;
833
// calculate the field lengths
834
int *lengths = getFieldLengths();
836
std::cerr << "creating " << attributeFields.size() << " fields" << std::endl;
837
for(int i = 0; i < attributeFields.size(); i++)
839
// check the field length - if > 10 we need to truncate it
840
QgsField attrField = attributeFields[i];
841
if(attrField.name().length() > 10)
843
attrField = attrField.name().left(10);
845
// all fields are created as string (for now)
846
OGRFieldDefn fld(attrField.name(), OFTString);
847
// set the length for the field -- but we don't know what it is...
848
fld.SetWidth(lengths[i]);
850
std::cerr << "creating field " << attrField.name() << " width length " << lengths[i] << std::endl;
851
if(poLayer->CreateField(&fld) != OGRERR_NONE)
853
QMessageBox::warning(0, "Error", "Error creating field " + attrField.name());
856
// read the delimited text file and create the features
857
std::cerr << "Done creating fields" << std::endl;
860
QTextStream stream( mFile );
862
while ( !stream.atEnd() ) {
863
line = stream.readLine(); // line of text excluding '\n'
864
std::cerr << line << std::endl;
866
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
867
std::cerr << "Split line into " << parts.size() << std::endl;
869
// create the feature
870
OGRFeature *poFeature;
872
poFeature = new OGRFeature( poLayer->GetLayerDefn() );
874
// iterate over the parts and set the fields
875
std::cerr << "Setting the field values" << std::endl;
876
// set limit - we will ignore extra fields on the line
877
int limit = attributeFields.size();
879
if(parts.size() < limit){
881
// this is bad - not enough values where supplied on the line
882
// TODO We should inform the user about this...
887
for ( int i = 0; i < limit; i++ )
889
if(parts[i] != QString::null)
891
std::cerr << "Setting " << i << " " << attributeFields[i].name() << " to " << parts[i] << std::endl;
892
poFeature->SetField(attributeFields[i].name(), parts[i]);
897
poFeature->SetField(attributeFields[i].name(), "");
900
std::cerr << "Field values set" << std::endl;
902
OGRPoint *poPoint = new OGRPoint();
903
QString sX = parts[fieldPositions[mXField]];
904
QString sY = parts[fieldPositions[mYField]];
905
poPoint->setX(sX.toDouble());
906
poPoint->setY(sY.toDouble());
907
std::cerr << "Setting geometry" << std::endl;
909
poFeature->SetGeometryDirectly(poPoint);
910
if(poLayer->CreateFeature(poFeature) != OGRERR_NONE)
912
std::cerr << "Failed to create feature in shapefile" << std::endl;
916
std::cerr << "Added feature" << std::endl;
926
QMessageBox::warning(0,"Error", "Layer creation failed");
932
QMessageBox::warning(0, "Error creating shapefile",
933
"The shapefile could not be created (" + shapefileName + ")");
937
//std::cerr << "Saving to " << shapefileName << std::endl;
941
QMessageBox::warning(0, "Driver not found", driverName + " driver is not available");
948
int* QgsDelimitedTextProvider::getFieldLengths()
950
// this function parses the entire data file and calculates the
953
// Only do this if we haven't done it already (ie. the vector is
955
int *lengths = new int[attributeFields.size()];
956
// init the lengths to zero
957
for(int il=0; il < attributeFields.size(); il++)
964
QTextStream stream( mFile );
966
while ( !stream.atEnd() ) {
967
line = stream.readLine(); // line of text excluding '\n'
969
QStringList parts = QStringList::split(QRegExp(mDelimiter), line, true);
970
// iterate over the parts and update the max value
971
for ( int i = 0; i < parts.size(); i++ )
973
if(parts[i] != QString::null)
975
// std::cerr << "comparing length for " << parts[i] << " against max len of " << lengths[i] << std::endl;
976
if(parts[i].length() > lengths[i])
978
lengths[i] = parts[i].length();
988
* Class factory to return a pointer to a newly created
989
* QgsDelimitedTextProvider object
991
QGISEXTERN QgsDelimitedTextProvider * classFactory(const char *uri)
993
return new QgsDelimitedTextProvider(uri);
995
/** Required key function (used to map the plugin to a data store type)
997
QGISEXTERN QString providerKey(){
998
return QString("delimitedtext");
1001
* Required description function
1003
QGISEXTERN QString description(){
1004
return QString("Delimited text data provider");
1007
* Required isProvider function. Used to determine if this shared library
1008
* is a data provider plugin
1010
QGISEXTERN bool isProvider(){