Skip to content

Commit cc9f5fb

Browse files
NEDJIMAbelgacemwonder-sk
authored andcommittedMar 31, 2022
inital implementation of local COPC reading
1 parent 995c290 commit cc9f5fb

17 files changed

+1254
-2
lines changed
 

‎CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ if(WITH_CORE)
286286

287287
set (WITH_EPT TRUE CACHE BOOL "Determines whether Entwine Point Cloud (EPT) support should be built")
288288

289+
set (WITH_COPC TRUE CACHE BOOL "Determines whether Cloud Optimized Point Cloud (COPC) support should be built")
290+
289291
set (WITH_THREAD_LOCAL TRUE CACHE BOOL "Determines whether std::thread_local should be used")
290292
mark_as_advanced(WITH_THREAD_LOCAL)
291293

@@ -418,6 +420,15 @@ if(WITH_CORE)
418420
set(HAVE_EPT TRUE) # used in qgsconfig.h
419421
endif()
420422

423+
if (WITH_COPC) # COPC provider
424+
find_package(ZSTD REQUIRED) # for decompression of point clouds
425+
find_package(LazPerf) # for decompression of point clouds
426+
if (NOT LazPerf_FOUND)
427+
message(STATUS "Using embedded laz-perf")
428+
endif()
429+
set(HAVE_COPC TRUE) # used in qgsconfig.h
430+
endif()
431+
421432
if (WITH_PDAL)
422433
if (NOT WITH_EPT)
423434
message(FATAL_ERROR "PDAL provider cannot be built with EPT disabled")

‎cmake_templates/qgsconfig.h.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@
9898

9999
#cmakedefine HAVE_EPT
100100

101+
#cmakedefine HAVE_COPC
102+
101103
#cmakedefine HAVE_PDAL_QGIS
102104
#define PDAL_VERSION "${PDAL_VERSION}"
103105
#define PDAL_VERSION_MAJOR_INT ${PDAL_VERSION_MAJOR}

‎src/core/CMakeLists.txt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,55 @@ if (WITH_EPT)
19731973
add_definitions( -DWITH_EPT )
19741974
endif()
19751975

1976+
if (WITH_COPC)
1977+
include_directories(providers/copc)
1978+
1979+
include_directories(SYSTEM
1980+
${ZSTD_INCLUDE_DIR}
1981+
)
1982+
1983+
if (LazPerf_FOUND)
1984+
# Use system laz-perf
1985+
include_directories(SYSTEM
1986+
${LazPerf_INCLUDE_DIR}
1987+
)
1988+
else()
1989+
# Use embedded laz-perf from external/laz-perf
1990+
include_directories(SYSTEM
1991+
)
1992+
1993+
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
1994+
${CMAKE_SOURCE_DIR}/external/lazperf/charbuf.cpp
1995+
${CMAKE_SOURCE_DIR}/external/lazperf/filestream.cpp
1996+
${CMAKE_SOURCE_DIR}/external/lazperf/header.cpp
1997+
${CMAKE_SOURCE_DIR}/external/lazperf/lazperf.cpp
1998+
${CMAKE_SOURCE_DIR}/external/lazperf/readers.cpp
1999+
${CMAKE_SOURCE_DIR}/external/lazperf/vlr.cpp
2000+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte10.cpp
2001+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte14.cpp
2002+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_gpstime10.cpp
2003+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_nir14.cpp
2004+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point10.cpp
2005+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point14.cpp
2006+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb10.cpp
2007+
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb14.cpp
2008+
)
2009+
endif()
2010+
2011+
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
2012+
providers/copc/qgscopcprovider.cpp
2013+
pointcloud/qgseptdecoder.cpp
2014+
pointcloud/qgscopcpointcloudindex.cpp
2015+
)
2016+
set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS}
2017+
providers/copc/qgscopcprovider.h
2018+
pointcloud/qgseptdecoder.h
2019+
pointcloud/qgscopcpointcloudindex.h
2020+
)
2021+
2022+
add_definitions( -DWITH_COPC )
2023+
endif()
2024+
19762025
if (APPLE)
19772026
# Libtasn1 is for DER-encoded PKI ASN.1 parsing/extracting workarounds
19782027
include_directories(SYSTEM
@@ -2109,6 +2158,11 @@ if (WITH_EPT)
21092158
${CMAKE_SOURCE_DIR}/src/core/providers/ept)
21102159
endif()
21112160

2161+
if (WITH_COPC)
2162+
target_include_directories(qgis_core PUBLIC
2163+
${CMAKE_SOURCE_DIR}/src/core/providers/copc)
2164+
endif()
2165+
21122166
GENERATE_EXPORT_HEADER(
21132167
qgis_core
21142168
BASE_NAME CORE
@@ -2229,6 +2283,15 @@ if (WITH_EPT)
22292283
endif()
22302284
endif()
22312285

2286+
if (WITH_COPC)
2287+
target_link_libraries(qgis_core
2288+
${ZSTD_LIBRARY}
2289+
)
2290+
if (LazPerf_FOUND)
2291+
target_link_libraries(qgis_core ${LazPerf_LIBRARY})
2292+
endif()
2293+
endif()
2294+
22322295
if (WITH_PDAL)
22332296
target_link_libraries(qgis_core
22342297
${PDAL_LIBRARIES}
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/***************************************************************************
2+
qgscopcpointcloudindex.cpp
3+
--------------------
4+
begin : March 2022
5+
copyright : (C) 2022 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgscopcpointcloudindex.h"
19+
#include <QFile>
20+
#include <QFileInfo>
21+
#include <QDir>
22+
#include <QJsonArray>
23+
#include <QJsonDocument>
24+
#include <QJsonObject>
25+
#include <QTime>
26+
#include <QtDebug>
27+
#include <QQueue>
28+
29+
#include "qgseptdecoder.h"
30+
#include "qgscoordinatereferencesystem.h"
31+
#include "qgspointcloudrequest.h"
32+
#include "qgspointcloudattribute.h"
33+
#include "qgslogger.h"
34+
#include "qgsfeedback.h"
35+
#include "qgsmessagelog.h"
36+
#include "qgspointcloudexpression.h"
37+
38+
#include "lazperf/lazperf.hpp"
39+
#include "lazperf/readers.hpp"
40+
41+
///@cond PRIVATE
42+
43+
#define PROVIDER_KEY QStringLiteral( "copc" )
44+
#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud provider" )
45+
46+
QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = default;
47+
48+
QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = default;
49+
50+
void QgsCopcPointCloudIndex::load( const QString &fileName )
51+
{
52+
QFile f( fileName );
53+
if ( !f.open( QIODevice::ReadOnly ) )
54+
{
55+
QgsMessageLog::logMessage( tr( "Unable to open %1 for reading" ).arg( fileName ) );
56+
mIsValid = false;
57+
return;
58+
}
59+
60+
mFileName = fileName;
61+
62+
bool success = loadSchema( fileName );
63+
64+
if ( success )
65+
{
66+
success = loadHierarchy( fileName );
67+
}
68+
69+
mIsValid = success;
70+
}
71+
72+
bool QgsCopcPointCloudIndex::loadSchema( const QString &filename )
73+
{
74+
std::ifstream file( filename.toStdString() );
75+
lazperf::reader::generic_file f( file );
76+
77+
mDataType = QStringLiteral( "copc" );
78+
79+
mPointCount = f.header().point_count;
80+
81+
mScale = QgsVector3D( f.header().scale.x, f.header().scale.y, f.header().scale.z );
82+
mOffset = QgsVector3D( f.header().offset.x, f.header().offset.y, f.header().offset.z );
83+
84+
// The COPC format only uses PDRF 6, 7 or 8. So there should be a OGC Coordinate System WKT record
85+
mWkt = QString();
86+
std::vector<char> wktRecordData = f.vlrData( "LASF_Projection", 2112 );
87+
if ( !wktRecordData.empty() )
88+
{
89+
lazperf::wkt_vlr wktVlr;
90+
wktVlr.fill( wktRecordData.data(), wktRecordData.size() );
91+
mWkt = QString::fromStdString( wktVlr.wkt );
92+
}
93+
94+
mExtent.set( f.header().minx, f.header().miny, f.header().maxx, f.header().maxy );
95+
96+
mZMin = f.header().minz;
97+
mZMax = f.header().maxz;
98+
99+
100+
// Attributes for COPC format
101+
// COPC supports only PDRF 6, 7 and 8
102+
103+
// TODO: How to handle bitfields in LAZ
104+
105+
QgsPointCloudAttributeCollection attributes;
106+
attributes.push_back( QgsPointCloudAttribute( "X", ( QgsPointCloudAttribute::DataType ) 9 ) );
107+
attributes.push_back( QgsPointCloudAttribute( "Y", ( QgsPointCloudAttribute::DataType ) 9 ) );
108+
attributes.push_back( QgsPointCloudAttribute( "Z", ( QgsPointCloudAttribute::DataType ) 9 ) );
109+
attributes.push_back( QgsPointCloudAttribute( "Classification", ( QgsPointCloudAttribute::DataType ) 0 ) );
110+
attributes.push_back( QgsPointCloudAttribute( "Intensity", ( QgsPointCloudAttribute::DataType ) 3 ) );
111+
attributes.push_back( QgsPointCloudAttribute( "ReturnNumber", ( QgsPointCloudAttribute::DataType ) 0 ) );
112+
attributes.push_back( QgsPointCloudAttribute( "NumberOfReturns", ( QgsPointCloudAttribute::DataType ) 0 ) );
113+
attributes.push_back( QgsPointCloudAttribute( "ScanDirectionFlag", ( QgsPointCloudAttribute::DataType ) 0 ) );
114+
attributes.push_back( QgsPointCloudAttribute( "EdgeOfFlightLine", ( QgsPointCloudAttribute::DataType ) 0 ) );
115+
attributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", ( QgsPointCloudAttribute::DataType ) 8 ) );
116+
attributes.push_back( QgsPointCloudAttribute( "UserData", ( QgsPointCloudAttribute::DataType ) 0 ) );
117+
attributes.push_back( QgsPointCloudAttribute( "PointSourceId", ( QgsPointCloudAttribute::DataType ) 3 ) );
118+
attributes.push_back( QgsPointCloudAttribute( "GpsTime", ( QgsPointCloudAttribute::DataType ) 9 ) );
119+
120+
switch ( f.header().point_format_id )
121+
{
122+
case 6:
123+
break;
124+
case 7:
125+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
126+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
127+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
128+
break;
129+
case 8:
130+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
131+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
132+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
133+
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "NIR" ), QgsPointCloudAttribute::UShort ) );
134+
break;
135+
default:
136+
return false;
137+
}
138+
139+
// TODO: add extrabyte attributes
140+
141+
setAttributes( attributes );
142+
143+
std::vector<char> copcInfoVlrData = f.vlrData( "copc", 1 );
144+
145+
lazperf::copc_info_vlr copcInfoVlr;
146+
copcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
147+
148+
const double xmin = copcInfoVlr.center_x - copcInfoVlr.halfsize;
149+
const double ymin = copcInfoVlr.center_y - copcInfoVlr.halfsize;
150+
const double zmin = copcInfoVlr.center_z - copcInfoVlr.halfsize;
151+
const double xmax = copcInfoVlr.center_x + copcInfoVlr.halfsize;
152+
const double ymax = copcInfoVlr.center_y + copcInfoVlr.halfsize;
153+
const double zmax = copcInfoVlr.center_z + copcInfoVlr.halfsize;
154+
155+
mRootBounds = QgsPointCloudDataBounds(
156+
( xmin - mOffset.x() ) / mScale.x(),
157+
( ymin - mOffset.y() ) / mScale.y(),
158+
( zmin - mOffset.z() ) / mScale.z(),
159+
( xmax - mOffset.x() ) / mScale.x(),
160+
( ymax - mOffset.y() ) / mScale.y(),
161+
( zmax - mOffset.z() ) / mScale.z()
162+
);
163+
164+
double calculatedSpan = nodeMapExtent( root() ).width() / copcInfoVlr.spacing;
165+
mSpan = calculatedSpan;
166+
167+
#ifdef QGIS_DEBUG
168+
double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
169+
QgsDebugMsgLevel( QStringLiteral( "lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 ); // all dims should be the same
170+
QgsDebugMsgLevel( QStringLiteral( "res at lvl0 %1" ).arg( dx / mSpan ), 2 );
171+
QgsDebugMsgLevel( QStringLiteral( "res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
172+
QgsDebugMsgLevel( QStringLiteral( "res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
173+
#endif
174+
175+
return true;
176+
}
177+
178+
QgsPointCloudBlock *QgsCopcPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
179+
{
180+
mHierarchyMutex.lock();
181+
const bool found = mHierarchy.contains( n );
182+
int pointCount = mHierarchy[n];
183+
uint64_t blockOffset = mHierarchyNodeOffset[n];
184+
int32_t blockSize = mHierarchyNodeByteSize[n];
185+
mHierarchyMutex.unlock();
186+
if ( !found )
187+
return nullptr;
188+
189+
// we need to create a copy of the expression to pass to the decoder
190+
// as the same QgsPointCloudExpression object mighgt be concurrently
191+
// used on another thread, for example in a 3d view
192+
QgsPointCloudExpression filterExpression = mFilterExpression;
193+
QgsPointCloudAttributeCollection requestAttributes = request.attributes();
194+
requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
195+
196+
return QgsEptDecoder::decompressCopc( mFileName, blockOffset, blockSize, pointCount, attributes(), requestAttributes, scale(), offset(), filterExpression );
197+
}
198+
199+
QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
200+
{
201+
Q_UNUSED( n );
202+
Q_UNUSED( request );
203+
Q_ASSERT( false );
204+
return nullptr; // unsupported
205+
}
206+
207+
QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
208+
{
209+
return QgsCoordinateReferenceSystem::fromWkt( mWkt );
210+
}
211+
212+
qint64 QgsCopcPointCloudIndex::pointCount() const
213+
{
214+
return mPointCount;
215+
}
216+
217+
QVariant QgsCopcPointCloudIndex::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
218+
{
219+
if ( !mMetadataStats.contains( attribute ) )
220+
return QVariant();
221+
222+
const AttributeStatistics &stats = mMetadataStats[ attribute ];
223+
switch ( statistic )
224+
{
225+
case QgsStatisticalSummary::Count:
226+
return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
227+
228+
case QgsStatisticalSummary::Mean:
229+
return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
230+
231+
case QgsStatisticalSummary::StDev:
232+
return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
233+
234+
case QgsStatisticalSummary::Min:
235+
return stats.minimum;
236+
237+
case QgsStatisticalSummary::Max:
238+
return stats.maximum;
239+
240+
case QgsStatisticalSummary::Range:
241+
return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
242+
243+
case QgsStatisticalSummary::CountMissing:
244+
case QgsStatisticalSummary::Sum:
245+
case QgsStatisticalSummary::Median:
246+
case QgsStatisticalSummary::StDevSample:
247+
case QgsStatisticalSummary::Minority:
248+
case QgsStatisticalSummary::Majority:
249+
case QgsStatisticalSummary::Variety:
250+
case QgsStatisticalSummary::FirstQuartile:
251+
case QgsStatisticalSummary::ThirdQuartile:
252+
case QgsStatisticalSummary::InterQuartileRange:
253+
case QgsStatisticalSummary::First:
254+
case QgsStatisticalSummary::Last:
255+
case QgsStatisticalSummary::All:
256+
return QVariant();
257+
}
258+
return QVariant();
259+
}
260+
261+
QVariantList QgsCopcPointCloudIndex::metadataClasses( const QString &attribute ) const
262+
{
263+
QVariantList classes;
264+
const QMap< int, int > values = mAttributeClasses.value( attribute );
265+
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
266+
{
267+
classes << it.key();
268+
}
269+
return classes;
270+
}
271+
272+
QVariant QgsCopcPointCloudIndex::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
273+
{
274+
if ( statistic != QgsStatisticalSummary::Count )
275+
return QVariant();
276+
277+
const QMap< int, int > values = mAttributeClasses.value( attribute );
278+
if ( !values.contains( value.toInt() ) )
279+
return QVariant();
280+
return values.value( value.toInt() );
281+
}
282+
283+
bool QgsCopcPointCloudIndex::loadHierarchy( const QString &filename )
284+
{
285+
std::ifstream file( filename.toStdString() );
286+
lazperf::reader::generic_file f( file );
287+
288+
std::vector<char> copcInfoVlrData = f.vlrData( "copc", 1 );
289+
290+
lazperf::copc_info_vlr copcInfoVlr;
291+
copcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
292+
293+
QQueue<std::pair<uint64_t, uint64_t>> queue;
294+
queue.push_back( std::make_pair( copcInfoVlr.root_hier_offset, copcInfoVlr.root_hier_size ) );
295+
296+
struct CopcVoxelKey
297+
{
298+
int32_t level;
299+
int32_t x;
300+
int32_t y;
301+
int32_t z;
302+
};
303+
304+
struct CopcEntry
305+
{
306+
CopcVoxelKey key;
307+
uint64_t offset;
308+
int32_t byteSize;
309+
int32_t pointCount;
310+
};
311+
312+
while ( !queue.isEmpty() )
313+
{
314+
auto [offset, size] = queue.dequeue();
315+
316+
file.seekg( offset );
317+
std::unique_ptr<char> data( new char[ offset ] );
318+
file.read( data.get(), size );
319+
320+
for ( uint64_t i = 0; i < size; i += sizeof( CopcEntry ) )
321+
{
322+
CopcEntry *entry = reinterpret_cast<CopcEntry *>( data.get() + i );
323+
if ( entry->pointCount < 0 )
324+
{
325+
queue.enqueue( std::make_pair( entry->offset, entry->byteSize ) );
326+
}
327+
else if ( entry->pointCount > 0 )
328+
{
329+
const IndexedPointCloudNode nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
330+
mHierarchyMutex.lock();
331+
mHierarchy[nodeId] = entry->pointCount;
332+
mHierarchyNodeOffset[nodeId] = entry->offset;
333+
mHierarchyNodeByteSize[nodeId] = entry->byteSize;
334+
mHierarchyMutex.unlock();
335+
}
336+
}
337+
}
338+
return true;
339+
}
340+
341+
bool QgsCopcPointCloudIndex::isValid() const
342+
{
343+
return mIsValid;
344+
}
345+
346+
///@endcond
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/***************************************************************************
2+
qgscopcpointcloudindex.h
3+
--------------------
4+
begin : March 2022
5+
copyright : (C) 2022 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSCOPCPOINTCLOUDINDEX_H
19+
#define QGSCOPCPOINTCLOUDINDEX_H
20+
21+
#include <QObject>
22+
#include <QString>
23+
#include <QHash>
24+
#include <QStringList>
25+
#include <QVector>
26+
#include <QList>
27+
#include <QFile>
28+
29+
#include "qgspointcloudindex.h"
30+
#include "qgspointcloudattribute.h"
31+
#include "qgsstatisticalsummary.h"
32+
#include "qgis_sip.h"
33+
34+
///@cond PRIVATE
35+
#define SIP_NO_FILE
36+
37+
class QgsCoordinateReferenceSystem;
38+
39+
class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex
40+
{
41+
Q_OBJECT
42+
public:
43+
44+
explicit QgsCopcPointCloudIndex();
45+
~QgsCopcPointCloudIndex();
46+
47+
void load( const QString &fileName ) override;
48+
49+
QgsPointCloudBlock *nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override;
50+
QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override;
51+
52+
QgsCoordinateReferenceSystem crs() const override;
53+
qint64 pointCount() const override;
54+
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const override;
55+
QVariantList metadataClasses( const QString &attribute ) const override;
56+
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const override;
57+
QVariantMap originalMetadata() const override { return mOriginalMetadata; }
58+
59+
bool isValid() const override;
60+
QgsPointCloudIndex::AccessType accessType() const override { return QgsPointCloudIndex::Local; };
61+
62+
protected:
63+
bool loadSchema( const QString &filename );
64+
bool loadHierarchy( const QString &filename );
65+
66+
bool mIsValid = false;
67+
QString mDataType;
68+
QString mFileName;
69+
QString mWkt;
70+
71+
qint64 mPointCount = 0;
72+
73+
mutable QHash<IndexedPointCloudNode, uint64_t> mHierarchyNodeOffset; //!< Additional data hierarchy for COPC
74+
mutable QHash<IndexedPointCloudNode, int32_t> mHierarchyNodeByteSize; //!< Additional data hierarchy for COPC
75+
76+
struct AttributeStatistics
77+
{
78+
int count = -1;
79+
QVariant minimum;
80+
QVariant maximum;
81+
double mean = std::numeric_limits< double >::quiet_NaN();
82+
double stDev = std::numeric_limits< double >::quiet_NaN();
83+
double variance = std::numeric_limits< double >::quiet_NaN();
84+
};
85+
86+
QMap< QString, AttributeStatistics > mMetadataStats;
87+
88+
QMap< QString, QMap< int, int > > mAttributeClasses;
89+
QVariantMap mOriginalMetadata;
90+
};
91+
92+
///@endcond
93+
#endif // QGSCOPCPOINTCLOUDINDEX_H

‎src/core/pointcloud/qgseptdecoder.cpp

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,4 +720,303 @@ QgsPointCloudBlock *QgsEptDecoder::decompressLaz( const QByteArray &byteArrayDat
720720
return __decompressLaz<std::istringstream>( file, attributes, requestedAttributes, scale, offset, filterExpression );
721721
}
722722

723+
724+
QgsPointCloudBlock *QgsEptDecoder::decompressCopc( const QString &filename, uint64_t blockOffset, uint64_t blockSize, int32_t pointCount, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &_scale, const QgsVector3D &_offset, QgsPointCloudExpression &filterExpression )
725+
{
726+
Q_UNUSED( attributes );
727+
Q_UNUSED( _scale );
728+
Q_UNUSED( _offset );
729+
std::ifstream file( filename.toStdString(), std::ios::binary );
730+
lazperf::reader::generic_file f( file );
731+
std::unique_ptr<char> data( new char[ blockSize ] );
732+
file.seekg( blockOffset );
733+
file.read( data.get(), blockSize );
734+
std::unique_ptr<char> decodedData( new char[ f.header().point_record_length ] );
735+
736+
lazperf::reader::chunk_decompressor decompressor( f.header().pointFormat(), f.header().ebCount(), data.get() );
737+
738+
const QgsVector3D hScale( f.header().scale.x, f.header().scale.y, f.header().scale.z );
739+
const QgsVector3D hOffset( f.header().offset.x, f.header().offset.y, f.header().offset.z );
740+
741+
const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
742+
QByteArray blockData;
743+
blockData.resize( requestedPointRecordSize * pointCount );
744+
char *dataBuffer = blockData.data();
745+
746+
const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
747+
748+
std::size_t outputOffset = 0;
749+
750+
enum class LazAttribute
751+
{
752+
X,
753+
Y,
754+
Z,
755+
Classification,
756+
Intensity,
757+
ReturnNumber,
758+
NumberOfReturns,
759+
ScanDirectionFlag,
760+
EdgeOfFlightLine,
761+
ScanAngleRank,
762+
UserData,
763+
PointSourceId,
764+
GpsTime,
765+
Red,
766+
Green,
767+
Blue,
768+
ExtraBytes,
769+
MissingOrUnknown
770+
};
771+
772+
struct RequestedAttributeDetails
773+
{
774+
RequestedAttributeDetails( LazAttribute attribute, QgsPointCloudAttribute::DataType type, int size, int offset = -1 )
775+
: attribute( attribute )
776+
, type( type )
777+
, size( size )
778+
, offset( offset )
779+
{}
780+
781+
LazAttribute attribute;
782+
QgsPointCloudAttribute::DataType type;
783+
int size;
784+
int offset; // Used in case the attribute is an extra byte attribute
785+
};
786+
787+
QVector<QgsEptDecoder::ExtraBytesAttributeDetails> extrabytesAttr = QgsEptDecoder::readExtraByteAttributes<std::ifstream>( file );
788+
789+
std::vector< RequestedAttributeDetails > requestedAttributeDetails;
790+
requestedAttributeDetails.reserve( requestedAttributesVector.size() );
791+
for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
792+
{
793+
if ( requestedAttribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 )
794+
{
795+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::X, requestedAttribute.type(), requestedAttribute.size() ) );
796+
}
797+
else if ( requestedAttribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 )
798+
{
799+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Y, requestedAttribute.type(), requestedAttribute.size() ) );
800+
}
801+
else if ( requestedAttribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
802+
{
803+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Z, requestedAttribute.type(), requestedAttribute.size() ) );
804+
}
805+
else if ( requestedAttribute.name().compare( QLatin1String( "Classification" ), Qt::CaseInsensitive ) == 0 )
806+
{
807+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Classification, requestedAttribute.type(), requestedAttribute.size() ) );
808+
}
809+
else if ( requestedAttribute.name().compare( QLatin1String( "Intensity" ), Qt::CaseInsensitive ) == 0 )
810+
{
811+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Intensity, requestedAttribute.type(), requestedAttribute.size() ) );
812+
}
813+
else if ( requestedAttribute.name().compare( QLatin1String( "ReturnNumber" ), Qt::CaseInsensitive ) == 0 )
814+
{
815+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ReturnNumber, requestedAttribute.type(), requestedAttribute.size() ) );
816+
}
817+
else if ( requestedAttribute.name().compare( QLatin1String( "NumberOfReturns" ), Qt::CaseInsensitive ) == 0 )
818+
{
819+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::NumberOfReturns, requestedAttribute.type(), requestedAttribute.size() ) );
820+
}
821+
else if ( requestedAttribute.name().compare( QLatin1String( "ScanDirectionFlag" ), Qt::CaseInsensitive ) == 0 )
822+
{
823+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanDirectionFlag, requestedAttribute.type(), requestedAttribute.size() ) );
824+
}
825+
else if ( requestedAttribute.name().compare( QLatin1String( "EdgeOfFlightLine" ), Qt::CaseInsensitive ) == 0 )
826+
{
827+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::EdgeOfFlightLine, requestedAttribute.type(), requestedAttribute.size() ) );
828+
}
829+
else if ( requestedAttribute.name().compare( QLatin1String( "ScanAngleRank" ), Qt::CaseInsensitive ) == 0 )
830+
{
831+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanAngleRank, requestedAttribute.type(), requestedAttribute.size() ) );
832+
}
833+
else if ( requestedAttribute.name().compare( QLatin1String( "UserData" ), Qt::CaseInsensitive ) == 0 )
834+
{
835+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::UserData, requestedAttribute.type(), requestedAttribute.size() ) );
836+
}
837+
else if ( requestedAttribute.name().compare( QLatin1String( "PointSourceId" ), Qt::CaseInsensitive ) == 0 )
838+
{
839+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::PointSourceId, requestedAttribute.type(), requestedAttribute.size() ) );
840+
}
841+
else if ( requestedAttribute.name().compare( QLatin1String( "GpsTime" ), Qt::CaseInsensitive ) == 0 )
842+
{
843+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::GpsTime, requestedAttribute.type(), requestedAttribute.size() ) );
844+
}
845+
else if ( requestedAttribute.name().compare( QLatin1String( "Red" ), Qt::CaseInsensitive ) == 0 )
846+
{
847+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Red, requestedAttribute.type(), requestedAttribute.size() ) );
848+
}
849+
else if ( requestedAttribute.name().compare( QLatin1String( "Green" ), Qt::CaseInsensitive ) == 0 )
850+
{
851+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Green, requestedAttribute.type(), requestedAttribute.size() ) );
852+
}
853+
else if ( requestedAttribute.name().compare( QLatin1String( "Blue" ), Qt::CaseInsensitive ) == 0 )
854+
{
855+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Blue, requestedAttribute.type(), requestedAttribute.size() ) );
856+
}
857+
else
858+
{
859+
bool foundAttr = false;
860+
for ( QgsEptDecoder::ExtraBytesAttributeDetails &eba : extrabytesAttr )
861+
{
862+
if ( requestedAttribute.name().compare( eba.attribute.trimmed() ) == 0 )
863+
{
864+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ExtraBytes, eba.type, eba.size, eba.offset ) );
865+
foundAttr = true;
866+
break;
867+
}
868+
}
869+
if ( !foundAttr )
870+
{
871+
// this can possibly happen -- e.g. if a style built using a different point cloud format references an attribute which isn't available from the laz file
872+
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::MissingOrUnknown, requestedAttribute.type(), requestedAttribute.size() ) );
873+
}
874+
}
875+
}
876+
877+
std::unique_ptr< QgsPointCloudBlock > block = std::make_unique< QgsPointCloudBlock >(
878+
pointCount,
879+
requestedAttributes,
880+
blockData, hScale, hOffset
881+
);
882+
883+
int skippedPoints = 0;
884+
const bool filterIsValid = filterExpression.isValid();
885+
if ( !filterExpression.prepare( block.get() ) && filterIsValid )
886+
{
887+
// skip processing if the expression cannot be prepared
888+
block->setPointCount( 0 );
889+
return block.release();
890+
}
891+
892+
lazperf::las::point14 p;
893+
lazperf::las::gpstime gps;
894+
lazperf::las::rgb rgb;
895+
896+
for ( int i = 0 ; i < pointCount; ++i )
897+
{
898+
decompressor.decompress( decodedData.get() );
899+
char *buf = decodedData.get();
900+
p.unpack( buf );
901+
gps.unpack( buf + sizeof( lazperf::las::point14 ) );
902+
rgb.unpack( buf + sizeof( lazperf::las::point14 ) + sizeof( lazperf::las::gpstime ) );
903+
904+
for ( const RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails )
905+
{
906+
switch ( requestedAttribute.attribute )
907+
{
908+
case LazAttribute::X:
909+
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.x() );
910+
break;
911+
case LazAttribute::Y:
912+
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.y() );
913+
break;
914+
case LazAttribute::Z:
915+
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.z() );
916+
break;
917+
case LazAttribute::Classification:
918+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.classification() );
919+
break;
920+
case LazAttribute::Intensity:
921+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.intensity() );
922+
break;
923+
case LazAttribute::ReturnNumber:
924+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.returnNum() );
925+
break;
926+
case LazAttribute::NumberOfReturns:
927+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.numReturns() );
928+
break;
929+
case LazAttribute::ScanDirectionFlag:
930+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.scanDirFlag() );
931+
break;
932+
case LazAttribute::EdgeOfFlightLine:
933+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.eofFlag() );
934+
break;
935+
case LazAttribute::ScanAngleRank:
936+
_storeToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, p.scanAngle() );
937+
break;
938+
case LazAttribute::UserData:
939+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.userData() );
940+
break;
941+
case LazAttribute::PointSourceId:
942+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.pointSourceID() );
943+
break;
944+
case LazAttribute::GpsTime:
945+
// lazperf internally stores gps value as int64 field, but in fact it is a double value
946+
_storeToStream<double>( dataBuffer, outputOffset, requestedAttribute.type,
947+
*reinterpret_cast<const double *>( reinterpret_cast<const void *>( &gps.value ) ) );
948+
break;
949+
case LazAttribute::Red:
950+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.r );
951+
break;
952+
case LazAttribute::Green:
953+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.g );
954+
break;
955+
case LazAttribute::Blue:
956+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.b );
957+
break;
958+
case LazAttribute::ExtraBytes:
959+
{
960+
switch ( requestedAttribute.type )
961+
{
962+
case QgsPointCloudAttribute::Char:
963+
_storeToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<char * >( &buf[requestedAttribute.offset] ) );
964+
break;
965+
case QgsPointCloudAttribute::UChar:
966+
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<unsigned char * >( &buf[requestedAttribute.offset] ) );
967+
break;
968+
case QgsPointCloudAttribute::Short:
969+
_storeToStream<qint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint16 * >( &buf[requestedAttribute.offset] ) );
970+
break;
971+
case QgsPointCloudAttribute::UShort:
972+
_storeToStream<quint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint16 * >( &buf[requestedAttribute.offset] ) );
973+
break;
974+
case QgsPointCloudAttribute::Int32:
975+
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint32 * >( &buf[requestedAttribute.offset] ) );
976+
break;
977+
case QgsPointCloudAttribute::UInt32:
978+
_storeToStream<quint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint32 * >( &buf[requestedAttribute.offset] ) );
979+
break;
980+
case QgsPointCloudAttribute::Int64:
981+
_storeToStream<qint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint64 * >( &buf[requestedAttribute.offset] ) );
982+
break;
983+
case QgsPointCloudAttribute::UInt64:
984+
_storeToStream<quint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint64 * >( &buf[requestedAttribute.offset] ) );
985+
break;
986+
case QgsPointCloudAttribute::Float:
987+
_storeToStream<float>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<float * >( &buf[requestedAttribute.offset] ) );
988+
break;
989+
case QgsPointCloudAttribute::Double:
990+
_storeToStream<double>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<double * >( &buf[requestedAttribute.offset] ) );
991+
break;
992+
}
993+
}
994+
break;
995+
case LazAttribute::MissingOrUnknown:
996+
// just store 0 for unknown/missing attributes
997+
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
998+
break;
999+
}
1000+
1001+
outputOffset += requestedAttribute.size;
1002+
}
1003+
1004+
// check if point needs to be filtered out
1005+
if ( filterIsValid )
1006+
{
1007+
// we're always evaluating the last written point in the buffer
1008+
double eval = filterExpression.evaluate( i - skippedPoints );
1009+
if ( !eval || std::isnan( eval ) )
1010+
{
1011+
// if the point is filtered out, rewind the offset so the next point is written over it
1012+
outputOffset -= requestedPointRecordSize;
1013+
++skippedPoints;
1014+
}
1015+
}
1016+
}
1017+
1018+
block->setPointCount( pointCount - skippedPoints );
1019+
return block.release();
1020+
}
1021+
7231022
///@endcond

‎src/core/pointcloud/qgseptdecoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ namespace QgsEptDecoder
5050
QgsPointCloudBlock *decompressZStandard( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
5151
QgsPointCloudBlock *decompressLaz( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
5252
QgsPointCloudBlock *decompressLaz( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
53+
QgsPointCloudBlock *decompressCopc( const QString &filename, uint64_t blockOffset, uint64_t blockSize, int32_t pointCount, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &_scale, const QgsVector3D &_offset, QgsPointCloudExpression &filterExpression );
5354

5455
//! Returns the list of extrabytes attributes with their type, size and offsets represented in the LAS file
5556
template<typename FileType>

‎src/core/pointcloud/qgspointcloudindex.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
262262
QgsDoubleRange nodeZRange( const IndexedPointCloudNode &node ) const;
263263

264264
//! Returns node's error in map units (used to determine in whether the node has enough detail for the current view)
265-
float nodeError( const IndexedPointCloudNode &n ) const;
265+
virtual float nodeError( const IndexedPointCloudNode &n ) const;
266266

267267
//! Returns scale
268268
QgsVector3D scale() const;
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/***************************************************************************
2+
qgscopcdataprovider.cpp
3+
-----------------------
4+
begin : March 2022
5+
copyright : (C) 2022 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgis.h"
19+
#include "qgslogger.h"
20+
#include "qgsproviderregistry.h"
21+
#include "qgscopcprovider.h"
22+
#include "qgscopcpointcloudindex.h"
23+
#include "qgsruntimeprofiler.h"
24+
#include "qgsapplication.h"
25+
#include "qgsprovidersublayerdetails.h"
26+
#include "qgsproviderutils.h"
27+
28+
#include <QFileInfo>
29+
30+
///@cond PRIVATE
31+
32+
#define PROVIDER_KEY QStringLiteral( "copc" )
33+
#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud data provider" )
34+
35+
QgsCopcProvider::QgsCopcProvider(
36+
const QString &uri,
37+
const QgsDataProvider::ProviderOptions &options,
38+
QgsDataProvider::ReadFlags flags )
39+
: QgsPointCloudDataProvider( uri, options, flags )
40+
{
41+
if ( uri.startsWith( QStringLiteral( "http" ), Qt::CaseSensitivity::CaseInsensitive ) )
42+
mIndex.reset( nullptr );
43+
else
44+
mIndex.reset( new QgsCopcPointCloudIndex );
45+
46+
std::unique_ptr< QgsScopedRuntimeProfile > profile;
47+
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
48+
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Open data source" ), QStringLiteral( "projectload" ) );
49+
50+
loadIndex( );
51+
}
52+
53+
QgsCopcProvider::~QgsCopcProvider() = default;
54+
55+
QgsCoordinateReferenceSystem QgsCopcProvider::crs() const
56+
{
57+
return mIndex->crs();
58+
}
59+
60+
QgsRectangle QgsCopcProvider::extent() const
61+
{
62+
return mIndex->extent();
63+
}
64+
65+
QgsPointCloudAttributeCollection QgsCopcProvider::attributes() const
66+
{
67+
return mIndex->attributes();
68+
}
69+
70+
bool QgsCopcProvider::isValid() const
71+
{
72+
if ( !mIndex.get() )
73+
{
74+
return false;
75+
}
76+
return mIndex->isValid();
77+
}
78+
79+
QString QgsCopcProvider::name() const
80+
{
81+
return QStringLiteral( "copc" );
82+
}
83+
84+
QString QgsCopcProvider::description() const
85+
{
86+
return QStringLiteral( "Point Clouds COPC" );
87+
}
88+
89+
QgsPointCloudIndex *QgsCopcProvider::index() const
90+
{
91+
return mIndex.get();
92+
}
93+
94+
qint64 QgsCopcProvider::pointCount() const
95+
{
96+
return mIndex->pointCount();
97+
}
98+
99+
QVariantList QgsCopcProvider::metadataClasses( const QString &attribute ) const
100+
{
101+
return mIndex->metadataClasses( attribute );
102+
}
103+
104+
QVariant QgsCopcProvider::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
105+
{
106+
return mIndex->metadataClassStatistic( attribute, value, statistic );
107+
}
108+
109+
void QgsCopcProvider::loadIndex( )
110+
{
111+
if ( mIndex->isValid() )
112+
return;
113+
114+
mIndex->load( dataSourceUri() );
115+
116+
// TODO: Read metadata from LAZ into mOriginalMetadata
117+
// NOTE: This is not done in EPT provider either
118+
}
119+
120+
QVariantMap QgsCopcProvider::originalMetadata() const
121+
{
122+
return mOriginalMetadata;
123+
}
124+
125+
void QgsCopcProvider::generateIndex()
126+
{
127+
//no-op, index is always generated
128+
}
129+
130+
QVariant QgsCopcProvider::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
131+
{
132+
return mIndex->metadataStatistic( attribute, statistic );
133+
}
134+
135+
QgsCopcProviderMetadata::QgsCopcProviderMetadata():
136+
QgsProviderMetadata( PROVIDER_KEY, PROVIDER_DESCRIPTION )
137+
{
138+
}
139+
140+
QgsCopcProvider *QgsCopcProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
141+
{
142+
return new QgsCopcProvider( uri, options, flags );
143+
}
144+
145+
QList<QgsProviderSublayerDetails> QgsCopcProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
146+
{
147+
const QVariantMap parts = decodeUri( uri );
148+
if ( parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
149+
{
150+
QgsProviderSublayerDetails details;
151+
details.setUri( uri );
152+
details.setProviderKey( QStringLiteral( "copc" ) );
153+
details.setType( QgsMapLayerType::PointCloudLayer );
154+
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( uri ) );
155+
return {details};
156+
}
157+
else
158+
{
159+
return {};
160+
}
161+
}
162+
163+
int QgsCopcProviderMetadata::priorityForUri( const QString &uri ) const
164+
{
165+
const QVariantMap parts = decodeUri( uri );
166+
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
167+
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
168+
return 100;
169+
170+
return 0;
171+
}
172+
173+
QList<QgsMapLayerType> QgsCopcProviderMetadata::validLayerTypesForUri( const QString &uri ) const
174+
{
175+
const QVariantMap parts = decodeUri( uri );
176+
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
177+
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
178+
return QList< QgsMapLayerType>() << QgsMapLayerType::PointCloudLayer;
179+
180+
return QList< QgsMapLayerType>();
181+
}
182+
183+
bool QgsCopcProviderMetadata::uriIsBlocklisted( const QString &uri ) const
184+
{
185+
const QVariantMap parts = decodeUri( uri );
186+
if ( !parts.contains( QStringLiteral( "path" ) ) )
187+
return false;
188+
189+
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
190+
191+
// internal details only
192+
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
193+
return true;
194+
195+
return false;
196+
}
197+
198+
QVariantMap QgsCopcProviderMetadata::decodeUri( const QString &uri ) const
199+
{
200+
const QString path = uri;
201+
QVariantMap uriComponents;
202+
uriComponents.insert( QStringLiteral( "path" ), path );
203+
uriComponents.insert( QStringLiteral( "isCopc" ), uri.endsWith( ".copc.laz" ) );
204+
return uriComponents;
205+
}
206+
207+
QString QgsCopcProviderMetadata::filters( QgsProviderMetadata::FilterType type )
208+
{
209+
switch ( type )
210+
{
211+
case QgsProviderMetadata::FilterType::FilterVector:
212+
case QgsProviderMetadata::FilterType::FilterRaster:
213+
case QgsProviderMetadata::FilterType::FilterMesh:
214+
case QgsProviderMetadata::FilterType::FilterMeshDataset:
215+
return QString();
216+
217+
case QgsProviderMetadata::FilterType::FilterPointCloud:
218+
return QObject::tr( "COPC Point Clouds" ) + QStringLiteral( "COPC LAZ files (*.copc.laz *.COPC.LAZ)" );
219+
}
220+
return QString();
221+
}
222+
223+
QgsProviderMetadata::ProviderCapabilities QgsCopcProviderMetadata::providerCapabilities() const
224+
{
225+
return FileBasedUris;
226+
}
227+
228+
QString QgsCopcProviderMetadata::encodeUri( const QVariantMap &parts ) const
229+
{
230+
const QString path = parts.value( QStringLiteral( "path" ) ).toString();
231+
return path;
232+
}
233+
234+
QgsProviderMetadata::ProviderMetadataCapabilities QgsCopcProviderMetadata::capabilities() const
235+
{
236+
return ProviderMetadataCapability::LayerTypesForUri
237+
| ProviderMetadataCapability::PriorityForUri
238+
| ProviderMetadataCapability::QuerySublayers;
239+
}
240+
///@endcond
241+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/***************************************************************************
2+
qgscopcdataprovider.h
3+
---------------------
4+
begin : March 2022
5+
copyright : (C) 2022 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSCOPCPROVIDER_H
19+
#define QGSCOPCPROVIDER_H
20+
21+
#include "qgis_core.h"
22+
#include "qgspointclouddataprovider.h"
23+
#include "qgsprovidermetadata.h"
24+
25+
#include <memory>
26+
27+
#include "qgis_sip.h"
28+
29+
///@cond PRIVATE
30+
#define SIP_NO_FILE
31+
32+
class QgsCopcPointCloudIndex;
33+
class QgsRemoteCopcPointCloudIndex;
34+
35+
class QgsCopcProvider: public QgsPointCloudDataProvider
36+
{
37+
Q_OBJECT
38+
public:
39+
QgsCopcProvider( const QString &uri,
40+
const QgsDataProvider::ProviderOptions &providerOptions,
41+
QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() );
42+
43+
~QgsCopcProvider();
44+
45+
QgsCoordinateReferenceSystem crs() const override;
46+
47+
QgsRectangle extent() const override;
48+
QgsPointCloudAttributeCollection attributes() const override;
49+
bool isValid() const override;
50+
QString name() const override;
51+
QString description() const override;
52+
QgsPointCloudIndex *index() const override;
53+
qint64 pointCount() const override;
54+
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const override;
55+
QVariantList metadataClasses( const QString &attribute ) const override;
56+
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const override;
57+
QVariantMap originalMetadata() const override;
58+
void loadIndex( ) override;
59+
void generateIndex( ) override;
60+
PointCloudIndexGenerationState indexingState( ) override { return PointCloudIndexGenerationState::Indexed; }
61+
62+
private:
63+
QVariantMap mOriginalMetadata;
64+
std::unique_ptr<QgsPointCloudIndex> mIndex;
65+
66+
QgsRectangle mExtent;
67+
uint64_t mPointCount;
68+
QgsCoordinateReferenceSystem mCrs;
69+
};
70+
71+
class QgsCopcProviderMetadata : public QgsProviderMetadata
72+
{
73+
public:
74+
QgsCopcProviderMetadata();
75+
QgsProviderMetadata::ProviderMetadataCapabilities capabilities() const override;
76+
QgsCopcProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() ) override;
77+
QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const override;
78+
int priorityForUri( const QString &uri ) const override;
79+
QList< QgsMapLayerType > validLayerTypesForUri( const QString &uri ) const override;
80+
bool uriIsBlocklisted( const QString &uri ) const override;
81+
QString encodeUri( const QVariantMap &parts ) const override;
82+
QVariantMap decodeUri( const QString &uri ) const override;
83+
QString filters( FilterType type ) override;
84+
ProviderCapabilities providerCapabilities() const override;
85+
};
86+
87+
///@endcond
88+
#endif // QGSCOPCPROVIDER_H

‎src/core/providers/qgsproviderregistry.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
#include "providers/ept/qgseptprovider.h"
4040
#endif
4141

42+
#ifdef HAVE_COPC
43+
#include "providers/copc/qgscopcprovider.h"
44+
#endif
45+
4246
#include "qgsruntimeprofiler.h"
4347
#include "qgsfileutils.h"
4448

@@ -197,7 +201,13 @@ void QgsProviderRegistry::init()
197201
mProviders[ pc->key() ] = pc;
198202
}
199203
#endif
200-
204+
#ifdef HAVE_COPC
205+
{
206+
const QgsScopedRuntimeProfile profile( QObject::tr( "Create COPC point cloud provider" ) );
207+
QgsProviderMetadata *pc = new QgsCopcProviderMetadata();
208+
mProviders[ pc->key() ] = pc;
209+
}
210+
#endif
201211
registerUnusableUriHandler( new PdalUnusableUriHandlerInterface() );
202212

203213
#ifdef HAVE_STATIC_PROVIDERS

‎src/gui/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,15 @@ if (WITH_EPT)
14271427
)
14281428
endif()
14291429

1430+
if (WITH_COPC)
1431+
set(QGIS_GUI_SRCS ${QGIS_GUI_SRCS}
1432+
providers/copc/qgscopcproviderguimetadata.cpp
1433+
)
1434+
set(QGIS_GUI_HDRS ${QGIS_GUI_HDRS}
1435+
providers/copc/qgscopcproviderguimetadata.h
1436+
)
1437+
endif()
1438+
14301439

14311440
# disable deprecation warnings for classes re-exporting deprecated methods
14321441
if(MSVC)
@@ -1512,6 +1521,12 @@ if (WITH_EPT)
15121521
)
15131522
endif()
15141523

1524+
if (WITH_COPC)
1525+
target_include_directories(qgis_gui PUBLIC
1526+
${CMAKE_SOURCE_DIR}/src/gui/providers/copc
1527+
)
1528+
endif()
1529+
15151530

15161531
GENERATE_EXPORT_HEADER(
15171532
qgis_gui
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/***************************************************************************
2+
qgscopcproviderguimetadata.cpp
3+
--------------------
4+
begin : March 2022
5+
copyright : (C) 2022 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsapplication.h"
19+
#include "qgscopcproviderguimetadata.h"
20+
21+
///@cond PRIVATE
22+
23+
QgsCopcProviderGuiMetadata::QgsCopcProviderGuiMetadata()
24+
: QgsProviderGuiMetadata( QStringLiteral( "copc" ) )
25+
{
26+
}
27+
28+
///@endcond
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/***************************************************************************
2+
qgscopcproviderguimetadata.h
3+
--------------------
4+
begin : March 2022
5+
copyright : (C) 2020 by Belgacem Nedjima
6+
email : belgacem dot nedjima at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSCOPCPROVIDERGUIMETADATA_H
19+
#define QGSCOPCPROVIDERGUIMETADATA_H
20+
21+
///@cond PRIVATE
22+
#define SIP_NO_FILE
23+
24+
#include <QList>
25+
#include <QMainWindow>
26+
27+
#include "qgsproviderguimetadata.h"
28+
29+
class QgsCopcProviderGuiMetadata: public QgsProviderGuiMetadata
30+
{
31+
public:
32+
QgsCopcProviderGuiMetadata();
33+
};
34+
35+
///@endcond
36+
37+
#endif // QGSCopcPROVIDERGUIMETADATA_H

‎src/gui/providers/qgspointcloudsourceselect.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ void QgsPointCloudSourceSelect::addButtonClicked()
7878
// auto determine preferred provider for each path
7979

8080
const QList< QgsProviderRegistry::ProviderCandidateDetails > preferredProviders = QgsProviderRegistry::instance()->preferredProvidersForUri( mPath );
81+
for ( QgsProviderRegistry::ProviderCandidateDetails p : preferredProviders )
82+
{
83+
qDebug() << p.metadata()->key();
84+
}
8185
// maybe we should raise an assert if preferredProviders size is 0 or >1? Play it safe for now...
8286
if ( preferredProviders.empty() )
8387
continue;

‎src/gui/qgsproviderguiregistry.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
#include "qgseptproviderguimetadata.h"
3434
#endif
3535

36+
#ifdef HAVE_COPC
37+
#include "qgscopcproviderguimetadata.h"
38+
#endif
39+
3640
#ifdef HAVE_STATIC_PROVIDERS
3741
#include "qgswmsprovidergui.h"
3842
#include "qgswcsprovidergui.h"
@@ -92,6 +96,11 @@ void QgsProviderGuiRegistry::loadStaticProviders( )
9296
mProviders[ ept->key() ] = ept;
9397
#endif
9498

99+
#ifdef HAVE_COPC
100+
QgsProviderGuiMetadata *copc = new QgsCopcProviderGuiMetadata();
101+
mProviders[ ept->key() ] = copc;
102+
#endif
103+
95104
// only show point cloud option if we have at least one point cloud provider available!
96105
if ( !QgsProviderRegistry::instance()->filePointCloudFilters().isEmpty() )
97106
{

‎tests/src/providers/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ if (WITH_EPT)
5858
add_qgis_test(testqgseptprovider.cpp MODULE provider LINKEDLIBRARIES qgis_core)
5959
endif()
6060

61+
# TODO: test COPC
62+
#if (WITH_COPC)
63+
# add_qgis_test(testqgscopcprovider.cpp MODULE provider LINKEDLIBRARIES qgis_core)
64+
#endif()
65+
6166
if (WITH_PDAL)
6267
include_directories(
6368
${CMAKE_SOURCE_DIR}/src/providers/pdal

0 commit comments

Comments
 (0)
Please sign in to comment.