Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QgsVectorFileWriter map NaN to -DBL_MAX exporting to Shape #50507

Merged
merged 4 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ Sets the geometry from a WKT string.
enum WkbFlag
{
FlagExportTrianglesAsPolygons,
FlagExportNanAsDoubleMin,
};
typedef QFlags<QgsAbstractGeometry::WkbFlag> WkbFlags;

Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgsabstractgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ class CORE_EXPORT QgsAbstractGeometry
enum WkbFlag
{
FlagExportTrianglesAsPolygons = 1 << 0, //!< Triangles should be exported as polygon geometries
FlagExportNanAsDoubleMin = 1 << 1, //!< Use -DOUBLE_MAX to represent NaN (since QGIS 3.30)
};
Q_DECLARE_FLAGS( WkbFlags, WkbFlag )

Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgscircularstring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ QByteArray QgsCircularString::asWkb( WkbFlags flags ) const
wkb << static_cast<quint32>( wkbType() );
QgsPointSequence pts;
points( pts );
QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure(), flags );
return wkbArray;
}

Expand Down
16 changes: 13 additions & 3 deletions src/core/geometry/qgsgeometryutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1216,19 +1216,29 @@ QgsPointSequence QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateLi
return points;
}

void QgsGeometryUtils::pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure )
void QgsGeometryUtils::pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags )
{
wkb << static_cast<quint32>( points.size() );
for ( const QgsPoint &point : points )
{
wkb << point.x() << point.y();
if ( is3D )
{
wkb << point.z();
double z = point.z();
if ( flags & QgsAbstractGeometry::FlagExportNanAsDoubleMin
&& std::isnan( z ) )
z = -std::numeric_limits<double>::max();
rouault marked this conversation as resolved.
Show resolved Hide resolved

wkb << z;
}
if ( isMeasure )
{
wkb << point.m();
double m = point.m();
if ( flags & QgsAbstractGeometry::FlagExportNanAsDoubleMin
&& std::isnan( m ) )
m = -std::numeric_limits<double>::max();

wkb << m;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgsgeometryutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ class CORE_EXPORT QgsGeometryUtils
* Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }
* \note not available in Python bindings
*/
static void pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure ) SIP_SKIP;
static void pointsToWKB( QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags ) SIP_SKIP;

/**
* Returns a WKT coordinate list
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgslinestring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ QByteArray QgsLineString::asWkb( WkbFlags flags ) const
wkb << static_cast<quint32>( wkbType() );
QgsPointSequence pts;
points( pts );
QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure(), flags );
return wkbArray;
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/geometry/qgspolygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,13 @@ QByteArray QgsPolygon::asWkb( QgsAbstractGeometry::WkbFlags flags ) const
{
QgsPointSequence pts;
mExteriorRing->points( pts );
QgsGeometryUtils::pointsToWKB( wkb, pts, mExteriorRing->is3D(), mExteriorRing->isMeasure() );
QgsGeometryUtils::pointsToWKB( wkb, pts, mExteriorRing->is3D(), mExteriorRing->isMeasure(), flags );
}
for ( const QgsCurve *curve : mInteriorRings )
{
QgsPointSequence pts;
curve->points( pts );
QgsGeometryUtils::pointsToWKB( wkb, pts, curve->is3D(), curve->isMeasure() );
QgsGeometryUtils::pointsToWKB( wkb, pts, curve->is3D(), curve->isMeasure(), flags );
}

return wkbArray;
Expand Down
26 changes: 22 additions & 4 deletions src/core/qgsvectorfilewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2798,9 +2798,19 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
// add m/z values if not present in the input wkb type -- this is needed for formats which determine
// geometry type based on features, e.g. geojson
if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( geom.wkbType() ) )
geom.get()->addZValue( 0 );
{
if ( mOgrDriverName == QLatin1String( "ESRI Shapefile" ) )
geom.get()->addZValue( std::numeric_limits<double>::quiet_NaN() );
else
geom.get()->addZValue( 0 );
}
if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( geom.wkbType() ) )
geom.get()->addMValue( 0 );
{
if ( mOgrDriverName == QLatin1String( "ESRI Shapefile" ) )
geom.get()->addMValue( std::numeric_limits<double>::quiet_NaN() );
else
geom.get()->addMValue( 0 );
}

if ( !mGeom2 )
{
Expand All @@ -2824,7 +2834,11 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
return nullptr;
}

QByteArray wkb( geom.asWkb() );
QgsAbstractGeometry::WkbFlags wkbFlags;
if ( mOgrDriverName == QLatin1String( "ESRI Shapefile" ) )
wkbFlags |= QgsAbstractGeometry::FlagExportNanAsDoubleMin;

QByteArray wkb( geom.asWkb( wkbFlags ) );
OGRErr err = OGR_G_ImportFromWkb( mGeom2, reinterpret_cast<unsigned char *>( const_cast<char *>( wkb.constData() ) ), wkb.length() );
if ( err != OGRERR_NONE )
{
Expand All @@ -2840,7 +2854,11 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
}
else // wkb type matches
{
QByteArray wkb( geom.asWkb( QgsAbstractGeometry::FlagExportTrianglesAsPolygons ) );
QgsAbstractGeometry::WkbFlags wkbFlags = QgsAbstractGeometry::FlagExportTrianglesAsPolygons;
if ( mOgrDriverName == QLatin1String( "ESRI Shapefile" ) )
rouault marked this conversation as resolved.
Show resolved Hide resolved
wkbFlags |= QgsAbstractGeometry::FlagExportNanAsDoubleMin;

QByteArray wkb( geom.asWkb( wkbFlags ) );
OGRGeometryH ogrGeom = createEmptyGeometry( mWkbType );
OGRErr err = OGR_G_ImportFromWkb( ogrGeom, reinterpret_cast<unsigned char *>( const_cast<char *>( wkb.constData() ) ), wkb.length() );
if ( err != OGRERR_NONE )
Expand Down
49 changes: 48 additions & 1 deletion tests/src/core/testqgsvectorfilewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ class TestQgsVectorFileWriter: public QObject
void testExportToGpxMultiLineStringForceRoute();
//! Test export using custom field names
void testExportCustomFieldNames();

//! Test export to shape with NaN values for Z
void testExportToShapeNanValuesForZ();
private:
// a little util fn used by all tests
bool cleanupFile( QString fileBase );
Expand Down Expand Up @@ -704,5 +705,51 @@ void TestQgsVectorFileWriter::testExportCustomFieldNames()
QCOMPARE( details.outputFields.at( 1 ).name(), "customfieldname" );
}

void TestQgsVectorFileWriter::testExportToShapeNanValuesForZ()
{
//
// Remove old copies that may be lying around
//
QString myFileName = QStringLiteral( "/testln.shp" );
myFileName = QDir::tempPath() + myFileName;
QVERIFY( QgsVectorFileWriter::deleteShapeFile( myFileName ) );

QgsVectorFileWriter::SaveVectorOptions saveOptions;
saveOptions.fileEncoding = mEncoding;
std::unique_ptr< QgsVectorFileWriter > writer( QgsVectorFileWriter::create( myFileName, mFields, QgsWkbTypes::LineStringZ, mCRS, QgsCoordinateTransformContext(), saveOptions ) );
//
// Create a feature
//
QgsLineString *ls = new QgsLineString();
ls->setPoints( QgsPointSequence() << QgsPoint( mPoint1 )
<< QgsPoint( mPoint2 )
<< QgsPoint( mPoint3 ) );
ls->setZAt( 1, std::numeric_limits<double>::quiet_NaN() );
const QgsGeometry mypLineGeometry( ls );
QgsFeature myFeature;
myFeature.setGeometry( mypLineGeometry );
myFeature.initAttributes( 1 );
myFeature.setAttribute( 0, "HelloWorld" );
//
// Write the feature to the filewriter
// and check for errors
//
QVERIFY( writer->addFeature( myFeature ) );
mError = writer->hasError();
if ( mError == QgsVectorFileWriter::ErrDriverNotFound )
{
std::cout << "Driver not found error" << std::endl;
}
else if ( mError == QgsVectorFileWriter::ErrCreateDataSource )
{
std::cout << "Create data source error" << std::endl;
}
else if ( mError == QgsVectorFileWriter::ErrCreateLayer )
{
std::cout << "Create layer error" << std::endl;
}
QVERIFY( mError == QgsVectorFileWriter::NoError );
}

QGSTEST_MAIN( TestQgsVectorFileWriter )
#include "testqgsvectorfilewriter.moc"