QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsnewgeopackagelayerdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsnewgeopackagelayerdialog.cpp
3 
4  -------------------
5  begin : April 2016
6  copyright : (C) 2016 by Even Rouault
7  email : even.rouault at spatialys.com
8  ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 
21 
22 #include "qgis.h"
23 #include "qgsapplication.h"
24 #include "qgsproviderregistry.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsmaplayerregistry.h"
29 #include "qgscrscache.h"
30 
31 #include "qgslogger.h"
32 
33 #include <QPushButton>
34 #include <QSettings>
35 #include <QLineEdit>
36 #include <QMessageBox>
37 #include <QFileDialog>
38 #include <QLibrary>
39 
40 #include <ogr_api.h>
41 #include <ogr_srs_api.h>
42 #include <gdal_version.h>
43 #include <cpl_error.h>
44 #include <cpl_string.h>
45 
46 #if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,0,0)
47 #define SUPPORT_GEOMETRY_LESS
48 #define SUPPORT_CURVE_GEOMETRIES
49 #define SUPPORT_INTEGER64
50 #define SUPPORT_SPATIAL_INDEX
51 #define SUPPORT_IDENTIFIER_DESCRIPTION
52 #define SUPPORT_FIELD_WIDTH
53 #endif
54 
56  : QDialog( parent, fl )
57  , mOkButton( nullptr )
58  , mTableNameEdited( false )
59  , mLayerIdentifierEdited( false )
60 {
61  setupUi( this );
62 
63  QSettings settings;
64  restoreGeometry( settings.value( "/Windows/NewGeoPackageLayer/geometry" ).toByteArray() );
65 
66  mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( "/mActionNewAttribute.svg" ) );
67  mRemoveAttributeButton->setIcon( QgsApplication::getThemeIcon( "/mActionDeleteAttribute.svg" ) );
68 
69 #ifdef SUPPORT_GEOMETRY_LESS
70  mGeometryTypeBox->addItem( tr( "Non spatial" ), wkbNone );
71 #endif
72  mGeometryTypeBox->addItem( tr( "Point" ), wkbPoint );
73  mGeometryTypeBox->addItem( tr( "Line" ), wkbLineString );
74  mGeometryTypeBox->addItem( tr( "Polygon" ), wkbPolygon );
75  mGeometryTypeBox->addItem( tr( "Multi point" ), wkbMultiPoint );
76  mGeometryTypeBox->addItem( tr( "Multi line" ), wkbMultiLineString );
77  mGeometryTypeBox->addItem( tr( "Multi polygon" ), wkbMultiPolygon );
78 #ifdef SUPPORT_CURVE_GEOMETRIES
79 #if 0
80  // QGIS always create CompoundCurve and there's no real interest of having just CircularString. CompoundCurve are more useful
81  mGeometryTypeBox->addItem( tr( "Circular string" ), wkbCircularString );
82 #endif
83  mGeometryTypeBox->addItem( tr( "Compound curve" ), wkbCompoundCurve );
84  mGeometryTypeBox->addItem( tr( "Curve polygon" ), wkbCurvePolygon );
85  mGeometryTypeBox->addItem( tr( "Multi curve" ), wkbMultiCurve );
86  mGeometryTypeBox->addItem( tr( "Multi surface" ), wkbMultiSurface );
87 #endif
88 
89 #ifdef SUPPORT_GEOMETRY_LESS
90  mGeometryColumnEdit->setEnabled( false );
91  mCheckBoxCreateSpatialIndex->setEnabled( false );
92  mCrsSelector->setEnabled( false );
93 #endif
94 
95  mFieldTypeBox->addItem( tr( "Text data" ), "text" );
96  mFieldTypeBox->addItem( tr( "Whole number (integer)" ), "integer" );
97 #ifdef SUPPORT_INTEGER64
98  mFieldTypeBox->addItem( tr( "Whole number (integer 64 bit)" ), "integer64" );
99 #endif
100  mFieldTypeBox->addItem( tr( "Decimal number (real)" ), "real" );
101  mFieldTypeBox->addItem( tr( "Date" ), "date" );
102  mFieldTypeBox->addItem( tr( "Date&time" ), "datetime" );
103 
104  mOkButton = buttonBox->button( QDialogButtonBox::Ok );
105  mOkButton->setEnabled( false );
106 
107  // Set the SRID box to a default of WGS84
108  QgsCoordinateReferenceSystem defaultCrs = QgsCRSCache::instance()->crsByOgcWmsCrs( settings.value( "/Projections/layerDefaultCrs", GEO_EPSG_CRS_AUTHID ).toString() );
109  defaultCrs.validate();
110  mCrsSelector->setCrs( defaultCrs );
111 
112  connect( mFieldNameEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( fieldNameChanged( QString ) ) );
113  connect( mAttributeView, SIGNAL( itemSelectionChanged() ), this, SLOT( selectionChanged() ) );
114  connect( mTableNameEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( checkOk() ) );
115  connect( mDatabaseEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( checkOk() ) );
116 
117  mAddAttributeButton->setEnabled( false );
118  mRemoveAttributeButton->setEnabled( false );
119 
120 #ifndef SUPPORT_SPATIAL_INDEX
121  mCheckBoxCreateSpatialIndex->hide();
122  mCheckBoxCreateSpatialIndex->setChecked( false );
123 #else
124  mCheckBoxCreateSpatialIndex->setChecked( true );
125 #endif
126 
127 #ifndef SUPPORT_IDENTIFIER_DESCRIPTION
128  mLayerIdentifierLabel->hide();
129  mLayerIdentifierEdit->hide();
130  mLayerDescriptionLabel->hide();
131  mLayerDescriptionEdit->hide();
132 #endif
133 
134 #ifndef SUPPORT_FIELD_WIDTH
135  mFieldLengthLabel->hide();
136  mFieldLengthEdit->hide();
137 #endif
138 }
139 
141 {
142  QSettings settings;
143  settings.setValue( "/Windows/NewGeoPackageLayer/geometry", saveGeometry() );
144 }
145 
146 void QgsNewGeoPackageLayerDialog::on_mFieldTypeBox_currentIndexChanged( int )
147 {
148  QString myType = mFieldTypeBox->itemData( mFieldTypeBox->currentIndex(), Qt::UserRole ).toString();
149  mFieldLengthEdit->setEnabled( myType == "text" );
150  if ( myType != "text" )
151  mFieldLengthEdit->setText( "" );
152 }
153 
154 
155 void QgsNewGeoPackageLayerDialog::on_mGeometryTypeBox_currentIndexChanged( int )
156 {
157  OGRwkbGeometryType geomType = static_cast<OGRwkbGeometryType>
158  ( mGeometryTypeBox->itemData( mGeometryTypeBox->currentIndex(), Qt::UserRole ).toInt() );
159  bool isSpatial = geomType != wkbNone;
160  mGeometryColumnEdit->setEnabled( isSpatial );
161  mCheckBoxCreateSpatialIndex->setEnabled( isSpatial );
162  mCrsSelector->setEnabled( isSpatial );
163 }
164 
165 void QgsNewGeoPackageLayerDialog::on_mSelectDatabaseButton_clicked()
166 {
167  QString fileName = QFileDialog::getSaveFileName( this, tr( "Select existing or create new GeoPackage Database File" ),
168  QDir::homePath(),
169  tr( "GeoPackage" ) + " (*.gpkg)",
170  nullptr,
171  QFileDialog::DontConfirmOverwrite );
172 
173  if ( fileName.isEmpty() )
174  return;
175 
176  if ( !fileName.endsWith( ".gpkg", Qt::CaseInsensitive ) )
177  {
178  fileName += ".gpkg";
179  }
180 
181  mDatabaseEdit->setText( fileName );
182 }
183 
184 void QgsNewGeoPackageLayerDialog::on_mDatabaseEdit_textChanged( const QString& text )
185 {
186  if ( !text.isEmpty() && !mTableNameEdited )
187  {
188  QFileInfo fileInfo( text );
189  mTableNameEdit->setText( fileInfo.baseName() );
190  }
191 }
192 
193 void QgsNewGeoPackageLayerDialog::on_mTableNameEdit_textChanged( const QString& text )
194 {
195  mTableNameEdited = !text.isEmpty();
196  if ( !text.isEmpty() && !mLayerIdentifierEdited )
197  {
198  mLayerIdentifierEdit->setText( text );
199  }
200 }
201 
202 void QgsNewGeoPackageLayerDialog::on_mTableNameEdit_textEdited( const QString& text )
203 {
204  // Remember if the user explicitly defined a name
205  mTableNameEdited = !text.isEmpty();
206 }
207 
208 void QgsNewGeoPackageLayerDialog::on_mLayerIdentifierEdit_textEdited( const QString& text )
209 {
210  // Remember if the user explicitly defined a name
211  mLayerIdentifierEdited = !text.isEmpty();
212 }
213 
214 void QgsNewGeoPackageLayerDialog::checkOk()
215 {
216  bool ok = !mDatabaseEdit->text().isEmpty() &&
217  !mTableNameEdit->text().isEmpty();
218  mOkButton->setEnabled( ok );
219 }
220 
221 void QgsNewGeoPackageLayerDialog::on_mAddAttributeButton_clicked()
222 {
223  if ( !mFieldNameEdit->text().isEmpty() )
224  {
225  QString myName = mFieldNameEdit->text();
226  if ( myName == mFeatureIdColumnEdit->text() )
227  {
228  QMessageBox::critical( this, tr( "Invalid field name" ), tr( "The field cannot have the same name as the feature identifier" ) );
229  return;
230  }
231 
232  //use userrole to avoid translated type string
233  QString myType = mFieldTypeBox->itemData( mFieldTypeBox->currentIndex(), Qt::UserRole ).toString();
234  QString length = mFieldLengthEdit->text();
235  mAttributeView->addTopLevelItem( new QTreeWidgetItem( QStringList() << myName << myType << length ) );
236 
237  checkOk();
238 
239  mFieldNameEdit->clear();
240  }
241 }
242 
243 void QgsNewGeoPackageLayerDialog::on_mRemoveAttributeButton_clicked()
244 {
245  delete mAttributeView->currentItem();
246 
247  checkOk();
248 }
249 
250 void QgsNewGeoPackageLayerDialog::fieldNameChanged( const QString& name )
251 {
252  mAddAttributeButton->setDisabled( name.isEmpty() || ! mAttributeView->findItems( name, Qt::MatchExactly ).isEmpty() );
253 }
254 
255 void QgsNewGeoPackageLayerDialog::selectionChanged()
256 {
257  mRemoveAttributeButton->setDisabled( mAttributeView->selectedItems().isEmpty() );
258 }
259 
260 void QgsNewGeoPackageLayerDialog::on_buttonBox_accepted()
261 {
262  if ( apply() )
263  accept();
264 }
265 
266 void QgsNewGeoPackageLayerDialog::on_buttonBox_rejected()
267 {
268  reject();
269 }
270 
271 bool QgsNewGeoPackageLayerDialog::apply()
272 {
273  QString fileName( mDatabaseEdit->text() );
274  bool createNewDb = false;
275  if ( QFile( fileName ).exists( fileName ) )
276  {
277  QMessageBox msgBox;
278  msgBox.setIcon( QMessageBox::Question );
279  msgBox.setWindowTitle( tr( "The file already exists." ) );
280  msgBox.setText( tr( "Do you want to overwrite the existing file with a new database or add a new layer to it?" ) );
281  QPushButton *overwriteButton = msgBox.addButton( tr( "Overwrite" ), QMessageBox::ActionRole );
282  QPushButton *addNewLayerButton = msgBox.addButton( tr( "Add new layer" ), QMessageBox::ActionRole );
283  msgBox.setStandardButtons( QMessageBox::Cancel );
284  msgBox.setDefaultButton( addNewLayerButton );
285  bool overwrite = false;
286  bool cancel = false;
287  if ( property( "hideDialogs" ).toBool() )
288  {
289  overwrite = property( "question_existing_db_answer_overwrite" ).toBool();
290  if ( !overwrite )
291  cancel = !property( "question_existing_db_answer_add_new_layer" ).toBool();
292  }
293  else
294  {
295  int ret = msgBox.exec();
296  if ( ret == QMessageBox::Cancel )
297  cancel = true;
298  if ( msgBox.clickedButton() == overwriteButton )
299  overwrite = true;
300  }
301  if ( cancel )
302  {
303  return false;
304  }
305  if ( overwrite )
306  {
307  QFile( fileName ).remove();
308  createNewDb = true;
309  }
310  }
311  else
312  {
313  createNewDb = true;
314  }
315 
316  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
317  if ( !hGpkgDriver )
318  {
319  if ( !property( "hideDialogs" ).toBool() )
320  QMessageBox::critical( this, tr( "Layer creation failed" ),
321  tr( "GeoPackage driver not found" ) );
322  return false;
323  }
324 
325  OGRDataSourceH hDS = nullptr;
326  if ( createNewDb )
327  {
328  hDS = OGR_Dr_CreateDataSource( hGpkgDriver, fileName.toUtf8().constData(), nullptr );
329  if ( !hDS )
330  {
331  QString msg( tr( "Creation of database failed (OGR error:%1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
332  if ( !property( "hideDialogs" ).toBool() )
333  QMessageBox::critical( this, tr( "Layer creation failed" ), msg );
334  return false;
335  }
336  }
337  else
338  {
339  OGRSFDriverH hDriver = nullptr;
340  hDS = OGROpen( fileName.toUtf8().constData(), true, &hDriver );
341  if ( !hDS )
342  {
343  QString msg( tr( "Opening of database failed (OGR error:%1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
344  if ( !property( "hideDialogs" ).toBool() )
345  QMessageBox::critical( this, tr( "Layer creation failed" ), msg );
346  return false;
347  }
348  if ( hDriver != hGpkgDriver )
349  {
350  QString msg( tr( "Opening of file succeeded, but this is not a GeoPackage database" ) );
351  if ( !property( "hideDialogs" ).toBool() )
352  QMessageBox::critical( this, tr( "Layer creation failed" ), msg );
353  OGR_DS_Destroy( hDS );
354  return false;
355  }
356  }
357 
358  QString tableName( mTableNameEdit->text() );
359 
360  bool overwriteTable = false;
361  if ( OGR_DS_GetLayerByName( hDS, tableName.toUtf8().constData() ) != nullptr )
362  {
363  if ( property( "hideDialogs" ).toBool() )
364  {
365  overwriteTable = property( "question_existing_layer_answer_overwrite" ).toBool();
366  }
367  else if ( QMessageBox::question( this, tr( "Existing layer" ),
368  tr( "A table with the same name already exists. Do you want to overwrite it?" ),
369  QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::Yes )
370  {
371  overwriteTable = true;
372  }
373 
374  if ( !overwriteTable )
375  {
376  OGR_DS_Destroy( hDS );
377  return false;
378  }
379  }
380 
381  QString layerIdentifier( mLayerIdentifierEdit->text() );
382  QString layerDescription( mLayerDescriptionEdit->text() );
383 
384  OGRwkbGeometryType wkbType = static_cast<OGRwkbGeometryType>
385  ( mGeometryTypeBox->itemData( mGeometryTypeBox->currentIndex(), Qt::UserRole ).toInt() );
386 
387  OGRSpatialReferenceH hSRS = nullptr;
388  // consider spatial reference system of the layer
389  int crsId = mCrsSelector->crs().srsid();
390  if ( wkbType != wkbNone && crsId > 0 )
391  {
393  QString srsWkt = srs.toWkt();
394  hSRS = OSRNewSpatialReference( srsWkt.toLocal8Bit().data() );
395  }
396 
397  // Set options
398  char **options = nullptr;
399 
400  if ( overwriteTable )
401  options = CSLSetNameValue( options, "OVERWRITE", "YES" );
402  if ( !layerIdentifier.isEmpty() )
403  options = CSLSetNameValue( options, "IDENTIFIER", layerIdentifier.toUtf8().constData() );
404  if ( !layerDescription.isEmpty() )
405  options = CSLSetNameValue( options, "DESCRIPTION", layerDescription.toUtf8().constData() );
406 
407  QString featureId( mFeatureIdColumnEdit->text() );
408  if ( !featureId.isEmpty() )
409  options = CSLSetNameValue( options, "FID", featureId.toUtf8().constData() );
410 
411  QString geometryColumn( mGeometryColumnEdit->text() );
412  if ( wkbType != wkbNone && !geometryColumn.isEmpty() )
413  options = CSLSetNameValue( options, "GEOMETRY_COLUMN", geometryColumn.toUtf8().constData() );
414 
415 #ifdef SUPPORT_SPATIAL_INDEX
416  if ( wkbType != wkbNone )
417  options = CSLSetNameValue( options, "SPATIAL_INDEX", mCheckBoxCreateSpatialIndex->isChecked() ? "YES" : "NO" );
418 #endif
419 
420  OGRLayerH hLayer = OGR_DS_CreateLayer( hDS, tableName.toUtf8().constData(), hSRS, wkbType, options );
421  CSLDestroy( options );
422  if ( hSRS != nullptr )
423  OSRRelease( hSRS );
424  if ( hLayer == nullptr )
425  {
426  QString msg( tr( "Creation of layer failed (OGR error:%1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
427  if ( !property( "hideDialogs" ).toBool() )
428  QMessageBox::critical( this, tr( "Layer creation failed" ), msg );
429  OGR_DS_Destroy( hDS );
430  return false;
431  }
432 
433  QTreeWidgetItemIterator it( mAttributeView );
434  while ( *it )
435  {
436  QString fieldName(( *it )->text( 0 ) );
437  QString fieldType(( *it )->text( 1 ) );
438  QString fieldWidth(( *it )->text( 2 ) );
439 
440  OGRFieldType ogrType( OFTString );
441  if ( fieldType == "text" )
442  ogrType = OFTString;
443  else if ( fieldType == "integer" )
444  ogrType = OFTInteger;
445 #ifdef SUPPORT_INTEGER64
446  else if ( fieldType == "integer64" )
447  ogrType = OFTInteger64;
448 #endif
449  else if ( fieldType == "real" )
450  ogrType = OFTReal;
451  else if ( fieldType == "date" )
452  ogrType = OFTDate;
453  else if ( fieldType == "datetime" )
454  ogrType = OFTDateTime;
455 
456  int ogrWidth = fieldWidth.toInt();
457 
458  OGRFieldDefnH fld = OGR_Fld_Create( fieldName.toUtf8().constData(), ogrType );
459  OGR_Fld_SetWidth( fld, ogrWidth );
460 
461  if ( OGR_L_CreateField( hLayer, fld, true ) != OGRERR_NONE )
462  {
463  if ( !property( "hideDialogs" ).toBool() )
464  {
465  QMessageBox::critical( this, tr( "Layer creation failed" ),
466  tr( "Creation of field %1 failed (OGR error: %2)" )
467  .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
468  }
469  OGR_Fld_Destroy( fld );
470  OGR_DS_Destroy( hDS );
471  return false;
472  }
473  OGR_Fld_Destroy( fld );
474 
475  ++it;
476  }
477 
478  // In GDAL >= 2.0, the driver implements a defered creation strategy, so
479  // issue a command that will force table creation
480  CPLErrorReset();
481  OGR_L_ResetReading( hLayer );
482  if ( CPLGetLastErrorType() != CE_None )
483  {
484  QString msg( tr( "Creation of layer failed (OGR error:%1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
485  if ( !property( "hideDialogs" ).toBool() )
486  QMessageBox::critical( this, tr( "Layer creation failed" ), msg );
487  OGR_DS_Destroy( hDS );
488  return false;
489  }
490 
491  OGR_DS_Destroy( hDS );
492 
493  QString uri( QString( "%1|layername=%2" ).arg( mDatabaseEdit->text(), tableName ) );
494  QString userVisiblelayerName( layerIdentifier.isEmpty() ? tableName : layerIdentifier );
495  QgsVectorLayer *layer = new QgsVectorLayer( uri, userVisiblelayerName, "ogr" );
496  if ( layer->isValid() )
497  {
498  // register this layer with the central layers registry
499  QList<QgsMapLayer *> myList;
500  myList << layer;
501  //addMapLayers returns a list of all successfully added layers
502  //so we compare that to our original list.
503  if ( myList == QgsMapLayerRegistry::instance()->addMapLayers( myList ) )
504  return true;
505  }
506  else
507  {
508  if ( !property( "hideDialogs" ).toBool() )
509  QMessageBox::critical( this, tr( "Invalid Layer" ), tr( "%1 is an invalid layer and cannot be loaded." ).arg( tableName ) );
510  delete layer;
511  }
512 
513  return false;
514 }
515 
QByteArray toByteArray() const
void setupUi(QWidget *widget)
virtual void reject()
QgsCoordinateReferenceSystem crsByOgcWmsCrs(const QString &ogcCrs) const
Returns the CRS from a given OGC WMS-format Coordinate Reference System string.
void setDefaultButton(QPushButton *button)
bool remove()
void validate()
Perform some validation on this CRS.
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
bool exists() const
QString homePath()
QString tr(const char *sourceText, const char *disambiguation, int n)
QgsCoordinateReferenceSystem crsBySrsId(long srsId) const
Returns the CRS from a specified QGIS SRS ID.
QAbstractButton * clickedButton() const
void setValue(const QString &key, const QVariant &value)
const char * name() const
void setEnabled(bool)
QString fromUtf8(const char *str, int size)
QVariant property(const char *name) const
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer *> &theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
StandardButton question(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
bool restoreGeometry(const QByteArray &geometry)
void setWindowTitle(const QString &title)
bool isEmpty() const
void setText(const QString &text)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
const QString GEO_EPSG_CRS_AUTHID
Geographic coord sys from EPSG authority.
Definition: qgis.cpp:74
void setIcon(Icon)
void setStandardButtons(QFlags< QMessageBox::StandardButton > buttons)
virtual void accept()
QByteArray toLocal8Bit() const
QVariant value(const QString &key, const QVariant &defaultValue) const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QByteArray saveGeometry() const
QgsNewGeoPackageLayerDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgisGui::ModalDialogFlags)
Constructor.
Class for storing a coordinate reference system (CRS)
QString toWkt() const
Returns a WKT representation of this CRS.
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
StandardButton critical(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
bool toBool() const
char * data()
typedef WindowFlags
void addButton(QAbstractButton *button, ButtonRole role)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
static QgsCRSCache * instance()
Returns a pointer to the QgsCRSCache singleton.
Definition: qgscrscache.cpp:91
QString baseName() const
void * OGRSpatialReferenceH