[FEATURE][processing] one-to-many join support to the join attribute table alg. (#6499)

This commit is contained in:
Mathieu Pellerin 2018-03-02 11:58:16 +07:00 committed by GitHub
parent 2238b42501
commit 6f47b25037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 207 additions and 10 deletions

View File

@ -87,8 +87,8 @@ class SpatialJoin(QgisAlgorithm):
'crosses': 'crosses'}
self.methods = [
self.tr('Create separate feature for each located feature'),
self.tr('Take attributes of the first located feature only')
self.tr('Create separate feature for each located feature (one-to-one)'),
self.tr('Take attributes of the first located feature only (one-to-many)')
]
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,

View File

@ -0,0 +1,41 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>join_attribute_table</Name>
<ElementPath>join_attribute_table</ElementPath>
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>10</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>8.00000</ExtentXMax>
<ExtentYMin>-5.00000</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>id</Name>
<ElementPath>id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>id2</Name>
<ElementPath>id2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>id_2</Name>
<ElementPath>id_2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>NUM_A</Name>
<ElementPath>NUM_A</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>ST_A</Name>
<ElementPath>ST_A</ElementPath>
<Type>String</Type>
<Width>8</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:join_attribute_table fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:id_2>1</ogr:id_2>
<ogr:NUM_A>1.100000</ogr:NUM_A>
<ogr:ST_A>string a</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:id_2>2</ogr:id_2>
<ogr:NUM_A>2.400000</ogr:NUM_A>
<ogr:ST_A>string c</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:id_2>2</ogr:id_2>
<ogr:NUM_A>2.200000</ogr:NUM_A>
<ogr:ST_A>string a</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:id_2>3</ogr:id_2>
<ogr:NUM_A>3.300000</ogr:NUM_A>
<ogr:ST_A>string a</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:id_2>4</ogr:id_2>
<ogr:NUM_A>4.400000</ogr:NUM_A>
<ogr:ST_A>string b</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>5</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:id_2>5</ogr:id_2>
<ogr:NUM_A>5.500000</ogr:NUM_A>
<ogr:ST_A>string b</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>6</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:id_2>6</ogr:id_2>
<ogr:NUM_A>6.600000</ogr:NUM_A>
<ogr:ST_A>string b</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>7</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:id_2>7</ogr:id_2>
<ogr:NUM_A>7.700000</ogr:NUM_A>
<ogr:ST_A>string b</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:id_2>8</ogr:id_2>
<ogr:NUM_A>8.800000</ogr:NUM_A>
<ogr:ST_A>string b</ogr:ST_A>
</ogr:join_attribute_table>
</gml:featureMember>
<gml:featureMember>
<ogr:join_attribute_table fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>9</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:join_attribute_table>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -2772,8 +2772,9 @@ tests:
type: vector
- algorithm: native:joinattributestable
name: join the attribute table by common field
name: join the attribute table by common field (one-to-one)
params:
METHOD: 0
INPUT:
name: points.gml
type: vector
@ -2787,9 +2788,27 @@ tests:
name: expected/join_attribute_table.gml
type: vector
- algorithm: native:joinattributestable
name: join the attribute table by common field (one-to-many)
params:
METHOD: 1
INPUT:
name: points.gml
type: vector
INPUT_2:
name: table.dbf
type: table
FIELD: id
FIELD_2: ID
results:
OUTPUT:
name: expected/join_attribute_table_all_match.gml
type: vector
- algorithm: native:joinattributestable
name: Join attributes table with subset of fields
params:
METHOD: 0
FIELD: id
FIELDS_TO_COPY:
- NUM_A

View File

@ -46,6 +46,10 @@ QString QgsJoinByAttributeAlgorithm::groupId() const
void QgsJoinByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
{
QStringList methods;
methods << QObject::tr( "Take attributes of the first matching feature only (one-to-one)" )
<< QObject::tr( "Create separate feature for each matching feature (one-to-many)" );
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
QObject::tr( "Input layer" ), QList< int>() << QgsProcessing::TypeVector ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ),
@ -61,6 +65,11 @@ void QgsJoinByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
QVariant(), QStringLiteral( "INPUT_2" ), QgsProcessingParameterField::Any,
true, true ) );
addParameter( new QgsProcessingParameterEnum(
QStringLiteral( "METHOD" ),
QObject::tr( "Join type" ),
methods, false, 0 ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ) ) );
}
@ -69,7 +78,8 @@ QString QgsJoinByAttributeAlgorithm::shortHelpString() const
return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the "
"input one, with additional attributes in its attribute table.\n\n"
"The additional attributes and their values are taken from a second vector layer. An attribute is selected "
"in each of them to define the join criteria." );
"in each of them to define the join criteria.\n\n"
"The algorithm will output one feature per matching row(s) from the second vector layer." );
}
QgsJoinByAttributeAlgorithm *QgsJoinByAttributeAlgorithm::createInstance() const
@ -79,6 +89,8 @@ QgsJoinByAttributeAlgorithm *QgsJoinByAttributeAlgorithm::createInstance() const
QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
int joinMethod = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
std::unique_ptr< QgsFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
std::unique_ptr< QgsFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
if ( !input || !input2 )
@ -129,7 +141,7 @@ QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &pa
// cache attributes of input2
QHash< QVariant, QgsAttributes > input2AttributeCache;
QMultiHash< QVariant, QgsAttributes > input2AttributeCache;
QgsFeatureIterator features = input2->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( fields2Fetch ) );
double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
int i = 0;
@ -144,7 +156,7 @@ QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &pa
feedback->setProgress( i * step );
if ( input2AttributeCache.contains( feat.attribute( joinField2Index ) ) )
if ( joinMethod == 0 && input2AttributeCache.contains( feat.attribute( joinField2Index ) ) )
continue;
// only keep selected attributes
@ -173,10 +185,24 @@ QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &pa
feedback->setProgress( 50 + i * step );
QgsAttributes attrs = feat.attributes();
attrs.append( input2AttributeCache.value( feat.attribute( joinField1Index ) ) );
feat.setAttributes( attrs );
sink->addFeature( feat, QgsFeatureSink::FastInsert );
if ( input2AttributeCache.count( feat.attribute( joinField1Index ) ) > 0 )
{
QgsAttributes attrs = feat.attributes();
QList< QgsAttributes > attributes = input2AttributeCache.values( feat.attribute( joinField1Index ) );
QList< QgsAttributes >::iterator attrsIt = attributes.begin();
for ( ; attrsIt != attributes.end(); ++attrsIt )
{
QgsAttributes newAttrs = attrs;
newAttrs.append( *attrsIt );
feat.setAttributes( newAttrs );
sink->addFeature( feat, QgsFeatureSink::FastInsert );
}
}
else
{
sink->addFeature( feat, QgsFeatureSink::FastInsert );
}
}
QVariantMap outputs;