Skip to content

Commit

Permalink
Merge pull request #5613 from nyalldawson/custom_crs_fixes
Browse files Browse the repository at this point in the history
Custom crs fixes
  • Loading branch information
nyalldawson committed Nov 13, 2017
2 parents fd18c3b + be8bad8 commit 3b2228a
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 26 deletions.
9 changes: 5 additions & 4 deletions python/core/qgscoordinatereferencesystem.sip
Expand Up @@ -636,11 +636,12 @@ Returns whether this CRS is correctly initialized and usable
%End


bool saveAsUserCrs( const QString &name );
long saveAsUserCrs( const QString &name );
%Docstring
Save the proj4-string as a custom CRS
:return: bool true if success else false
:rtype: bool
Save the proj4-string as a custom CRS.

Returns the new CRS srsid(), or -1 if the CRS could not be saved.
:rtype: long
%End

QString geographicCrsAuthId() const;
Expand Down
19 changes: 12 additions & 7 deletions src/core/qgscoordinatereferencesystem.cpp
Expand Up @@ -1637,12 +1637,12 @@ QString QgsCoordinateReferenceSystem::validationHint()
/// Copied from QgsCustomProjectionDialog ///
/// Please refactor into SQL handler !!! ///

bool QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name )
long QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name )
{
if ( !d->mIsValid )
{
QgsDebugMsgLevel( "Can't save an invalid CRS!", 4 );
return false;
return -1;
}

QString mySql;
Expand Down Expand Up @@ -1691,13 +1691,16 @@ bool QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name )
}
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );

qint64 return_id;
qint64 returnId;
if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
{
QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( toProj4() ), QObject::tr( "CRS" ) );

return_id = sqlite3_last_insert_rowid( myDatabase );
setInternalId( return_id );
returnId = sqlite3_last_insert_rowid( myDatabase );
setInternalId( returnId );
if ( authid().isEmpty() )
setAuthId( QStringLiteral( "USER:%1" ).arg( returnId ) );
setDescription( name );

//We add the just created user CRS to the list of recently used CRS
QgsSettings settings;
Expand All @@ -1713,8 +1716,10 @@ bool QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name )

}
else
return_id = -1;
return return_id;
returnId = -1;

invalidateCache();
return returnId;
}

long QgsCoordinateReferenceSystem::getRecordCount()
Expand Down
7 changes: 4 additions & 3 deletions src/core/qgscoordinatereferencesystem.h
Expand Up @@ -613,10 +613,11 @@ class CORE_EXPORT QgsCoordinateReferenceSystem


/**
* Save the proj4-string as a custom CRS
* \returns bool true if success else false
* Save the proj4-string as a custom CRS.
*
* Returns the new CRS srsid(), or -1 if the CRS could not be saved.
*/
bool saveAsUserCrs( const QString &name );
long saveAsUserCrs( const QString &name );

//! Returns auth id of related geographic CRS
QString geographicCrsAuthId() const;
Expand Down
36 changes: 29 additions & 7 deletions src/core/qgsproject.cpp
Expand Up @@ -451,9 +451,6 @@ QgsCoordinateReferenceSystem QgsProject::crs() const
void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs )
{
mCrs = crs;
writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ), crs.toProj4() );
writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), static_cast< int >( crs.srsid() ) );
writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ), crs.authid() );
writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
setDirty( true );
emit crsChanged();
Expand Down Expand Up @@ -884,14 +881,34 @@ bool QgsProject::readProjectFile( const QString &filename )
QgsCoordinateReferenceSystem projectCrs;
if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
{
long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
if ( currentCRS != -1 )
// first preference - dedicated projectCrs node
QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
if ( !srsNode.isNull() )
{
projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
projectCrs.readXml( srsNode );
}

if ( !projectCrs.isValid() )
{
// else we try using the stored proj4 string - it's consistent across different QGIS installs,
// whereas the srsid can vary (e.g. for custom projections)
QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
if ( !projCrsString.isEmpty() )
{
projectCrs = QgsCoordinateReferenceSystem::fromProj4( projCrsString );
}
// last try using crs id - most fragile
if ( !projectCrs.isValid() )
{
long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
if ( currentCRS != -1 && currentCRS < USER_CRS_START_ID )
{
projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
}
}
}
}
mCrs = projectCrs;
emit crsChanged();

QDomNodeList nl = doc->elementsByTagName( QStringLiteral( "autotransaction" ) );
if ( nl.count() )
Expand Down Expand Up @@ -1340,6 +1357,11 @@ bool QgsProject::writeProjectFile( const QString &filename )
QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
titleNode.appendChild( titleText );

// write project CRS
QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
mCrs.writeXml( srsNode, *doc );
qgisNode.appendChild( srsNode );

// write layer tree - make sure it is without embedded subgroups
QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
Expand Down
59 changes: 54 additions & 5 deletions tests/src/core/testqgscoordinatereferencesystem.cpp
Expand Up @@ -15,17 +15,19 @@ Email : sherman at mrcc dot com
#include "qgstest.h"
#include <QPixmap>

#include <qgsapplication.h>
#include "qgsapplication.h"
#include "qgslogger.h"

//header for class being tested
#include <qgscoordinatereferencesystem.h>
#include <qgis.h>
#include <qgsvectorlayer.h>
#include "qgscoordinatereferencesystem.h"
#include "qgis.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"

#include <proj_api.h>
#include <gdal.h>
#include <cpl_conv.h>
#include <QtTest/QSignalSpy>

class TestQgsCoordinateReferenceSystem: public QObject
{
Expand Down Expand Up @@ -76,6 +78,8 @@ class TestQgsCoordinateReferenceSystem: public QObject
void validSrsIds();
void asVariant();
void bounds();
void saveAsUserCrs();
void projectWithCustomCrs();

private:
void debugPrint( QgsCoordinateReferenceSystem &crs );
Expand All @@ -86,20 +90,31 @@ class TestQgsCoordinateReferenceSystem: public QObject
QStringList myProj4Strings;
QStringList myTOWGS84Strings;
QStringList myAuthIdStrings;
QString mTempFolder;
QString testESRIWkt( int i, QgsCoordinateReferenceSystem &crs );
};


void TestQgsCoordinateReferenceSystem::initTestCase()
{
// we start from a clean profile - we don't want to mess with user custom srses
// create temporary folder
QString subPath = QUuid::createUuid().toString().remove( '-' ).remove( '{' ).remove( '}' );
mTempFolder = QDir::tempPath() + '/' + subPath;
if ( !QDir( mTempFolder ).exists() )
QDir().mkpath( mTempFolder );

//
// Runs once before any tests are run
//
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
QgsApplication::init( mTempFolder );
QgsApplication::createDatabase();
QgsApplication::initQgis();
QgsApplication::showSettings();

QgsDebugMsg( QString( "Custom srs database: %1" ).arg( QgsApplication::qgisUserDatabaseFilePath() ) );

qDebug() << "GEOPROJ4 constant: " << GEOPROJ4;
qDebug() << "GDAL version (build): " << GDAL_RELEASE_NAME;
qDebug() << "GDAL version (runtime): " << GDALVersionInfo( "RELEASE_NAME" );
Expand Down Expand Up @@ -799,5 +814,39 @@ void TestQgsCoordinateReferenceSystem::bounds()
QGSCOMPARENEAR( bounds.yMaximum(), 90.00000, 0.0001 );
}

void TestQgsCoordinateReferenceSystem::saveAsUserCrs()
{
QString madeUpProjection = QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" );
QgsCoordinateReferenceSystem userCrs = QgsCoordinateReferenceSystem::fromProj4( madeUpProjection );
QVERIFY( userCrs.isValid() );
QCOMPARE( userCrs.toProj4(), madeUpProjection );
QCOMPARE( userCrs.srsid(), 0L ); // not saved to database yet

long newId = userCrs.saveAsUserCrs( QStringLiteral( "babies first projection" ) );
QCOMPARE( newId, static_cast< long >( USER_CRS_START_ID ) );
QCOMPARE( userCrs.srsid(), newId );
QCOMPARE( userCrs.authid(), QStringLiteral( "USER:100000" ) );
QCOMPARE( userCrs.description(), QStringLiteral( "babies first projection" ) );

// new CRS with same definition, check that it's matched to user crs
QgsCoordinateReferenceSystem userCrs2 = QgsCoordinateReferenceSystem::fromProj4( madeUpProjection );
QVERIFY( userCrs2.isValid() );
QCOMPARE( userCrs2.toProj4(), madeUpProjection );
QCOMPARE( userCrs2.srsid(), userCrs.srsid() );
QCOMPARE( userCrs2.authid(), QStringLiteral( "USER:100000" ) );
QCOMPARE( userCrs2.description(), QStringLiteral( "babies first projection" ) );
}

void TestQgsCoordinateReferenceSystem::projectWithCustomCrs()
{
// tests loading a 2.x project with a custom CRS defined
QgsProject p;
QSignalSpy spyCrsChanged( &p, &QgsProject::crsChanged );
QVERIFY( p.read( TEST_DATA_DIR + QStringLiteral( "/projects/custom_crs.qgs" ) ) );
QVERIFY( p.crs().isValid() );
QCOMPARE( p.crs().toProj4(), QStringLiteral( "+proj=ortho +lat_0=42.1 +lon_0=12.8 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs" ) );
QCOMPARE( spyCrsChanged.count(), 1 );
}

QGSTEST_MAIN( TestQgsCoordinateReferenceSystem )
#include "testqgscoordinatereferencesystem.moc"
129 changes: 129 additions & 0 deletions tests/testdata/projects/custom_crs.qgs
@@ -0,0 +1,129 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis projectname="" version="2.18.9">
<title></title>
<autotransaction active="0"/>
<evaluateDefaultValues active="0"/>
<layer-tree-group expanded="1" checked="Qt::Checked" name="">
<customproperties/>
</layer-tree-group>
<relations/>
<mapcanvas>
<units>meters</units>
<extent>
<xmin>-1579730.21282207453623414</xmin>
<ymin>-4271174.32985332515090704</ymin>
<xmax>-1242009.40286419028416276</xmax>
<ymax>-4054770.71263487357646227</ymax>
</extent>
<rotation>0</rotation>
<projections>1</projections>
<destinationsrs>
<spatialrefsys>
<proj4>+proj=ortho +lat_0=42.1 +lon_0=12.8 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs</proj4>
<srsid>100001</srsid>
<srid>0</srid>
<authid>USER:100001</authid>
<description>new CRS</description>
<projectionacronym>ortho</projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</destinationsrs>
<rendermaptile>0</rendermaptile>
<layer_coordinate_transform_info/>
</mapcanvas>
<layer-tree-canvas>
<custom-order enabled="0"/>
</layer-tree-canvas>
<legend updateDrawingOrder="true"/>
<projectlayers/>
<properties>
<WMSContactPerson type="QString"></WMSContactPerson>
<Variables>
<variableNames type="QStringList"/>
<variableValues type="QStringList"/>
</Variables>
<WMSOnlineResource type="QString"></WMSOnlineResource>
<WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
<WMSContactOrganization type="QString"></WMSContactOrganization>
<WMSKeywordList type="QStringList">
<value></value>
</WMSKeywordList>
<WFSUrl type="QString"></WFSUrl>
<Paths>
<Absolute type="bool">false</Absolute>
</Paths>
<WMSServiceTitle type="QString"></WMSServiceTitle>
<WFSLayers type="QStringList"/>
<WMSContactMail type="QString"></WMSContactMail>
<PositionPrecision>
<DecimalPlaces type="int">2</DecimalPlaces>
<Automatic type="bool">true</Automatic>
<DegreeFormat type="QString">MU</DegreeFormat>
</PositionPrecision>
<WCSUrl type="QString"></WCSUrl>
<WMSContactPhone type="QString"></WMSContactPhone>
<WMSServiceCapabilities type="bool">false</WMSServiceCapabilities>
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
<WMSContactPosition type="QString"></WMSContactPosition>
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
<Measure>
<Ellipsoid type="QString">NONE</Ellipsoid>
</Measure>
<WMSPrecision type="QString">8</WMSPrecision>
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
<WFSTLayers>
<Insert type="QStringList"/>
<Update type="QStringList"/>
<Delete type="QStringList"/>
</WFSTLayers>
<Gui>
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
</Gui>
<Digitizing>
<DefaultSnapToleranceUnit type="int">2</DefaultSnapToleranceUnit>
<SnappingMode type="QString">current_layer</SnappingMode>
<DefaultSnapType type="QString">off</DefaultSnapType>
<DefaultSnapTolerance type="double">0</DefaultSnapTolerance>
</Digitizing>
<Identify>
<disabledLayers type="QStringList"/>
</Identify>
<Macros>
<pythonCode type="QString"></pythonCode>
</Macros>
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
<WCSLayers type="QStringList"/>
<Legend>
<filterByMap type="bool">false</filterByMap>
</Legend>
<SpatialRefSys>
<ProjectCRSProj4String type="QString">+proj=ortho +lat_0=42.1 +lon_0=12.8 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs</ProjectCRSProj4String>
<ProjectCrs type="QString">USER:100001</ProjectCrs>
<ProjectCRSID type="int">100001</ProjectCRSID>
<ProjectionsEnabled type="int">1</ProjectionsEnabled>
</SpatialRefSys>
<DefaultStyles>
<Fill type="QString"></Fill>
<Line type="QString"></Line>
<Marker type="QString"></Marker>
<RandomColors type="bool">true</RandomColors>
<AlphaInt type="int">255</AlphaInt>
<ColorRamp type="QString"></ColorRamp>
</DefaultStyles>
<WMSFees type="QString">conditions unknown</WMSFees>
<WMSImageQuality type="int">90</WMSImageQuality>
<Measurement>
<DistanceUnits type="QString">meters</DistanceUnits>
<AreaUnits type="QString">m2</AreaUnits>
</Measurement>
<WMSUrl type="QString"></WMSUrl>
</properties>
<visibility-presets/>
</qgis>

0 comments on commit 3b2228a

Please sign in to comment.