Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 3 additions & 3 deletions src/analysis/processing/pdal/qgsalgorithmpdalcompare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,19 @@ bool QgsPdalCompareAlgorithm::checkParameterValues( const QVariantMap &parameter

const QString outputName = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );

if ( inputLayer->source().endsWith( ".vpc"_L1 ) )
if ( inputLayer->source().endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || inputLayer->source().endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )
{
*message = QObject::tr( "This algorithm does not support VPC files as the Input layer." );
return false;
}

if ( inputCompareLayer->source().endsWith( ".vpc"_L1 ) )
if ( inputCompareLayer->source().endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || inputCompareLayer->source().endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )
{
*message = QObject::tr( "This algorithm does not support VPC files as the Compare layer." );
return false;
}

if ( outputName.endsWith( ".vpc"_L1 ) )
if ( outputName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || outputName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) )
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( outputName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || outputName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) )
if ( outputName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || outputName.endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦

{
*message = QObject::tr( "This algorithm does not support VPC files as the Output layer." );
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/processing/pdal/qgsalgorithmpdalmerge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ QStringList QgsPdalMergeAlgorithm::createArgumentLists( const QVariantMap &param

const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );

if ( outputFile.endsWith( u".vpc"_s, Qt::CaseInsensitive ) )
if ( outputFile.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || outputFile.endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )
throw QgsProcessingException(
QObject::tr(
"This algorithm does not support output to VPC. Please use LAS or LAZ as the output format. "
Expand Down
15 changes: 8 additions & 7 deletions src/analysis/processing/pdal/qgspdalalgorithmbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,13 @@ void QgsPdalAlgorithmBase::applyThreadsParameter( QStringList &arguments, QgsPro

QString QgsPdalAlgorithmBase::fixOutputFileName( const QString &inputFileName, const QString &outputFileName, QgsProcessingContext &context )
{
bool inputIsVpc = inputFileName.endsWith( u".vpc"_s, Qt::CaseInsensitive );
bool isTempOutput = outputFileName.startsWith( QgsProcessingUtils::tempFolder(), Qt::CaseInsensitive );
const QFileInfo ifi( inputFileName );
const bool inputIsVpc = ifi.suffix().compare( "vpc", Qt::CaseInsensitive ) == 0 || ifi.suffix().compare( "vpz", Qt::CaseInsensitive ) == 0;
const bool isTempOutput = outputFileName.startsWith( QgsProcessingUtils::tempFolder(), Qt::CaseInsensitive );
if ( inputIsVpc && isTempOutput )
{
QFileInfo fi( outputFileName );
QString newFileName = fi.path() + '/' + fi.completeBaseName() + u".vpc"_s;
const QFileInfo ofi( outputFileName );
const QString newFileName = u"%1/%2.%3"_s.arg( ofi.path(), ofi.completeBaseName(), ifi.suffix().toLower() );

if ( context.willLoadLayerOnCompletion( outputFileName ) )
{
Expand All @@ -145,8 +146,8 @@ QString QgsPdalAlgorithmBase::fixOutputFileName( const QString &inputFileName, c

void QgsPdalAlgorithmBase::checkOutputFormat( const QString &inputFileName, const QString &outputFileName )
{
bool inputIsVpc = inputFileName.endsWith( u".vpc"_s, Qt::CaseInsensitive );
bool outputIsVpc = outputFileName.endsWith( u".vpc"_s, Qt::CaseInsensitive );
bool inputIsVpc = inputFileName.endsWith( u".vpc"_s, Qt::CaseInsensitive ) || inputFileName.endsWith( u".vpz"_s, Qt::CaseInsensitive );
bool outputIsVpc = outputFileName.endsWith( u".vpc"_s, Qt::CaseInsensitive ) || outputFileName.endsWith( u".vpz"_s, Qt::CaseInsensitive );
if ( !inputIsVpc && outputIsVpc )
throw QgsProcessingException(
QObject::tr(
Expand Down Expand Up @@ -358,7 +359,7 @@ QString QgsPdalAlgorithmBase::copcIndexFile( const QString &filename )

void QgsPdalAlgorithmBase::applyVpcOutputFormatParameter( const QString &outputFilename, QStringList &arguments, const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
if ( outputFilename.endsWith( u".vpc"_s, Qt::CaseInsensitive ) )
if ( outputFilename.endsWith( u".vpc"_s, Qt::CaseInsensitive ) || outputFilename.endsWith( u".vpz"_s, Qt::CaseInsensitive ) )
{
QString vpcOutputFormat = parameterAsEnumString( parameters, u"VPC_OUTPUT_FORMAT"_s, context );

Expand Down
2 changes: 1 addition & 1 deletion src/analysis/processing/pdal/qgspdalalgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ QList<QPair<QString, QString>> QgsPdalAlgorithms::supportedOutputRasterLayerForm

QStringList QgsPdalAlgorithms::supportedOutputPointCloudLayerExtensions() const
{
return QStringList() << u"las"_s << u"laz"_s << u"copc.laz"_s << u"vpc"_s;
return QStringList() << u"las"_s << u"laz"_s << u"copc.laz"_s << u"vpc"_s << u"vpz"_s;
}

void QgsPdalAlgorithms::loadAlgorithms()
Expand Down
92 changes: 79 additions & 13 deletions src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
#include "qgsproviderutils.h"
#include "qgsruntimeprofiler.h"
#include "qgsthreadingutils.h"
#include "qgsziputils.h"

#include <QIcon>
#include <QString>
#include <QTemporaryDir>

#include "moc_qgsvirtualpointcloudprovider.cpp"

Expand All @@ -51,6 +53,7 @@ using namespace Qt::StringLiterals;
#define PROVIDER_KEY u"vpc"_s
#define PROVIDER_DESCRIPTION u"Virtual point cloud data provider"_s


QgsVirtualPointCloudProvider::QgsVirtualPointCloudProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
: QgsPointCloudDataProvider( uri, options, flags )
{
Expand Down Expand Up @@ -174,25 +177,31 @@ void QgsVirtualPointCloudProvider::parseFile()
url.setUrl( path );
QNetworkRequest request( url );
const QgsNetworkReplyContent reply = QgsNetworkAccessManager::instance()->blockingGet( request, authcfg );
if ( reply.error() == QNetworkReply::NoError )
if ( reply.error() != QNetworkReply::NoError )
{
jsonData = reply.content();
appendError( QgsErrorMessage( u"Could not download file: %1"_s.arg( reply.errorString() ) ) );
return;
}
else

QTemporaryDir tmpDir;
QFile file( tmpDir.filePath( url.fileName() ) );
if ( !file.open( QFile::WriteOnly ) )
{
appendError( QgsErrorMessage( u"Could not download file: %1"_s.arg( reply.errorString() ) ) );
appendError( QgsErrorMessage( u"Could not create temporary file: %1"_s.arg( file.fileName() ) ) );
return;
}

file.write( reply.content() );
file.close();

mAllLocalFiles = false;

jsonData = readFileContents( file.fileName() );
}
else
{
url = QUrl::fromLocalFile( path );
QFile file( url.toLocalFile() );
if ( file.open( QFile::ReadOnly ) )
{
jsonData = file.readAll();
}
jsonData = readFileContents( url.toLocalFile() );
}

if ( jsonData.isEmpty() )
Expand Down Expand Up @@ -467,6 +476,58 @@ void QgsVirtualPointCloudProvider::parseFile()
populateAttributeCollection( attributeNames );
}

QByteArray QgsVirtualPointCloudProvider::readFileContents( const QString &path )
{
std::unique_ptr<QTemporaryDir> tmpDir;
QString readFromFilename;
if ( path.endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )
{
tmpDir = std::make_unique<QTemporaryDir>();
if ( !tmpDir->isValid() )
{
appendError( QgsErrorMessage( u"Could not create temporary folder"_s ) );
return {};
}
QStringList fileList;
if ( !QgsZipUtils::unzip( path, tmpDir->path(), fileList ) )
{
appendError( QgsErrorMessage( u"Could not open VPZ file"_s ) );
return {};
}

const QDir dir( tmpDir->path() );
for ( const QString &f : std::as_const( fileList ) )
{
if ( f.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) )
{
if ( !readFromFilename.isEmpty() )
{
appendError( QgsErrorMessage( u"VPZ file contains multiple VPCs"_s ) );
return {};
}
readFromFilename = dir.filePath( f );
}
}
Comment on lines +498 to +504
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we care if there are multiple VPCs in the file? Can we not just grab first and return early? We are only reading the ZIP and are not error detecting/correcting. I believe other software also just use the first file that has the correct extension in similar situations.

No strong opinion.

However, if you'd still prefer to have the check instead of an early return, consider something like this instead of the loop:

const QStringList vpcFiles = dir.entryList( QStringList( "*.vpc" ), QDir::Files );
if ( vpcFiles.isEmpty() )
... 
else if ( vpcFiles.size() > 1 )
...

readFromFilename = dir.filePath( vpcFiles.first() );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not to use the first vpc in case there are multiple, as that may not be obvious to the user that only a single vpc is being used.
I updated to use the suggested QDir::entryList :)

if ( readFromFilename.isEmpty() )
{
appendError( QgsErrorMessage( u"VPZ file does not contain any VPCs"_s ) );
return {};
}
}
else
{
readFromFilename = path;
}

QFile file( readFromFilename );
if ( !file.open( QFile::ReadOnly ) )
{
appendError( QgsErrorMessage( u"Could not open VPC file"_s ) );
return {};
}
return file.readAll();
}

QgsGeometry QgsVirtualPointCloudProvider::polygonBounds() const
{
return *mPolygonBounds;
Expand Down Expand Up @@ -659,7 +720,7 @@ QgsVirtualPointCloudProvider *QgsVirtualPointCloudProviderMetadata::createProvid
QList<QgsProviderSublayerDetails> QgsVirtualPointCloudProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
{
const QVariantMap parts = decodeUri( uri );
if ( parts.value( u"file-name"_s ).toString().endsWith( ".vpc", Qt::CaseSensitivity::CaseInsensitive ) )
if ( isVpcFileName( parts.value( u"file-name"_s ).toString() ) )
{
QgsProviderSublayerDetails details;
details.setUri( uri );
Expand All @@ -677,7 +738,7 @@ QList<QgsProviderSublayerDetails> QgsVirtualPointCloudProviderMetadata::querySub
int QgsVirtualPointCloudProviderMetadata::priorityForUri( const QString &uri ) const
{
const QVariantMap parts = decodeUri( uri );
if ( parts.value( u"file-name"_s ).toString().endsWith( ".vpc", Qt::CaseSensitivity::CaseInsensitive ) )
if ( isVpcFileName( parts.value( u"file-name"_s ).toString() ) )
return 100;

return 0;
Expand All @@ -686,7 +747,7 @@ int QgsVirtualPointCloudProviderMetadata::priorityForUri( const QString &uri ) c
QList<Qgis::LayerType> QgsVirtualPointCloudProviderMetadata::validLayerTypesForUri( const QString &uri ) const
{
const QVariantMap parts = decodeUri( uri );
if ( parts.value( u"file-name"_s ).toString().endsWith( ".vpc", Qt::CaseSensitivity::CaseInsensitive ) )
if ( isVpcFileName( parts.value( u"file-name"_s ).toString() ) )
return QList< Qgis::LayerType>() << Qgis::LayerType::PointCloud;

return QList< Qgis::LayerType>();
Expand Down Expand Up @@ -725,7 +786,7 @@ QString QgsVirtualPointCloudProviderMetadata::filters( Qgis::FileFilterType type
return QString();

case Qgis::FileFilterType::PointCloud:
return QObject::tr( "Virtual Point Clouds" ) + u" (*.vpc *.VPC)"_s;
return QObject::tr( "Virtual Point Clouds" ) + u" (*.vpc *.VPC *.vpz *.VPZ)"_s;
}
return QString();
}
Expand All @@ -740,6 +801,11 @@ QList<Qgis::LayerType> QgsVirtualPointCloudProviderMetadata::supportedLayerTypes
return { Qgis::LayerType::PointCloud };
}

bool QgsVirtualPointCloudProviderMetadata::isVpcFileName( const QString &name )
{
return name.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) || name.endsWith( ".vpz"_L1, Qt::CaseInsensitive );
}

QString QgsVirtualPointCloudProviderMetadata::encodeUri( const QVariantMap &parts ) const
{
QString uri = parts.value( u"path"_s ).toString();
Expand Down
2 changes: 2 additions & 0 deletions src/core/providers/vpc/qgsvirtualpointcloudprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class CORE_EXPORT QgsVirtualPointCloudProvider : public QgsPointCloudDataProvide

private:
void parseFile();
QByteArray readFileContents( const QString &path );
void populateAttributeCollection( QSet<QString> names );
QVector<QgsPointCloudSubIndex> mSubLayers;
std::unique_ptr<QgsGeometry> mPolygonBounds;
Expand Down Expand Up @@ -130,6 +131,7 @@ class QgsVirtualPointCloudProviderMetadata : public QgsProviderMetadata
QString filters( Qgis::FileFilterType type ) override;
ProviderCapabilities providerCapabilities() const override;
QList< Qgis::LayerType > supportedLayerTypes() const override;
static bool isVpcFileName( const QString &name );
};

///@endcond
Expand Down
7 changes: 5 additions & 2 deletions src/gui/providers/qgspointcloudsourceselect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,12 @@ void QgsPointCloudSourceSelect::addButtonClicked()
QUrl url = QUrl::fromUserInput( mPath );
QString fileName = url.fileName();

if ( fileName.compare( "ept.json"_L1, Qt::CaseInsensitive ) != 0 && !fileName.endsWith( ".copc.laz"_L1, Qt::CaseInsensitive ) && !fileName.endsWith( ".vpc"_L1, Qt::CaseInsensitive ) )
if ( fileName.compare( "ept.json"_L1, Qt::CaseInsensitive ) != 0
&& !fileName.endsWith( ".copc.laz"_L1, Qt::CaseInsensitive )
&& !fileName.endsWith( ".vpc"_L1, Qt::CaseInsensitive )
&& !fileName.endsWith( ".vpz"_L1, Qt::CaseInsensitive ) )
{
QMessageBox::information( this, tr( "Add Point Cloud Layers" ), tr( "Invalid point cloud URL \"%1\", please make sure your URL ends with /ept.json or .copc.laz or .vpc" ).arg( mPath ) );
QMessageBox::information( this, tr( "Add Point Cloud Layers" ), tr( "Invalid point cloud URL \"%1\", please make sure your URL ends with /ept.json, .copc.laz, .vpc or .vpz" ).arg( mPath ) );
return;
}

Expand Down
Loading
Loading