QGIS/src/analysis/vector/qgsgeometryanalyzer.cpp

1494 lines
39 KiB
C++

/***************************************************************************
qgsgeometryanalyzer.cpp - QGIS Tools for vector geometry analysis
-------------------
begin : 19 March 2009
copyright : (C) Carson Farmer
email : carson.farmer@gmail.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgeometryanalyzer.h"
#include "qgsapplication.h"
#include "qgsfields.h"
#include "qgsfeature.h"
#include "qgsfeatureiterator.h"
#include "qgslogger.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsvectorfilewriter.h"
#include "qgsvectordataprovider.h"
#include "qgsdistancearea.h"
#include "qgis.h"
#include "qgsvectorlayer.h"
#include <QProgressDialog>
bool QgsGeometryAnalyzer::simplify( QgsVectorLayer *layer,
const QString &shapefileName,
double tolerance,
bool onlySelectedFeatures,
QProgressDialog *p )
{
if ( !layer )
{
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
return false;
}
QgsWkbTypes::Type outputType = dp->wkbType();
QgsCoordinateReferenceSystem crs = layer->crs();
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), layer->fields(), outputType, crs );
QgsFeature currentFeature;
//take only selection
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
if ( p )
{
p->setMaximum( selection.size() );
}
int processedFeatures = 0;
QgsFeatureIds::const_iterator it = selection.constBegin();
for ( ; it != selection.constEnd(); ++it )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
{
continue;
}
simplifyFeature( currentFeature, &vWriter, tolerance );
++processedFeatures;
}
if ( p )
{
p->setValue( selection.size() );
}
}
//take all features
else
{
QgsFeatureIterator fit = layer->getFeatures();
int featureCount = layer->featureCount();
if ( p )
{
p->setMaximum( featureCount );
}
int processedFeatures = 0;
while ( fit.nextFeature( currentFeature ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
simplifyFeature( currentFeature, &vWriter, tolerance );
++processedFeatures;
}
if ( p )
{
p->setValue( featureCount );
}
}
return true;
}
void QgsGeometryAnalyzer::simplifyFeature( QgsFeature &f, QgsVectorFileWriter *vfw, double tolerance )
{
if ( !f.hasGeometry() )
{
return;
}
QgsGeometry featureGeometry = f.geometry();
// simplify feature
QgsGeometry tmpGeometry = featureGeometry.simplify( tolerance );
QgsFeature newFeature;
newFeature.setGeometry( tmpGeometry );
newFeature.setAttributes( f.attributes() );
//add it to vector file writer
if ( vfw )
{
vfw->addFeature( newFeature );
}
}
bool QgsGeometryAnalyzer::centroids( QgsVectorLayer *layer, const QString &shapefileName,
bool onlySelectedFeatures, QProgressDialog *p )
{
if ( !layer )
{
QgsDebugMsg( "No layer passed to centroids" );
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
QgsDebugMsg( "No data provider for layer passed to centroids" );
return false;
}
QgsWkbTypes::Type outputType = QgsWkbTypes::Point;
QgsCoordinateReferenceSystem crs = layer->crs();
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), layer->fields(), outputType, crs );
QgsFeature currentFeature;
//take only selection
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
if ( p )
{
p->setMaximum( selection.size() );
}
int processedFeatures = 0;
QgsFeatureIds::const_iterator it = selection.constBegin();
for ( ; it != selection.constEnd(); ++it )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
{
continue;
}
centroidFeature( currentFeature, &vWriter );
++processedFeatures;
}
if ( p )
{
p->setValue( selection.size() );
}
}
//take all features
else
{
QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) );
int featureCount = layer->featureCount();
if ( p )
{
p->setMaximum( featureCount );
}
int processedFeatures = 0;
while ( fit.nextFeature( currentFeature ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
centroidFeature( currentFeature, &vWriter );
++processedFeatures;
}
if ( p )
{
p->setValue( featureCount );
}
}
return true;
}
void QgsGeometryAnalyzer::centroidFeature( QgsFeature &f, QgsVectorFileWriter *vfw )
{
if ( !f.hasGeometry() )
{
return;
}
QgsGeometry featureGeometry = f.geometry();
QgsFeature newFeature;
newFeature.setGeometry( featureGeometry.centroid() );
newFeature.setAttributes( f.attributes() );
//add it to vector file writer
if ( vfw )
{
vfw->addFeature( newFeature );
}
}
bool QgsGeometryAnalyzer::extent( QgsVectorLayer *layer,
const QString &shapefileName,
bool onlySelectedFeatures,
QProgressDialog * )
{
if ( !layer )
{
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
return false;
}
QgsWkbTypes::Type outputType = QgsWkbTypes::Polygon;
QgsCoordinateReferenceSystem crs = layer->crs();
QgsFields fields;
fields.append( QgsField( QStringLiteral( "MINX" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "MINY" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "MAXX" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "MAXY" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "CNTX" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "CNTY" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "AREA" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "PERIM" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "HEIGHT" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "WIDTH" ), QVariant::Double ) );
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), fields, outputType, crs );
QgsRectangle rect;
if ( onlySelectedFeatures ) // take only selection
{
rect = layer->boundingBoxOfSelected();
}
else
{
rect = layer->extent();
}
double minx = rect.xMinimum();
double miny = rect.yMinimum();
double maxx = rect.xMaximum();
double maxy = rect.yMaximum();
double height = rect.height();
double width = rect.width();
double cntx = minx + ( width / 2.0 );
double cnty = miny + ( height / 2.0 );
double area = width * height;
double perim = ( 2 * width ) + ( 2 * height );
QgsFeature feat;
QgsAttributes attrs( 10 );
attrs[0] = QVariant( minx );
attrs[1] = QVariant( miny );
attrs[2] = QVariant( maxx );
attrs[3] = QVariant( maxy );
attrs[4] = QVariant( cntx );
attrs[5] = QVariant( cnty );
attrs[6] = QVariant( area );
attrs[7] = QVariant( perim );
attrs[8] = QVariant( height );
attrs[9] = QVariant( width );
feat.setAttributes( attrs );
feat.setGeometry( QgsGeometry::fromRect( rect ) );
vWriter.addFeature( feat );
return true;
}
QList<double> QgsGeometryAnalyzer::simpleMeasure( QgsGeometry &mpGeometry )
{
QList<double> list;
double perim;
if ( mpGeometry.wkbType() == QgsWkbTypes::Point )
{
QgsPointXY pt = mpGeometry.asPoint();
list.append( pt.x() );
list.append( pt.y() );
}
else
{
QgsDistanceArea measure;
list.append( measure.measureArea( mpGeometry ) );
if ( mpGeometry.type() == QgsWkbTypes::PolygonGeometry )
{
perim = perimeterMeasure( mpGeometry, measure );
list.append( perim );
}
}
return list;
}
double QgsGeometryAnalyzer::perimeterMeasure( const QgsGeometry &geometry, QgsDistanceArea &measure )
{
return measure.measurePerimeter( geometry );
}
bool QgsGeometryAnalyzer::convexHull( QgsVectorLayer *layer, const QString &shapefileName,
bool onlySelectedFeatures, int uniqueIdField, QProgressDialog *p )
{
if ( !layer )
{
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
return false;
}
bool useField = false;
if ( uniqueIdField == -1 )
{
uniqueIdField = 0;
}
else
{
useField = true;
}
QgsFields fields;
fields.append( QgsField( QStringLiteral( "UID" ), QVariant::String ) );
fields.append( QgsField( QStringLiteral( "AREA" ), QVariant::Double ) );
fields.append( QgsField( QStringLiteral( "PERIM" ), QVariant::Double ) );
QgsWkbTypes::Type outputType = QgsWkbTypes::Polygon;
QgsCoordinateReferenceSystem crs = layer->crs();
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), fields, outputType, crs );
QgsFeature currentFeature;
QgsGeometry dissolveGeometry; //dissolve geometry
QMultiMap<QString, QgsFeatureId> map;
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
QgsFeatureIds::const_iterator it = selection.constBegin();
for ( ; it != selection.constEnd(); ++it )
{
#if 0
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
// break; // it may be better to do something else here?
return false;
}
#endif
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
{
continue;
}
map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
}
}
else
{
QgsFeatureIterator fit = layer->getFeatures();
while ( fit.nextFeature( currentFeature ) )
{
#if 0
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
// break; // it may be better to do something else here?
return false;
}
#endif
map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
}
}
QMultiMap<QString, QgsFeatureId>::const_iterator jt = map.constBegin();
while ( jt != map.constEnd() )
{
QString currentKey = jt.key();
int processedFeatures = 0;
//take only selection
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
if ( p )
{
p->setMaximum( selection.size() );
}
processedFeatures = 0;
while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
{
if ( p && p->wasCanceled() )
{
break;
}
if ( selection.contains( jt.value() ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
{
continue;
}
convexFeature( currentFeature, processedFeatures, dissolveGeometry );
++processedFeatures;
}
++jt;
}
QList<double> values;
if ( dissolveGeometry.isNull() )
{
QgsDebugMsg( "no dissolved geometry - should not happen" );
return false;
}
dissolveGeometry = dissolveGeometry.convexHull();
values = simpleMeasure( dissolveGeometry );
QgsAttributes attributes( 3 );
attributes[0] = QVariant( currentKey );
attributes[1] = values.at( 0 );
attributes[2] = values.at( 1 );
QgsFeature dissolveFeature;
dissolveFeature.setAttributes( attributes );
dissolveFeature.setGeometry( dissolveGeometry );
vWriter.addFeature( dissolveFeature );
}
//take all features
else
{
int featureCount = layer->featureCount();
if ( p )
{
p->setMaximum( featureCount );
}
processedFeatures = 0;
while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
{
continue;
}
convexFeature( currentFeature, processedFeatures, dissolveGeometry );
++processedFeatures;
++jt;
}
QList<double> values;
if ( dissolveGeometry.isNull() )
{
QgsDebugMsg( "no dissolved geometry - should not happen" );
return false;
}
dissolveGeometry = dissolveGeometry.convexHull();
// values = simpleMeasure( tmpGeometry );
values = simpleMeasure( dissolveGeometry );
QgsAttributes attributes;
attributes[0] = QVariant( currentKey );
attributes[1] = QVariant( values[ 0 ] );
attributes[2] = QVariant( values[ 1 ] );
QgsFeature dissolveFeature;
dissolveFeature.setAttributes( attributes );
dissolveFeature.setGeometry( dissolveGeometry );
vWriter.addFeature( dissolveFeature );
}
}
return true;
}
void QgsGeometryAnalyzer::convexFeature( QgsFeature &f, int nProcessedFeatures, QgsGeometry &dissolveGeometry )
{
if ( !f.hasGeometry() )
{
return;
}
QgsGeometry featureGeometry = f.geometry();
QgsGeometry convexGeometry = featureGeometry.convexHull();
if ( nProcessedFeatures == 0 )
{
dissolveGeometry = convexGeometry;
}
else
{
dissolveGeometry = dissolveGeometry.combine( convexGeometry );
}
}
bool QgsGeometryAnalyzer::dissolve( QgsVectorLayer *layer, const QString &shapefileName,
bool onlySelectedFeatures, int uniqueIdField, QProgressDialog *p )
{
if ( !layer )
{
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
return false;
}
bool useField = false;
if ( uniqueIdField == -1 )
{
uniqueIdField = 0;
}
else
{
useField = true;
}
QgsWkbTypes::Type outputType = dp->wkbType();
QgsCoordinateReferenceSystem crs = layer->crs();
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), layer->fields(), outputType, crs );
QgsFeature currentFeature;
QMultiMap<QString, QgsFeatureId> map;
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
QgsFeatureIds::const_iterator it = selection.constBegin();
for ( ; it != selection.constEnd(); ++it )
{
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
{
continue;
}
map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
}
}
else
{
QgsFeatureIterator fit = layer->getFeatures();
while ( fit.nextFeature( currentFeature ) )
{
map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
}
}
QgsGeometry dissolveGeometry; //dissolve geometry
QMultiMap<QString, QgsFeatureId>::const_iterator jt = map.constBegin();
QgsFeature outputFeature;
while ( jt != map.constEnd() )
{
QString currentKey = jt.key();
int processedFeatures = 0;
bool first = true;
//take only selection
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
if ( p )
{
p->setMaximum( selection.size() );
}
while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
{
if ( p && p->wasCanceled() )
{
break;
}
if ( selection.contains( jt.value() ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
{
continue;
}
if ( first )
{
outputFeature.setAttributes( currentFeature.attributes() );
first = false;
}
dissolveGeometry = dissolveFeature( currentFeature, dissolveGeometry );
++processedFeatures;
}
++jt;
}
}
//take all features
else
{
int featureCount = layer->featureCount();
if ( p )
{
p->setMaximum( featureCount );
}
while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
{
continue;
}
{
outputFeature.setAttributes( currentFeature.attributes() );
first = false;
}
dissolveGeometry = dissolveFeature( currentFeature, dissolveGeometry );
++processedFeatures;
++jt;
}
}
outputFeature.setGeometry( dissolveGeometry );
vWriter.addFeature( outputFeature );
}
return true;
}
QgsGeometry QgsGeometryAnalyzer::dissolveFeature( const QgsFeature &f, const QgsGeometry &dissolveInto )
{
if ( !f.hasGeometry() )
{
return dissolveInto;
}
QgsGeometry featureGeometry = f.geometry();
if ( dissolveInto.isNull() )
{
return featureGeometry;
}
else
{
return dissolveInto.combine( featureGeometry );
}
}
bool QgsGeometryAnalyzer::buffer( QgsVectorLayer *layer, const QString &shapefileName, double bufferDistance,
bool onlySelectedFeatures, bool dissolve, int bufferDistanceField, QProgressDialog *p )
{
if ( !layer )
{
return false;
}
QgsVectorDataProvider *dp = layer->dataProvider();
if ( !dp )
{
return false;
}
QgsWkbTypes::Type outputType = QgsWkbTypes::Polygon;
if ( dissolve )
{
outputType = QgsWkbTypes::MultiPolygon;
}
QgsCoordinateReferenceSystem crs = layer->crs();
QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), layer->fields(), outputType, crs );
QgsFeature currentFeature;
QgsGeometry dissolveGeometry; //dissolve geometry (if dissolve enabled)
//take only selection
if ( onlySelectedFeatures )
{
//use QgsVectorLayer::featureAtId
const QgsFeatureIds selection = layer->selectedFeatureIds();
if ( p )
{
p->setMaximum( selection.size() );
}
int processedFeatures = 0;
QgsFeatureIds::const_iterator it = selection.constBegin();
for ( ; it != selection.constEnd(); ++it )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
{
continue;
}
bufferFeature( currentFeature, processedFeatures, &vWriter, dissolve, dissolveGeometry, bufferDistance, bufferDistanceField );
++processedFeatures;
}
if ( p )
{
p->setValue( selection.size() );
}
}
//take all features
else
{
QgsFeatureIterator fit = layer->getFeatures();
int featureCount = layer->featureCount();
if ( p )
{
p->setMaximum( featureCount );
}
int processedFeatures = 0;
while ( fit.nextFeature( currentFeature ) )
{
if ( p )
{
p->setValue( processedFeatures );
}
if ( p && p->wasCanceled() )
{
break;
}
bufferFeature( currentFeature, processedFeatures, &vWriter, dissolve, dissolveGeometry, bufferDistance, bufferDistanceField );
++processedFeatures;
}
if ( p )
{
p->setValue( featureCount );
}
}
if ( dissolve )
{
QgsFeature dissolveFeature;
if ( dissolveGeometry.isNull() )
{
QgsDebugMsg( "no dissolved geometry - should not happen" );
return false;
}
dissolveFeature.setGeometry( dissolveGeometry );
vWriter.addFeature( dissolveFeature );
}
return true;
}
void QgsGeometryAnalyzer::bufferFeature( QgsFeature &f, int nProcessedFeatures, QgsVectorFileWriter *vfw, bool dissolve,
QgsGeometry &dissolveGeometry, double bufferDistance, int bufferDistanceField )
{
if ( !f.hasGeometry() )
{
return;
}
double currentBufferDistance;
QgsGeometry featureGeometry = f.geometry();
QgsGeometry bufferGeometry;
//create buffer
if ( bufferDistanceField == -1 )
{
currentBufferDistance = bufferDistance;
}
else
{
currentBufferDistance = f.attribute( bufferDistanceField ).toDouble();
}
bufferGeometry = featureGeometry.buffer( currentBufferDistance, 5 );
if ( dissolve )
{
if ( nProcessedFeatures == 0 )
{
dissolveGeometry = bufferGeometry;
}
else
{
dissolveGeometry = dissolveGeometry.combine( bufferGeometry );
}
}
else //dissolve
{
QgsFeature newFeature;
newFeature.setGeometry( bufferGeometry );
newFeature.setAttributes( f.attributes() );
//add it to vector file writer
if ( vfw )
{
vfw->addFeature( newFeature );
}
}
}
bool QgsGeometryAnalyzer::eventLayer( QgsVectorLayer *lineLayer, QgsVectorLayer *eventLayer, int lineField, int eventField, QgsFeatureIds &unlocatedFeatureIds, const QString &outputLayer,
const QString &outputFormat, int locationField1, int locationField2, int offsetField, double offsetScale,
bool forceSingleGeometry, QgsVectorDataProvider *memoryProvider, QProgressDialog *p )
{
if ( !lineLayer || !eventLayer || !lineLayer->isValid() || !eventLayer->isValid() )
{
return false;
}
//create line field / id map for line layer
QMultiHash< QString, QgsFeature > lineLayerIdMap; //1:n possible (e.g. several linear reference geometries for one feature in the event layer)
QgsFeatureIterator fit = lineLayer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() << lineField ) );
QgsFeature fet;
while ( fit.nextFeature( fet ) )
{
lineLayerIdMap.insert( fet.attribute( lineField ).toString(), fet );
}
//create output datasource or attributes in memory provider
QgsVectorFileWriter *fileWriter = nullptr;
QgsFeatureList memoryProviderFeatures;
if ( !memoryProvider )
{
QgsWkbTypes::Type memoryProviderType = QgsWkbTypes::MultiLineString;
if ( locationField2 == -1 )
{
memoryProviderType = forceSingleGeometry ? QgsWkbTypes::Point : QgsWkbTypes::MultiPoint;
}
else
{
memoryProviderType = forceSingleGeometry ? QgsWkbTypes::LineString : QgsWkbTypes::MultiLineString;
}
fileWriter = new QgsVectorFileWriter( outputLayer,
eventLayer->dataProvider()->encoding(),
eventLayer->fields(),
memoryProviderType,
lineLayer->crs(),
outputFormat );
}
else
{
memoryProvider->addAttributes( eventLayer->fields().toList() );
}
//iterate over eventLayer and write new features to output file or layer
fit = eventLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) );
QgsGeometry lrsGeom;
double measure1, measure2 = 0.0;
int nEventFeatures = eventLayer->featureCount();
int featureCounter = 0;
int nOutputFeatures = 0; //number of output features for the current event feature
if ( p )
{
p->setWindowModality( Qt::WindowModal );
p->setMinimum( 0 );
p->setMaximum( nEventFeatures );
p->show();
}
while ( fit.nextFeature( fet ) )
{
nOutputFeatures = 0;
//update progress dialog
if ( p )
{
if ( p->wasCanceled() )
{
break;
}
p->setValue( featureCounter );
++featureCounter;
}
measure1 = fet.attribute( locationField1 ).toDouble();
if ( locationField2 != -1 )
{
measure2 = fet.attribute( locationField2 ).toDouble();
if ( qgsDoubleNear( ( measure2 - measure1 ), 0.0 ) )
{
continue;
}
}
QList<QgsFeature> featureIdList = lineLayerIdMap.values( fet.attribute( eventField ).toString() );
QList<QgsFeature>::iterator featureIdIt = featureIdList.begin();
for ( ; featureIdIt != featureIdList.end(); ++featureIdIt )
{
if ( locationField2 == -1 )
{
lrsGeom = locateAlongMeasure( measure1, featureIdIt->geometry() );
}
else
{
lrsGeom = locateBetweenMeasures( measure1, measure2, featureIdIt->geometry() );
}
if ( !lrsGeom.isNull() )
{
++nOutputFeatures;
addEventLayerFeature( fet, lrsGeom, featureIdIt->geometry(), fileWriter, memoryProviderFeatures, offsetField, offsetScale, forceSingleGeometry );
}
}
if ( nOutputFeatures < 1 )
{
unlocatedFeatureIds.insert( fet.id() );
}
}
if ( p )
{
p->setValue( nEventFeatures );
}
if ( memoryProvider )
{
memoryProvider->addFeatures( memoryProviderFeatures );
}
delete fileWriter;
return true;
}
void QgsGeometryAnalyzer::addEventLayerFeature( QgsFeature &feature, const QgsGeometry &geom, const QgsGeometry &lineGeom, QgsVectorFileWriter *fileWriter, QgsFeatureList &memoryFeatures,
int offsetField, double offsetScale, bool forceSingleType )
{
if ( geom.isNull() )
{
return;
}
QList<QgsGeometry> geomList;
if ( forceSingleType )
{
geomList = geom.asGeometryCollection();
}
else
{
geomList.push_back( geom );
}
QList<QgsGeometry>::iterator geomIt = geomList.begin();
for ( ; geomIt != geomList.end(); ++geomIt )
{
//consider offset
QgsGeometry newGeom = *geomIt;
if ( offsetField >= 0 )
{
double offsetVal = feature.attribute( offsetField ).toDouble();
offsetVal *= offsetScale;
newGeom = createOffsetGeometry( *geomIt, lineGeom, offsetVal );
if ( newGeom.isNull() )
{
continue;
}
}
feature.setGeometry( newGeom );
if ( fileWriter )
{
fileWriter->addFeature( feature );
}
else
{
memoryFeatures << feature;
}
}
}
QgsGeometry QgsGeometryAnalyzer::createOffsetGeometry( const QgsGeometry &geom, const QgsGeometry &lineGeom, double offset )
{
if ( !geom || lineGeom.isNull() )
{
return QgsGeometry();
}
QList<QgsGeometry> inputGeomList;
if ( geom.isMultipart() )
{
inputGeomList = geom.asGeometryCollection();
}
else
{
inputGeomList.push_back( geom );
}
QList<GEOSGeometry *> outputGeomList;
QList<QgsGeometry>::const_iterator inputGeomIt = inputGeomList.constBegin();
GEOSContextHandle_t geosctxt = QgsGeometry::getGEOSHandler();
for ( ; inputGeomIt != inputGeomList.constEnd(); ++inputGeomIt )
{
if ( geom.type() == QgsWkbTypes::LineGeometry )
{
GEOSGeometry *inputGeomItGeos = inputGeomIt->exportToGeos();
GEOSGeometry *offsetGeom = GEOSOffsetCurve_r( geosctxt, inputGeomItGeos, -offset, 8 /*quadSegments*/, 0 /*joinStyle*/, 5.0 /*miterLimit*/ );
GEOSGeom_destroy_r( geosctxt, inputGeomItGeos );
if ( !offsetGeom || !GEOSisValid_r( geosctxt, offsetGeom ) )
{
return QgsGeometry();
}
if ( !GEOSisValid_r( geosctxt, offsetGeom ) || GEOSGeomTypeId_r( geosctxt, offsetGeom ) != GEOS_LINESTRING || GEOSGeomGetNumPoints_r( geosctxt, offsetGeom ) < 1 )
{
GEOSGeom_destroy_r( geosctxt, offsetGeom );
return QgsGeometry();
}
outputGeomList.push_back( offsetGeom );
}
else if ( geom.type() == QgsWkbTypes::PointGeometry )
{
QgsPointXY p = ( *inputGeomIt ).asPoint();
p = createPointOffset( p.x(), p.y(), offset, lineGeom );
GEOSCoordSequence *ptSeq = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
GEOSCoordSeq_setX_r( geosctxt, ptSeq, 0, p.x() );
GEOSCoordSeq_setY_r( geosctxt, ptSeq, 0, p.y() );
GEOSGeometry *geosPt = GEOSGeom_createPoint_r( geosctxt, ptSeq );
outputGeomList.push_back( geosPt );
}
}
QgsGeometry outGeometry;
if ( !geom.isMultipart() )
{
GEOSGeometry *outputGeom = outputGeomList.at( 0 );
if ( outputGeom )
{
outGeometry.fromGeos( outputGeom );
}
}
else
{
GEOSGeometry **geomArray = new GEOSGeometry*[outputGeomList.size()];
for ( int i = 0; i < outputGeomList.size(); ++i )
{
geomArray[i] = outputGeomList.at( i );
}
GEOSGeometry *collection = nullptr;
if ( geom.type() == QgsWkbTypes::PointGeometry )
{
collection = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOINT, geomArray, outputGeomList.size() );
}
else if ( geom.type() == QgsWkbTypes::LineGeometry )
{
collection = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTILINESTRING, geomArray, outputGeomList.size() );
}
outGeometry.fromGeos( collection );
delete[] geomArray;
}
return outGeometry;
}
QgsPointXY QgsGeometryAnalyzer::createPointOffset( double x, double y, double dist, const QgsGeometry &lineGeom ) const
{
QgsPointXY p( x, y );
QgsPointXY minDistPoint;
int afterVertexNr;
lineGeom.closestSegmentWithContext( p, minDistPoint, afterVertexNr );
int beforeVertexNr = afterVertexNr - 1;
QgsPointXY beforeVertex = lineGeom.vertexAt( beforeVertexNr );
QgsPointXY afterVertex = lineGeom.vertexAt( afterVertexNr );
//get normal vector
double dx = afterVertex.x() - beforeVertex.x();
double dy = afterVertex.y() - beforeVertex.y();
double normalX = -dy;
double normalY = dx; //#spellok
double normalLength = sqrt( normalX * normalX + normalY * normalY ); //#spellok
normalX *= ( dist / normalLength );
normalY *= ( dist / normalLength ); //#spellok
double debugLength = sqrt( normalX * normalX + normalY * normalY ); //control //#spellok
Q_UNUSED( debugLength );
return QgsPointXY( x - normalX, y - normalY ); //negative values -> left side, positive values -> right side //#spellok
}
QgsGeometry QgsGeometryAnalyzer::locateBetweenMeasures( double fromMeasure, double toMeasure, const QgsGeometry &lineGeom )
{
if ( lineGeom.isNull() )
{
return QgsGeometry();
}
QgsMultiPolyline resultGeom;
//need to go with WKB and z coordinate until QgsGeometry supports M values
QByteArray wkb( lineGeom.exportToWkb() );
QgsConstWkbPtr wkbPtr( wkb );
wkbPtr.readHeader();
QgsWkbTypes::Type wkbType = lineGeom.wkbType();
if ( wkbType != QgsWkbTypes::LineString25D && wkbType != QgsWkbTypes::MultiLineString25D )
{
return QgsGeometry();
}
if ( wkbType == QgsWkbTypes::LineString25D )
{
locateBetweenWkbString( wkbPtr, resultGeom, fromMeasure, toMeasure );
}
else if ( wkbType == QgsWkbTypes::MultiLineString25D )
{
int nLines;
wkbPtr >> nLines;
for ( int i = 0; i < nLines; ++i )
{
wkbPtr.readHeader();
wkbPtr = locateBetweenWkbString( wkbPtr, resultGeom, fromMeasure, toMeasure );
}
}
if ( resultGeom.size() < 1 )
{
return QgsGeometry();
}
return QgsGeometry::fromMultiPolyline( resultGeom );
}
QgsGeometry QgsGeometryAnalyzer::locateAlongMeasure( double measure, const QgsGeometry &lineGeom )
{
if ( lineGeom.isNull() )
{
return QgsGeometry();
}
QgsMultiPoint resultGeom;
//need to go with WKB and z coordinate until QgsGeometry supports M values
QByteArray wkb( lineGeom.exportToWkb() );
QgsConstWkbPtr wkbPtr( wkb );
QgsWkbTypes::Type wkbType = lineGeom.wkbType();
if ( wkbType != QgsWkbTypes::LineString25D && wkbType != QgsWkbTypes::MultiLineString25D )
{
return QgsGeometry();
}
if ( wkbType == QgsWkbTypes::LineString25D )
{
locateAlongWkbString( wkbPtr, resultGeom, measure );
}
else if ( wkbType == QgsWkbTypes::MultiLineString25D )
{
int nLines;
wkbPtr >> nLines;
for ( int i = 0; i < nLines; ++i )
{
wkbPtr.readHeader();
wkbPtr = locateAlongWkbString( wkbPtr, resultGeom, measure );
}
}
if ( resultGeom.size() < 1 )
{
return QgsGeometry();
}
return QgsGeometry::fromMultiPoint( resultGeom );
}
QgsConstWkbPtr QgsGeometryAnalyzer::locateBetweenWkbString( QgsConstWkbPtr wkbPtr, QgsMultiPolyline &result, double fromMeasure, double toMeasure )
{
int nPoints;
wkbPtr >> nPoints;
QgsPolyline currentLine;
double prevx = 0.0, prevy = 0.0, prevz = 0.0;
for ( int i = 0; i < nPoints; ++i )
{
double x, y, z;
wkbPtr >> x >> y >> z;
if ( i > 0 )
{
QgsPointXY pt1, pt2;
bool secondPointClipped; //true if second point is != segment endpoint
bool measureInSegment = clipSegmentByRange( prevx, prevy, prevz, x, y, z, fromMeasure, toMeasure, pt1, pt2, secondPointClipped );
if ( measureInSegment )
{
if ( currentLine.size() < 1 ) //no points collected yet, so the first point needs to be added to the line
{
currentLine.append( pt1 );
}
if ( pt1 != pt2 ) //avoid duplicated entry if measure value equals m-value of vertex
{
currentLine.append( pt2 );
}
if ( secondPointClipped || i == nPoints - 1 ) //close current segment
{
if ( currentLine.size() > 1 )
{
result.append( currentLine );
}
currentLine.clear();
}
}
}
prevx = x;
prevy = y;
prevz = z;
}
return wkbPtr;
}
QgsConstWkbPtr QgsGeometryAnalyzer::locateAlongWkbString( QgsConstWkbPtr wkbPtr, QgsMultiPoint &result, double measure )
{
int nPoints;
wkbPtr >> nPoints;
double x, y, z;
double prevx = 0.0, prevy = 0.0, prevz = 0.0;
for ( int i = 0; i < nPoints; ++i )
{
wkbPtr >> x >> y >> z;
if ( i > 0 )
{
QgsPointXY pt1, pt2;
bool pt1Ok, pt2Ok;
locateAlongSegment( prevx, prevy, prevz, x, y, z, measure, pt1Ok, pt1, pt2Ok, pt2 );
if ( pt1Ok )
{
result.append( pt1 );
}
if ( pt2Ok && i == nPoints - 1 )
{
result.append( pt2 );
}
}
prevx = x;
prevy = y;
prevz = z;
}
return wkbPtr;
}
bool QgsGeometryAnalyzer::clipSegmentByRange( double x1, double y1, double m1, double x2, double y2, double m2, double range1, double range2, QgsPointXY &pt1,
QgsPointXY &pt2, bool &secondPointClipped )
{
bool reversed = m1 > m2;
double tmp;
//reverse m1, m2 if necessary (and consequently also x1,x2 / y1, y2)
if ( reversed )
{
tmp = m1;
m1 = m2;
m2 = tmp;
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
}
//reverse range1, range2 if necessary
if ( range1 > range2 )
{
tmp = range1;
range1 = range2;
range2 = tmp;
}
//segment completely outside of range
if ( m2 < range1 || m1 > range2 )
{
return false;
}
//segment completely inside of range
if ( m2 <= range2 && m1 >= range1 )
{
if ( reversed )
{
pt1.setX( x2 );
pt1.setY( y2 );
pt2.setX( x1 );
pt2.setY( y1 );
}
else
{
pt1.setX( x1 );
pt1.setY( y1 );
pt2.setX( x2 );
pt2.setY( y2 );
}
secondPointClipped = false;
return true;
}
//m1 inside and m2 not
if ( m1 >= range1 && m1 <= range2 )
{
pt1.setX( x1 );
pt1.setY( y1 );
double dist = ( range2 - m1 ) / ( m2 - m1 );
pt2.setX( x1 + ( x2 - x1 ) * dist );
pt2.setY( y1 + ( y2 - y1 ) * dist );
secondPointClipped = !reversed;
}
//m2 inside and m1 not
if ( m2 >= range1 && m2 <= range2 )
{
pt2.setX( x2 );
pt2.setY( y2 );
double dist = ( m2 - range1 ) / ( m2 - m1 );
pt1.setX( x2 - ( x2 - x1 ) * dist );
pt1.setY( y2 - ( y2 - y1 ) * dist );
secondPointClipped = reversed;
}
//range1 and range 2 both inside the segment
if ( range1 >= m1 && range2 <= m2 )
{
double dist1 = ( range1 - m1 ) / ( m2 - m1 );
double dist2 = ( range2 - m1 ) / ( m2 - m1 );
pt1.setX( x1 + ( x2 - x1 ) * dist1 );
pt1.setY( y1 + ( y2 - y1 ) * dist1 );
pt2.setX( x1 + ( x2 - x1 ) * dist2 );
pt2.setY( y1 + ( y2 - y1 ) * dist2 );
secondPointClipped = true;
}
if ( reversed ) //switch p1 and p2
{
QgsPointXY tmpPt = pt1;
pt1 = pt2;
pt2 = tmpPt;
}
return true;
}
void QgsGeometryAnalyzer::locateAlongSegment( double x1, double y1, double m1, double x2, double y2, double m2, double measure, bool &pt1Ok, QgsPointXY &pt1, bool &pt2Ok, QgsPointXY &pt2 )
{
bool reversed = false;
pt1Ok = false;
pt2Ok = false;
double tolerance = 0.000001; //work with a small tolerance to catch e.g. locations at endpoints
if ( m1 > m2 )
{
double tmp = m1;
m1 = m2;
m2 = tmp;
reversed = true;
}
//segment does not match
if ( ( m1 - measure ) > tolerance || ( measure - m2 ) > tolerance )
{
pt1Ok = false;
pt2Ok = false;
return;
}
//match with vertex1
if ( qgsDoubleNear( m1, measure, tolerance ) )
{
if ( reversed )
{
pt2Ok = true;
pt2.setX( x2 );
pt2.setY( y2 );
}
else
{
pt1Ok = true;
pt1.setX( x1 );
pt1.setY( y1 );
}
}
//match with vertex2
if ( qgsDoubleNear( m2, measure, tolerance ) )
{
if ( reversed )
{
pt1Ok = true;
pt1.setX( x1 );
pt1.setY( y1 );
}
else
{
pt2Ok = true;
pt2.setX( x2 );
pt2.setY( y2 );
}
}
if ( pt1Ok || pt2Ok )
{
return;
}
//match between the vertices
if ( qgsDoubleNear( m1, m2 ) )
{
pt1.setX( x1 );
pt1.setY( y1 );
pt1Ok = true;
return;
}
double dist = ( measure - m1 ) / ( m2 - m1 );
if ( reversed )
{
dist = 1 - dist;
}
pt1.setX( x1 + dist * ( x2 - x1 ) );
pt1.setY( y1 + dist * ( y2 - y1 ) );
pt1Ok = true;
}