[delimiter text] Add Point{Z,M,ZM} geometry support (fixes #25645) (#31595)

[FEATURE][delimiter text] Add Point{Z,M,ZM} geometry support (fixes #25645)
This commit is contained in:
Mathieu Pellerin 2019-09-06 19:35:27 +07:00 committed by GitHub
parent 87b1aa9a5d
commit 5df309447e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 281 additions and 15 deletions

View File

@ -431,13 +431,25 @@ QgsGeometry QgsDelimitedTextFeatureIterator::loadGeometryXY( const QStringList &
isNull = true;
return QgsGeometry();
}
isNull = false;
QgsPointXY pt;
bool ok = QgsDelimitedTextProvider::pointFromXY( sX, sY, pt, mSource->mDecimalPoint, mSource->mXyDms );
if ( ok && wantGeometry( pt ) )
isNull = false;
QgsPoint *pt = new QgsPoint();
bool ok = QgsDelimitedTextProvider::pointFromXY( sX, sY, *pt, mSource->mDecimalPoint, mSource->mXyDms );
QString sZ, sM;
if ( mSource->mZFieldIndex > -1 )
sZ = tokens[mSource->mZFieldIndex];
if ( mSource->mMFieldIndex > -1 )
sM = tokens[mSource->mMFieldIndex];
if ( !sZ.isEmpty() || !sM.isEmpty() )
{
return QgsGeometry::fromPointXY( pt );
QgsDelimitedTextProvider::appendZM( sZ, sM, *pt, mSource->mDecimalPoint );
}
if ( ok && wantGeometry( *pt ) )
{
return QgsGeometry( pt );
}
return QgsGeometry();
}
@ -511,6 +523,8 @@ QgsDelimitedTextFeatureSource::QgsDelimitedTextFeatureSource( const QgsDelimited
, mFieldCount( p->mFieldCount )
, mXFieldIndex( p->mXFieldIndex )
, mYFieldIndex( p->mYFieldIndex )
, mZFieldIndex( p->mZFieldIndex )
, mMFieldIndex( p->mMFieldIndex )
, mWktFieldIndex( p->mWktFieldIndex )
, mWktHasPrefix( p->mWktHasPrefix )
, mGeometryType( p->mGeometryType )

View File

@ -43,6 +43,8 @@ class QgsDelimitedTextFeatureSource : public QgsAbstractFeatureSource
int mFieldCount; // Note: this includes field count for wkt field
int mXFieldIndex;
int mYFieldIndex;
int mZFieldIndex;
int mMFieldIndex;
int mWktFieldIndex;
bool mWktHasPrefix;
QgsWkbTypes::GeometryType mGeometryType;

View File

@ -101,8 +101,14 @@ QgsDelimitedTextProvider::QgsDelimitedTextProvider( const QString &uri, const Pr
mGeometryType = QgsWkbTypes::PointGeometry;
mXFieldName = url.queryItemValue( QStringLiteral( "xField" ) );
mYFieldName = url.queryItemValue( QStringLiteral( "yField" ) );
if ( url.hasQueryItem( QStringLiteral( "zField" ) ) )
mZFieldName = url.queryItemValue( QStringLiteral( "zField" ) );
if ( url.hasQueryItem( QStringLiteral( "mField" ) ) )
mMFieldName = url.queryItemValue( QStringLiteral( "mField" ) );
QgsDebugMsg( "xField is: " + mXFieldName );
QgsDebugMsg( "yField is: " + mYFieldName );
QgsDebugMsg( "zField is: " + mZFieldName );
QgsDebugMsg( "mField is: " + mMFieldName );
if ( url.hasQueryItem( QStringLiteral( "xyDms" ) ) )
{
@ -336,11 +342,27 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
mYFieldIndex = mFile->fieldIndex( mYFieldName );
if ( mXFieldIndex < 0 )
{
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "X" ), mWktFieldName ) );
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "X" ), mXFieldName ) );
}
if ( mYFieldIndex < 0 )
{
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "Y" ), mWktFieldName ) );
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "Y" ), mYFieldName ) );
}
if ( !mZFieldName.isEmpty() )
{
mZFieldIndex = mFile->fieldIndex( mZFieldName );
if ( mZFieldIndex < 0 )
{
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "Z" ), mZFieldName ) );
}
}
if ( !mMFieldName.isEmpty() )
{
mMFieldIndex = mFile->fieldIndex( mMFieldName );
if ( mMFieldIndex < 0 )
{
messages.append( tr( "%0 field %1 is not defined in delimited text file" ).arg( QStringLiteral( "M" ), mMFieldName ) );
}
}
}
if ( !messages.isEmpty() )
@ -466,6 +488,11 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
QString sX = mXFieldIndex < parts.size() ? parts[mXFieldIndex] : QString();
QString sY = mYFieldIndex < parts.size() ? parts[mYFieldIndex] : QString();
QString sZ, sM;
if ( mZFieldIndex > -1 )
sZ = mZFieldIndex < parts.size() ? parts[mZFieldIndex] : QString();
if ( mMFieldIndex > -1 )
sM = mMFieldIndex < parts.size() ? parts[mMFieldIndex] : QString();
if ( sX.isEmpty() && sY.isEmpty() )
{
nEmptyGeometry++;
@ -473,11 +500,14 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
}
else
{
QgsPointXY pt;
QgsPoint pt;
bool ok = pointFromXY( sX, sY, pt, mDecimalPoint, mXyDms );
if ( ok )
{
if ( !sZ.isEmpty() || sM.isEmpty() )
appendZM( sZ, sM, pt, mDecimalPoint );
if ( foundFirstGeometry )
{
mExtent.combineExtentWith( pt.x(), pt.y() );
@ -487,6 +517,10 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
// Extent for the first point is just the first point
mExtent.set( pt.x(), pt.y(), pt.x(), pt.y() );
mWkbType = QgsWkbTypes::Point;
if ( mZFieldIndex > -1 )
mWkbType = QgsWkbTypes::addZ( mWkbType );
if ( mMFieldIndex > -1 )
mWkbType = QgsWkbTypes::addM( mWkbType );
mGeometryType = QgsWkbTypes::PointGeometry;
foundFirstGeometry = true;
}
@ -841,7 +875,31 @@ double QgsDelimitedTextProvider::dmsStringToDouble( const QString &sX, bool *xOk
return x;
}
bool QgsDelimitedTextProvider::pointFromXY( QString &sX, QString &sY, QgsPointXY &pt, const QString &decimalPoint, bool xyDms )
void QgsDelimitedTextProvider::appendZM( QString &sZ, QString &sM, QgsPoint &point, const QString &decimalPoint )
{
if ( ! decimalPoint.isEmpty() )
{
sZ.replace( decimalPoint, QLatin1String( "." ) );
sM.replace( decimalPoint, QLatin1String( "." ) );
}
bool zOk, mOk;
double z, m;
if ( !sZ.isEmpty() )
{
z = sZ.toDouble( &zOk );
if ( zOk )
point.addZValue( z );
}
if ( !sM.isEmpty() )
{
m = sM.toDouble( &mOk );
if ( mOk )
point.addMValue( m );
}
}
bool QgsDelimitedTextProvider::pointFromXY( QString &sX, QString &sY, QgsPoint &pt, const QString &decimalPoint, bool xyDms )
{
if ( ! decimalPoint.isEmpty() )
{

View File

@ -211,7 +211,8 @@ class QgsDelimitedTextProvider : public QgsVectorDataProvider
static QgsGeometry geomFromWkt( QString &sWkt, bool wktHasPrefixRegexp );
static bool pointFromXY( QString &sX, QString &sY, QgsPointXY &point, const QString &decimalPoint, bool xyDms );
static bool pointFromXY( QString &sX, QString &sY, QgsPoint &point, const QString &decimalPoint, bool xyDms );
static void appendZM( QString &sZ, QString &sM, QgsPoint &point, const QString &decimalPoint );
static double dmsStringToDouble( const QString &sX, bool *xOk );
// mLayerValid defines whether the layer has been loaded as a valid layer
@ -232,10 +233,14 @@ class QgsDelimitedTextProvider : public QgsVectorDataProvider
QString mWktFieldName;
QString mXFieldName;
QString mYFieldName;
QString mZFieldName;
QString mMFieldName;
bool mDetectTypes = true;
mutable int mXFieldIndex = -1;
mutable int mYFieldIndex = -1;
mutable int mZFieldIndex = -1;
mutable int mMFieldIndex = -1;
mutable int mWktFieldIndex = -1;
// mWktPrefix regexp is used to clean up

View File

@ -152,13 +152,24 @@ void QgsDelimitedTextSourceSelect::addButtonClicked()
bool haveGeom = true;
if ( geomTypeXY->isChecked() )
{
QString field;
if ( !cmbXField->currentText().isEmpty() && !cmbYField->currentText().isEmpty() )
{
QString field = cmbXField->currentText();
field = cmbXField->currentText();
url.addQueryItem( QStringLiteral( "xField" ), field );
field = cmbYField->currentText();
url.addQueryItem( QStringLiteral( "yField" ), field );
}
if ( !cmbZField->currentText().isEmpty() )
{
field = cmbZField->currentText();
url.addQueryItem( QStringLiteral( "zField" ), field );
}
if ( !cmbMField->currentText().isEmpty() )
{
field = cmbMField->currentText();
url.addQueryItem( QStringLiteral( "mField" ), field );
}
}
else if ( geomTypeWKT->isChecked() )
{
@ -407,11 +418,15 @@ void QgsDelimitedTextSourceSelect::updateFieldLists()
QString columnX = cmbXField->currentText();
QString columnY = cmbYField->currentText();
QString columnZ = cmbZField->currentText();
QString columnM = cmbMField->currentText();
QString columnWkt = cmbWktField->currentText();
// clear the field lists
cmbXField->clear();
cmbYField->clear();
cmbZField->clear();
cmbMField->clear();
cmbWktField->clear();
// clear the sample text box
@ -532,6 +547,8 @@ void QgsDelimitedTextSourceSelect::updateFieldLists()
if ( field.isEmpty() ) continue;
cmbXField->addItem( field );
cmbYField->addItem( field );
cmbZField->addItem( field );
cmbMField->addItem( field );
cmbWktField->addItem( field );
fieldNo++;
}
@ -541,6 +558,8 @@ void QgsDelimitedTextSourceSelect::updateFieldLists()
cmbWktField->setCurrentIndex( cmbWktField->findText( columnWkt ) );
cmbXField->setCurrentIndex( cmbXField->findText( columnX ) );
cmbYField->setCurrentIndex( cmbYField->findText( columnY ) );
cmbZField->setCurrentIndex( cmbYField->findText( columnZ ) );
cmbMField->setCurrentIndex( cmbYField->findText( columnM ) );
// Now try setting optional X,Y fields - will only reset the fields if
// not already set.

View File

@ -879,7 +879,7 @@
</property>
<item>
<layout class="QGridLayout" name="gridLayout_5" columnstretch="0,0">
<item row="4" column="1">
<item row="3" column="1" colspan="3">
<widget class="QCheckBox" name="cbxXyDms">
<property name="toolTip">
<string>X and Y coordinates are expressed in degrees/minutes/seconds</string>
@ -957,8 +957,70 @@
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="cmbZField">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Name of the field containing z values</string>
</property>
<property name="statusTip">
<string>Name of the field containing z values</string>
</property>
<property name="whatsThis">
<string>Name of the field containing z values</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="cmbMField">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Name of the field containing m values</string>
</property>
<property name="statusTip">
<string>Name of the field containing m values</string>
</property>
<property name="whatsThis">
<string>Name of the field containing m values</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabelx">
<widget class="QLabel" name="textLabelX">
<property name="enabled">
<bool>true</bool>
</property>
@ -974,7 +1036,7 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabely">
<widget class="QLabel" name="textLabelY">
<property name="enabled">
<bool>true</bool>
</property>
@ -992,6 +1054,44 @@
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="textLabelZ">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;p align=&quot;left&quot;&gt;Z field&lt;/p&gt;</string>
</property>
<property name="buddy">
<cstring>cmbZField</cstring>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="textLabelM">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;p align=&quot;left&quot;&gt;M field&lt;/p&gt;</string>
</property>
<property name="buddy">
<cstring>cmbMField</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -41,7 +41,8 @@ from qgis.core import (
QgsFeatureRequest,
QgsRectangle,
QgsApplication,
QgsFeature)
QgsFeature,
QgsWkbTypes)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath, compareWkt
@ -826,6 +827,67 @@ class TestQgsDelimitedTextProviderOther(unittest.TestCase):
components = registry.decodeUri('delimitedtext', uri)
self.assertEqual(components['path'], filename)
def test_044_ZM(self):
# Create test layer
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
basetestfile = os.path.join(srcpath, 'delimited_xyzm.csv')
url = MyUrl.fromLocalFile(basetestfile)
url.addQueryItem("crs", "epsg:4326")
url.addQueryItem("type", "csv")
url.addQueryItem("xField", "X")
url.addQueryItem("yField", "Y")
url.addQueryItem("zField", "Z")
url.addQueryItem("mField", "M")
url.addQueryItem("spatialIndex", "no")
url.addQueryItem("subsetIndex", "no")
url.addQueryItem("watchFile", "no")
vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
assert vl.isValid(), "{} is invalid".format(basetestfile)
assert vl.wkbType() == QgsWkbTypes.PointZM, "wrong wkb type, should be PointZM"
assert vl.getFeature(2).geometry().asWkt() == "PointZM (-71.12300000000000466 78.23000000000000398 1 2)", "wrong PointZM geometry"
def test_045_Z(self):
# Create test layer
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
basetestfile = os.path.join(srcpath, 'delimited_xyzm.csv')
url = MyUrl.fromLocalFile(basetestfile)
url.addQueryItem("crs", "epsg:4326")
url.addQueryItem("type", "csv")
url.addQueryItem("xField", "X")
url.addQueryItem("yField", "Y")
url.addQueryItem("zField", "Z")
url.addQueryItem("spatialIndex", "no")
url.addQueryItem("subsetIndex", "no")
url.addQueryItem("watchFile", "no")
vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
assert vl.isValid(), "{} is invalid".format(basetestfile)
assert vl.wkbType() == QgsWkbTypes.PointZ, "wrong wkb type, should be PointZ"
assert vl.getFeature(2).geometry().asWkt() == "PointZ (-71.12300000000000466 78.23000000000000398 1)", "wrong PointZ geometry"
def test_046_M(self):
# Create test layer
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
basetestfile = os.path.join(srcpath, 'delimited_xyzm.csv')
url = MyUrl.fromLocalFile(basetestfile)
url.addQueryItem("crs", "epsg:4326")
url.addQueryItem("type", "csv")
url.addQueryItem("xField", "X")
url.addQueryItem("yField", "Y")
url.addQueryItem("mField", "M")
url.addQueryItem("spatialIndex", "no")
url.addQueryItem("subsetIndex", "no")
url.addQueryItem("watchFile", "no")
vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
assert vl.isValid(), "{} is invalid".format(basetestfile)
assert vl.wkbType() == QgsWkbTypes.PointM, "wrong wkb type, should be PointM"
assert vl.getFeature(2).geometry().asWkt() == "PointM (-71.12300000000000466 78.23000000000000398 2)", "wrong PointM geometry"
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,6 @@
pk,cnt,name,name2,num_char,X,Y,Z,M
5,-200,,NuLl,5,-71.123,78.23,1,2
3,300,Pear,PEaR,3,,,3,4
1,100,Orange,oranGe,1,-70.332,66.33,3,4
2,200,Apple,Apple,2,-68.2,70.8,3,4
4,400,Honey,Honey,4,-65.32,78.3,3,4
1 pk cnt name name2 num_char X Y Z M
2 5 -200 NuLl 5 -71.123 78.23 1 2
3 3 300 Pear PEaR 3 3 4
4 1 100 Orange oranGe 1 -70.332 66.33 3 4
5 2 200 Apple Apple 2 -68.2 70.8 3 4
6 4 400 Honey Honey 4 -65.32 78.3 3 4