mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
port the basic renderer from Martin's prototype
This commit is contained in:
parent
13ecb8c452
commit
8a42c5759f
@ -350,6 +350,9 @@ IF(WITH_CORE)
|
||||
FIND_PACKAGE(ZLIB REQUIRED) # for decompression of vector tiles in MBTiles file
|
||||
MESSAGE(STATUS "Found zlib: ${ZLIB_LIBRARIES}")
|
||||
|
||||
FIND_PACKAGE(ZSTD REQUIRED) # for decompression of point clouds
|
||||
FIND_PACKAGE(LazPerf REQUIRED) # for decompression of point clouds
|
||||
|
||||
# optional
|
||||
IF (WITH_POSTGRESQL)
|
||||
FIND_PACKAGE(Postgres) # PostgreSQL provider
|
||||
|
||||
30
cmake/FindLazPerf.cmake
Normal file
30
cmake/FindLazPerf.cmake
Normal file
@ -0,0 +1,30 @@
|
||||
# CMake module to search for laz-perf
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# LazPerf_FOUND - system has the zip library
|
||||
# LazPerf_INCLUDE_DIRS - the zip include directories
|
||||
#
|
||||
# Copyright (c) 2020, Peter Petrik, <zilolv@gmail.com>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
FIND_PATH(LazPerf_INCLUDE_DIR
|
||||
laz-perf/io.hpp
|
||||
"$ENV{LIB_DIR}/include"
|
||||
"$ENV{INCLUDE}"
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
)
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LazPerf DEFAULT_MSG LazPerf_INCLUDE_DIR)
|
||||
|
||||
MARK_AS_ADVANCED(LazPerf_INCLUDE_DIR)
|
||||
|
||||
IF (LazPerf_FOUND)
|
||||
MESSAGE(STATUS "Found laz-perf: ${LazPerf_INCLUDE_DIR}")
|
||||
ELSE (LazPerf_FOUND)
|
||||
MESSAGE(FATAL_ERROR "Could not find laz-perf")
|
||||
ENDIF (LazPerf_FOUND)
|
||||
34
cmake/FindZSTD.cmake
Normal file
34
cmake/FindZSTD.cmake
Normal file
@ -0,0 +1,34 @@
|
||||
# CMake module to search for libzstd
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# ZSTD_FOUND - system has the zip library
|
||||
# ZSTD_INCLUDE_DIRS - the zip include directories
|
||||
# ZSTD_LIBRARY - Link this to use the zip library
|
||||
#
|
||||
# Copyright (c) 2020, Peter Petrik, <zilolv@gmail.com>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
FIND_PATH(ZSTD_INCLUDE_DIR
|
||||
zstd.h
|
||||
"$ENV{LIB_DIR}/include"
|
||||
"$ENV{INCLUDE}"
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
)
|
||||
|
||||
FIND_LIBRARY(ZSTD_LIBRARY NAMES zstd PATHS "$ENV{LIB_DIR}/lib" "$ENV{LIB}" /usr/local/lib /usr/lib )
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZSTD DEFAULT_MSG
|
||||
ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
|
||||
|
||||
MARK_AS_ADVANCED(ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
|
||||
|
||||
IF (ZSTD_FOUND)
|
||||
MESSAGE(STATUS "Found ZSTD: ${ZSTD_LIBRARY}")
|
||||
ELSE (ZSTD_FOUND)
|
||||
MESSAGE(FATAL_ERROR "Could not find ZSTD")
|
||||
ENDIF (ZSTD_FOUND)
|
||||
@ -1,44 +0,0 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/pointcloud/qgspointcloudindex.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsPointCloudIndex: QObject
|
||||
{
|
||||
%Docstring
|
||||
|
||||
Represents a indexed point clouds data in octree
|
||||
|
||||
.. note::
|
||||
|
||||
The API is considered EXPERIMENTAL and can be changed without a notice
|
||||
|
||||
.. versionadded:: 3.18
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgspointcloudindex.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
explicit QgsPointCloudIndex();
|
||||
~QgsPointCloudIndex();
|
||||
|
||||
void load( const QString &fileName );
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/pointcloud/qgspointcloudindex.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsPointCloudRenderer: QgsMapLayerRenderer
|
||||
{
|
||||
%Docstring
|
||||
@ -38,7 +39,6 @@ Represents a 2D renderer of point cloud data
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -439,7 +439,6 @@
|
||||
%Include auto_generated/mesh/qgsmeshcalculator.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudlayer.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudrenderer.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudindex.sip
|
||||
%Include auto_generated/metadata/qgsabstractmetadatabase.sip
|
||||
%Include auto_generated/metadata/qgslayermetadata.sip
|
||||
%Include auto_generated/metadata/qgslayermetadataformatter.sip
|
||||
|
||||
@ -626,6 +626,7 @@ SET(QGIS_CORE_SRCS
|
||||
pointcloud/qgspointcloudindex.cpp
|
||||
pointcloud/qgspointclouddataitems.cpp
|
||||
pointcloud/qgspointcloudprovidermetadata.cpp
|
||||
pointcloud/qgspointclouddecoder.cpp
|
||||
|
||||
labeling/qgslabelfeature.cpp
|
||||
labeling/qgslabelingengine.cpp
|
||||
@ -1301,6 +1302,7 @@ SET(QGIS_CORE_HDRS
|
||||
pointcloud/qgspointcloudindex.h
|
||||
pointcloud/qgspointclouddataitems.h
|
||||
pointcloud/qgspointcloudprovidermetadata.h
|
||||
pointcloud/qgspointclouddecoder.h
|
||||
|
||||
metadata/qgsabstractmetadatabase.h
|
||||
metadata/qgslayermetadata.h
|
||||
@ -1612,6 +1614,8 @@ INCLUDE_DIRECTORIES(SYSTEM
|
||||
${Qt5SerialPort_INCLUDE_DIRS}
|
||||
${Protobuf_INCLUDE_DIRS}
|
||||
${ZLIB_INCLUDE_DIRS}
|
||||
${ZSTD_INCLUDE_DIR}
|
||||
${LazPerf_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
|
||||
@ -1748,6 +1752,7 @@ TARGET_LINK_LIBRARIES(qgis_core
|
||||
${SQLITE3_LIBRARY}
|
||||
${SPATIALITE_LIBRARY}
|
||||
${LIBZIP_LIBRARY}
|
||||
${ZSTD_LIBRARY}
|
||||
${Protobuf_LITE_LIBRARY}
|
||||
${ZLIB_LIBRARIES}
|
||||
)
|
||||
|
||||
169
src/core/pointcloud/qgspointclouddecoder.cpp
Normal file
169
src/core/pointcloud/qgspointclouddecoder.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
/***************************************************************************
|
||||
qgspointcloudrenderer.cpp
|
||||
--------------------
|
||||
begin : October 2020
|
||||
copyright : (C) 2020 by Peter Petrik
|
||||
email : zilolv at gmail dot 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 "qgspointclouddecoder.h"
|
||||
#include "qgspointcloudindex.h"
|
||||
#include "qgsvector3d.h"
|
||||
|
||||
#include <zstd.h>
|
||||
#include <QFile>
|
||||
#include <iostream>
|
||||
|
||||
#include "laz-perf/io.hpp"
|
||||
#include "laz-perf/common/common.hpp"
|
||||
|
||||
QVector<qint32> QgsPointCloudDecoder::decompressBinary(const QString& filename, QgsPointCloudDataBounds &db)
|
||||
{
|
||||
Q_ASSERT( QFile::exists( filename ) );
|
||||
|
||||
QFile f( filename );
|
||||
bool r = f.open(QIODevice::ReadOnly);
|
||||
Q_ASSERT(r);
|
||||
|
||||
// WHY??? per-record should be 18 based on schema, not 46
|
||||
int stride = 46; //18;
|
||||
int count = f.size() / stride;
|
||||
qint32 xMin = -999999999, yMin = -999999999, zMin = -999999999;
|
||||
qint32 xMax = 999999999, yMax = 999999999, zMax = 999999999;
|
||||
QVector<qint32> data( count * 3 );
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
QByteArray bytes = f.read( stride );
|
||||
// WHY??? X,Y,Z are int32 values stored as doubles
|
||||
double *bytesD = (double*) bytes.constData();
|
||||
data[i*3+0] = (bytesD[0]);
|
||||
data[i*3+1] = (bytesD[1]);
|
||||
data[i*3+2] = (bytesD[2]);
|
||||
|
||||
xMin = std::min( xMin, data[i*3+0]);
|
||||
xMax = std::max( xMax, data[i*3+0]);
|
||||
yMin = std::min( yMin, data[i*3+1]);
|
||||
yMax = std::max( yMax, data[i*3+1]);
|
||||
zMin = std::min( zMin, data[i*3+2]);
|
||||
zMax = std::max( zMax, data[i*3+2]);
|
||||
}
|
||||
db = QgsPointCloudDataBounds(xMin, xMax, yMin, yMax, zMin, zMax);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* *************************************************************************************** */
|
||||
|
||||
QByteArray decompressZtdStream( const QByteArray &dataCompressed )
|
||||
{
|
||||
// NOTE: this is very primitive implementation because we expect the uncompressed
|
||||
// data will be always less than 10 MB
|
||||
|
||||
const int MAXSIZE=10000000;
|
||||
QByteArray dataUncompressed;
|
||||
dataUncompressed.resize( MAXSIZE );
|
||||
|
||||
ZSTD_DStream *strm = ZSTD_createDStream();
|
||||
ZSTD_initDStream(strm);
|
||||
|
||||
ZSTD_inBuffer m_inBuf;
|
||||
m_inBuf.src = reinterpret_cast<const void *>(dataCompressed.constData());
|
||||
m_inBuf.size = dataCompressed.size();
|
||||
m_inBuf.pos = 0;
|
||||
|
||||
ZSTD_outBuffer outBuf { reinterpret_cast<void *>(dataUncompressed.data()), MAXSIZE, 0 };
|
||||
size_t ret = ZSTD_decompressStream(strm, &outBuf, &m_inBuf);
|
||||
Q_ASSERT (!ZSTD_isError(ret));
|
||||
Q_ASSERT( outBuf.pos );
|
||||
Q_ASSERT( outBuf.pos < outBuf.size );
|
||||
|
||||
ZSTD_freeDStream(strm);
|
||||
dataUncompressed.resize(outBuf.pos);
|
||||
return dataUncompressed;
|
||||
}
|
||||
|
||||
QVector<qint32> QgsPointCloudDecoder::decompressZStandard(const QString& filename, QgsPointCloudDataBounds &db)
|
||||
{
|
||||
Q_ASSERT( QFile::exists( filename ) );
|
||||
|
||||
QFile f( filename );
|
||||
bool r = f.open(QIODevice::ReadOnly);
|
||||
Q_ASSERT(r);
|
||||
|
||||
QByteArray dataCompressed = f.readAll();
|
||||
QByteArray dataUncompressed = decompressZtdStream( dataCompressed );
|
||||
|
||||
// from here it's the same as "binary"
|
||||
|
||||
// WHY??? per-record should be 18 based on schema, not 46
|
||||
int stride = 46; //18;
|
||||
int count = dataUncompressed.size() / stride;
|
||||
qint32 xMin = -999999999, yMin = -999999999, zMin = -999999999;
|
||||
qint32 xMax = 999999999, yMax = 999999999, zMax = 999999999;
|
||||
|
||||
QVector<qint32> data( count * 3 );
|
||||
const char *ptr = dataUncompressed.constData();
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
// WHY??? X,Y,Z are int32 values stored as doubles
|
||||
double *bytesD = (double*) (ptr+stride*i);
|
||||
data[i*3+0] = (bytesD[0]);
|
||||
data[i*3+1] = (bytesD[1]);
|
||||
data[i*3+2] = (bytesD[2]);
|
||||
|
||||
xMin = std::min( xMin, data[i*3+0]);
|
||||
xMax = std::max( xMax, data[i*3+0]);
|
||||
yMin = std::min( yMin, data[i*3+1]);
|
||||
yMax = std::max( yMax, data[i*3+1]);
|
||||
zMin = std::min( zMin, data[i*3+2]);
|
||||
zMax = std::max( zMax, data[i*3+2]);
|
||||
}
|
||||
db = QgsPointCloudDataBounds(xMin, xMax, yMin, yMax, zMin, zMax);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* *************************************************************************************** */
|
||||
|
||||
QVector<qint32> QgsPointCloudDecoder::decompressLaz(const QString& filename, QgsPointCloudDataBounds &db)
|
||||
{
|
||||
std::ifstream file(filename.toLatin1().constData(), std::ios::binary);
|
||||
Q_ASSERT (file.good());
|
||||
|
||||
auto start = common::tick();
|
||||
|
||||
laszip::io::reader::file f(file);
|
||||
|
||||
size_t count = f.get_header().point_count;
|
||||
char buf[256]; // a buffer large enough to hold our point
|
||||
|
||||
qint32 xMin = -999999999, yMin = -999999999, zMin = -999999999;
|
||||
qint32 xMax = 999999999, yMax = 999999999, zMax = 999999999;
|
||||
QVector<qint32> data( count * 3 );
|
||||
|
||||
for(size_t i = 0 ; i < count ; i ++) {
|
||||
f.readPoint(buf); // read the point out
|
||||
laszip::formats::las::point10 p = laszip::formats::packers<laszip::formats::las::point10>::unpack(buf);
|
||||
|
||||
data[i*3+0] = p.x ;
|
||||
data[i*3+1] = p.y ;
|
||||
data[i*3+2] = p.z ;
|
||||
|
||||
xMin = std::min( xMin, data[i*3+0]);
|
||||
xMax = std::max( xMax, data[i*3+0]);
|
||||
yMin = std::min( yMin, data[i*3+1]);
|
||||
yMax = std::max( yMax, data[i*3+1]);
|
||||
zMin = std::min( zMin, data[i*3+2]);
|
||||
zMax = std::max( zMax, data[i*3+2]);
|
||||
}
|
||||
db = QgsPointCloudDataBounds(xMin, xMax, yMin, yMax, zMin, zMax);
|
||||
float t = common::since(start);
|
||||
std::cout << "LAZ-PERF Read through the points in " << t << " seconds." << std::endl;
|
||||
}
|
||||
41
src/core/pointcloud/qgspointclouddecoder.h
Normal file
41
src/core/pointcloud/qgspointclouddecoder.h
Normal file
@ -0,0 +1,41 @@
|
||||
/***************************************************************************
|
||||
qgspointclouddecoder.h
|
||||
--------------------
|
||||
begin : October 2020
|
||||
copyright : (C) 2020 by Peter Petrik
|
||||
email : zilolv at gmail dot 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSPOINTCLOUDDECODER_H
|
||||
#define QGSPOINTCLOUDDECODER_H
|
||||
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
|
||||
class QgsPointCloudDataBounds;
|
||||
class QgsVector3D;
|
||||
|
||||
namespace QgsPointCloudDecoder
|
||||
{
|
||||
QVector<qint32> decompressBinary(const QString& filename, QgsPointCloudDataBounds &db);
|
||||
QVector<qint32> decompressZStandard(const QString& filename, QgsPointCloudDataBounds &db);
|
||||
QVector<qint32> decompressLaz(const QString& filename, QgsPointCloudDataBounds &db);
|
||||
};
|
||||
|
||||
|
||||
#endif // QGSPOINTCLOUDDECODER_H
|
||||
@ -16,13 +16,272 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgspointcloudindex.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTime>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "qgspointclouddecoder.h"
|
||||
|
||||
IndexedPointCloudNode::IndexedPointCloudNode(): d( -1 ), x( 0 ), y( 0 ), z( 0 ) {}
|
||||
|
||||
IndexedPointCloudNode::IndexedPointCloudNode( int _d, int _x, int _y, int _z ): d( _d ), x( _x ), y( _y ), z( _z ) {}
|
||||
|
||||
bool IndexedPointCloudNode::operator==( const IndexedPointCloudNode &other ) const { return d == other.d && x == other.x && y == other.y && z == other.z; }
|
||||
|
||||
IndexedPointCloudNode IndexedPointCloudNode::fromString( const QString &str )
|
||||
{
|
||||
QStringList lst = str.split( '-' );
|
||||
if ( lst.count() != 4 )
|
||||
return IndexedPointCloudNode();
|
||||
return IndexedPointCloudNode( lst[0].toInt(), lst[1].toInt(), lst[2].toInt(), lst[3].toInt() );
|
||||
}
|
||||
|
||||
QString IndexedPointCloudNode::toString() const
|
||||
{
|
||||
return QString( "%1-%2-%3-%4" ).arg( d ).arg( x ).arg( y ).arg( z );
|
||||
}
|
||||
|
||||
uint qHash( const IndexedPointCloudNode &id )
|
||||
{
|
||||
return id.d + id.x + id.y + id.z;
|
||||
}
|
||||
|
||||
QgsPointCloudDataBounds::QgsPointCloudDataBounds() = default;
|
||||
|
||||
QgsPointCloudDataBounds::QgsPointCloudDataBounds( qint32 xmin, qint32 ymin, qint32 xmax, qint32 ymax, qint32 zmin, qint32 zmax )
|
||||
: mXMin( xmin )
|
||||
, mYMin( ymin )
|
||||
, mXMax( xmax )
|
||||
, mYMax( ymax )
|
||||
, mZMin( zmin )
|
||||
, mZMax( zmax )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QgsPointCloudDataBounds::QgsPointCloudDataBounds( const QgsPointCloudDataBounds &obj )
|
||||
: mXMin( obj.xMin() )
|
||||
, mYMin( obj.yMin() )
|
||||
, mXMax( obj.xMax() )
|
||||
, mYMax( obj.yMax() )
|
||||
, mZMin( obj.zMin() )
|
||||
, mZMax( obj.zMax() )
|
||||
{
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::xMin() const
|
||||
{
|
||||
return mXMin;
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::yMin() const
|
||||
{
|
||||
return mYMin;
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::xMax() const
|
||||
{
|
||||
return mXMax;
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::yMax() const
|
||||
{
|
||||
return mYMax;
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::zMin() const
|
||||
{
|
||||
return mZMin;
|
||||
}
|
||||
|
||||
qint32 QgsPointCloudDataBounds::zMax() const
|
||||
{
|
||||
return mZMax;
|
||||
}
|
||||
|
||||
QgsPointCloudIndex::QgsPointCloudIndex() = default;
|
||||
|
||||
QgsPointCloudIndex::~QgsPointCloudIndex() = default;
|
||||
|
||||
void QgsPointCloudIndex::load( const QString &fileName )
|
||||
bool QgsPointCloudIndex::load( const QString &fileName )
|
||||
{
|
||||
// mDirectory = directory;
|
||||
QFile f( fileName );
|
||||
if ( !f.open( QIODevice::ReadOnly ) )
|
||||
return false;
|
||||
|
||||
const QDir directory = QFileInfo( fileName ).absoluteDir();
|
||||
|
||||
QByteArray dataJson = f.readAll();
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson( dataJson, &err );
|
||||
if ( err.error != QJsonParseError::NoError )
|
||||
return false;
|
||||
|
||||
mDataType = doc["dataType"].toString(); // "binary" or "laszip"
|
||||
if ( mDataType != "laszip" && mDataType != "binary" && mDataType != "zstandard" )
|
||||
return false;
|
||||
|
||||
QString hierarchyType = doc["hierarchyType"].toString(); // "json" or "gzip"
|
||||
if ( hierarchyType != "json" )
|
||||
return false;
|
||||
|
||||
mSpan = doc["span"].toInt();
|
||||
|
||||
QJsonArray bounds = doc["bounds"].toArray();
|
||||
if ( bounds.size() != 6 )
|
||||
return false;
|
||||
|
||||
QJsonArray schemaArray = doc["schema"].toArray();
|
||||
|
||||
for ( QJsonValue schemaItem : schemaArray )
|
||||
{
|
||||
QJsonObject schemaObj = schemaItem.toObject();
|
||||
QString name = schemaObj["name"].toString();
|
||||
QString type = schemaObj["type"].toString();
|
||||
int size = schemaObj["size"].toInt();
|
||||
|
||||
float scale = 1.f;
|
||||
if ( schemaObj.contains( "scale" ) )
|
||||
scale = schemaObj["scale"].toDouble();
|
||||
|
||||
float offset = 0.f;
|
||||
if ( schemaObj.contains( "offset" ) )
|
||||
offset = schemaObj["offset"].toDouble();
|
||||
|
||||
if ( name == "X" )
|
||||
{
|
||||
mOffset.set( offset, mOffset.y(), mOffset.z() );
|
||||
mScale.set( scale, mScale.y(), mScale.z() );
|
||||
}
|
||||
else if ( name == "Y" )
|
||||
{
|
||||
mOffset.set( mOffset.x(), offset, mOffset.z() );
|
||||
mScale.set( mScale.x(), scale, mScale.z() );
|
||||
}
|
||||
else if ( name == "Z" )
|
||||
{
|
||||
mOffset.set( mOffset.x(), mOffset.y(), offset );
|
||||
mScale.set( mScale.x(), mScale.y(), scale );
|
||||
}
|
||||
|
||||
// TODO: can parse also stats: "count", "minimum", "maximum", "mean", "stddev", "variance"
|
||||
}
|
||||
|
||||
// save mRootBounds
|
||||
|
||||
// bounds (cube - octree volume)
|
||||
double xmin = bounds[0].toDouble();
|
||||
double ymin = bounds[1].toDouble();
|
||||
double zmin = bounds[2].toDouble();
|
||||
double xmax = bounds[3].toDouble();
|
||||
double ymax = bounds[4].toDouble();
|
||||
double zmax = bounds[5].toDouble();
|
||||
|
||||
mRootBounds = QgsPointCloudDataBounds(
|
||||
( xmin - mOffset.x() ) / mScale.x(),
|
||||
( xmax - mOffset.x() ) / mScale.x(),
|
||||
( ymin - mOffset.y() ) / mScale.y(),
|
||||
( ymax - mOffset.y() ) / mScale.y(),
|
||||
( zmin - mOffset.z() ) / mScale.z(),
|
||||
( zmax - mOffset.z() ) / mScale.z()
|
||||
);
|
||||
|
||||
|
||||
double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
|
||||
qDebug() << "lvl0 node size in CRS units:" << dx << dy << dz; // all dims should be the same
|
||||
qDebug() << "res at lvl0" << dx / mSpan;
|
||||
qDebug() << "res at lvl1" << dx / mSpan / 2;
|
||||
qDebug() << "res at lvl2" << dx / mSpan / 4 << "with node size" << dx / 4;
|
||||
|
||||
// load hierarchy
|
||||
|
||||
QFile fH( directory.filePath( "/ept-hierarchy/0-0-0-0.json" ) );
|
||||
if ( !fH.open( QIODevice::ReadOnly ) )
|
||||
return false;
|
||||
|
||||
QByteArray dataJsonH = fH.readAll();
|
||||
QJsonParseError errH;
|
||||
QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
|
||||
if ( errH.error != QJsonParseError::NoError )
|
||||
return false;
|
||||
|
||||
QJsonObject rootHObj = docH.object();
|
||||
for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
|
||||
{
|
||||
QString nodeIdStr = it.key();
|
||||
int nodePointCount = it.value().toInt();
|
||||
IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr );
|
||||
mHierarchy[nodeId] = nodePointCount;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<IndexedPointCloudNode> QgsPointCloudIndex::children( const IndexedPointCloudNode &n )
|
||||
{
|
||||
Q_ASSERT( mHierarchy.contains( n ) );
|
||||
QList<IndexedPointCloudNode> lst;
|
||||
int d = n.d + 1;
|
||||
int x = n.x * 2;
|
||||
int y = n.y * 2;
|
||||
int z = n.z * 2;
|
||||
|
||||
for ( int i = 0; i < 8; ++i )
|
||||
{
|
||||
int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
|
||||
IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz );
|
||||
if ( mHierarchy.contains( n2 ) )
|
||||
lst.append( n2 );
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
QVector<qint32> QgsPointCloudIndex::nodePositionDataAsInt32( const IndexedPointCloudNode &n, QgsVector3D, QgsVector3D, QgsPointCloudDataBounds &db )
|
||||
{
|
||||
Q_ASSERT( mHierarchy.contains( n ) );
|
||||
int count = mHierarchy[n];
|
||||
|
||||
if ( mDataType == "binary" )
|
||||
{
|
||||
QString filename = QString( "%1/ept-data/%2.bin" ).arg( mDirectory ).arg( n.toString() );
|
||||
Q_ASSERT( QFile::exists( filename ) );
|
||||
return QgsPointCloudDecoder::decompressBinary( filename, db );
|
||||
}
|
||||
else if ( mDataType == "zstandard" )
|
||||
{
|
||||
QString filename = QString( "%1/ept-data/%2.zst" ).arg( mDirectory ).arg( n.toString() );
|
||||
Q_ASSERT( QFile::exists( filename ) );
|
||||
return QgsPointCloudDecoder::decompressBinary( filename, db );
|
||||
}
|
||||
else // if ( mDataType == "laz" )
|
||||
{
|
||||
QString filename = QString( "%1/ept-data/%2.laz" ).arg( mDirectory ).arg( n.toString() );
|
||||
Q_ASSERT( QFile::exists( filename ) );
|
||||
return QgsPointCloudDecoder::decompressBinary( filename, db );
|
||||
}
|
||||
}
|
||||
|
||||
QgsPointCloudDataBounds QgsPointCloudIndex::nodeBounds( const IndexedPointCloudNode &n )
|
||||
{
|
||||
qint32 xMin = -999999999, yMin = -999999999, zMin = -999999999;
|
||||
qint32 xMax = 999999999, yMax = 999999999, zMax = 999999999;
|
||||
|
||||
int d = mRootBounds.xMax() - mRootBounds.xMin();
|
||||
double dLevel = ( double )d / pow( 2, n.d );
|
||||
|
||||
xMin = round( mRootBounds.xMin() + dLevel * n.x );
|
||||
xMax = round( mRootBounds.xMin() + dLevel * ( n.x + 1 ) );
|
||||
yMin = round( mRootBounds.yMin() + dLevel * n.y );
|
||||
yMax = round( mRootBounds.yMin() + dLevel * ( n.y + 1 ) );
|
||||
zMin = round( mRootBounds.zMin() + dLevel * n.z );
|
||||
zMax = round( mRootBounds.zMin() + dLevel * ( n.z + 1 ) );
|
||||
|
||||
QgsPointCloudDataBounds db( xMin, xMax, yMin, yMax, zMin, zMax );
|
||||
return db;
|
||||
}
|
||||
|
||||
@ -20,8 +20,70 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgsvector3d.h"
|
||||
#include "qgis_sip.h"
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
*
|
||||
* Represents a indexed point cloud node in octree
|
||||
*
|
||||
* \note The API is considered EXPERIMENTAL and can be changed without a notice
|
||||
*
|
||||
* \since QGIS 3.18
|
||||
*/
|
||||
class CORE_EXPORT IndexedPointCloudNode
|
||||
{
|
||||
public:
|
||||
IndexedPointCloudNode(); // invalid node ID
|
||||
IndexedPointCloudNode( int _d, int _x, int _y, int _z );
|
||||
|
||||
bool isValid() const { return d >= 0; }
|
||||
|
||||
bool operator==( const IndexedPointCloudNode &other ) const;
|
||||
|
||||
static IndexedPointCloudNode fromString( const QString &str );
|
||||
|
||||
QString toString() const;
|
||||
|
||||
// TODO make private
|
||||
int d = -1, x = -1, y = -1, z = -1;
|
||||
};
|
||||
|
||||
uint qHash( const IndexedPointCloudNode &id );
|
||||
|
||||
// what are the min/max to expect in the piece of data
|
||||
class CORE_EXPORT QgsPointCloudDataBounds
|
||||
{
|
||||
public:
|
||||
QgsPointCloudDataBounds(); // invalid
|
||||
QgsPointCloudDataBounds( qint32 xmin, qint32 ymin, qint32 xmax, qint32 ymax, qint32 zmin, qint32 zmax );
|
||||
QgsPointCloudDataBounds( const QgsPointCloudDataBounds &obj );
|
||||
|
||||
qint32 xMin() const;
|
||||
|
||||
qint32 yMin() const;
|
||||
|
||||
qint32 xMax() const;
|
||||
|
||||
qint32 yMax() const;
|
||||
|
||||
qint32 zMin() const;
|
||||
|
||||
qint32 zMax() const;
|
||||
|
||||
private:
|
||||
qint32 mXMin, mYMin, mXMax, mYMax, mZMin, mZMax;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -40,7 +102,24 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
|
||||
explicit QgsPointCloudIndex();
|
||||
~QgsPointCloudIndex();
|
||||
|
||||
void load( const QString &fileName );
|
||||
bool load( const QString &fileName );
|
||||
IndexedPointCloudNode root() { return IndexedPointCloudNode( 0, 0, 0, 0 ); }
|
||||
|
||||
QList<IndexedPointCloudNode> children( const IndexedPointCloudNode &n );
|
||||
|
||||
QVector<qint32> nodePositionDataAsInt32( const IndexedPointCloudNode &n, QgsVector3D scale, QgsVector3D offset, QgsPointCloudDataBounds &db );
|
||||
|
||||
QgsPointCloudDataBounds nodeBounds( const IndexedPointCloudNode &n );
|
||||
|
||||
private:
|
||||
QString mDirectory;
|
||||
QString mDataType;
|
||||
|
||||
QHash<IndexedPointCloudNode, int> mHierarchy;
|
||||
QgsVector3D mScale; //!< Scale of our int32 coordinates compared to CRS coords
|
||||
QgsVector3D mOffset; //!< Offset of our int32 coordinates compared to CRS coords
|
||||
QgsPointCloudDataBounds mRootBounds; //!< Bounds of the root node's cube (in int32 coordinates)
|
||||
int mSpan; //!< Number of points in one direction in a single node
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
QgsPointCloudLayer::QgsPointCloudLayer( const QString &path, const QString &baseName )
|
||||
: QgsMapLayer( QgsMapLayerType::PointCloudLayer, path, baseName )
|
||||
{
|
||||
|
||||
setValid( loadDataSource() );
|
||||
}
|
||||
|
||||
QgsPointCloudLayer::~QgsPointCloudLayer() = default;
|
||||
@ -95,10 +95,11 @@ QString QgsPointCloudLayer::loadDefaultStyle( bool &resultFlag )
|
||||
|
||||
QgsPointCloudIndex *QgsPointCloudLayer::pointCloudIndex() const
|
||||
{
|
||||
return mPointCloudIndex;
|
||||
return mPointCloudIndex.get();
|
||||
}
|
||||
|
||||
bool QgsPointCloudLayer::loadDataSource()
|
||||
{
|
||||
return false;
|
||||
mPointCloudIndex.reset( new QgsPointCloudIndex() );
|
||||
return mPointCloudIndex->load( source() );
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ class CORE_EXPORT QgsPointCloudLayer : public QgsMapLayer
|
||||
QgsPointCloudLayer( const QgsPointCloudLayer &rhs );
|
||||
#endif
|
||||
|
||||
QgsPointCloudIndex *mPointCloudIndex = nullptr;
|
||||
std::unique_ptr<QgsPointCloudIndex> mPointCloudIndex;
|
||||
|
||||
//! Renderer assigned to the layer to draw map
|
||||
std::unique_ptr<QgsPointCloudRenderer> mRenderer;
|
||||
|
||||
@ -15,9 +15,12 @@
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <QTime>
|
||||
|
||||
#include "qgspointcloudrenderer.h"
|
||||
#include "qgspointcloudlayer.h"
|
||||
#include "qgsrendercontext.h"
|
||||
#include "qgspointcloudindex.h"
|
||||
|
||||
QgsPointCloudRenderer::QgsPointCloudRenderer( QgsPointCloudLayer *layer, QgsRenderContext &context )
|
||||
: QgsMapLayerRenderer( layer->id(), &context )
|
||||
@ -26,8 +29,91 @@ QgsPointCloudRenderer::QgsPointCloudRenderer( QgsPointCloudLayer *layer, QgsRend
|
||||
|
||||
}
|
||||
|
||||
static QList<IndexedPointCloudNode> _traverseTree( QgsPointCloudIndex *pc, IndexedPointCloudNode n, int maxDepth )
|
||||
{
|
||||
QList<IndexedPointCloudNode> nodes;
|
||||
nodes.append( n );
|
||||
|
||||
for ( auto nn : pc->children( n ) )
|
||||
{
|
||||
if ( maxDepth > 0 )
|
||||
nodes += _traverseTree( pc, nn, maxDepth - 1 );
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
bool QgsPointCloudRenderer::render()
|
||||
{
|
||||
// TODO cache!?
|
||||
QgsPointCloudIndex *pc = mLayer->pointCloudIndex();
|
||||
|
||||
QgsRenderContext &context = *renderContext();
|
||||
|
||||
// Set up the render configuration options
|
||||
QPainter *painter = context.painter();
|
||||
|
||||
painter->save();
|
||||
context.setPainterFlagsUsingContext( painter );
|
||||
|
||||
QPen pen = painter->pen();
|
||||
pen.setCapStyle( Qt::FlatCap );
|
||||
pen.setJoinStyle( Qt::MiterJoin );
|
||||
|
||||
double penWidth = context.convertToPainterUnits( 1, QgsUnitTypes::RenderUnit::RenderMillimeters );
|
||||
pen.setWidthF( penWidth );
|
||||
pen.setColor( Qt::black );
|
||||
painter->setPen( pen );
|
||||
|
||||
|
||||
QList<IndexedPointCloudNode> lvl1 = pc->children( IndexedPointCloudNode( 0, 0, 0, 0 ) );
|
||||
// qDebug() << lvl1;
|
||||
Q_ASSERT( lvl1.count() == 4 );
|
||||
|
||||
QgsVector3D scale;
|
||||
QgsVector3D offset;
|
||||
QgsPointCloudDataBounds db;
|
||||
|
||||
//dumpHierarchy( pc );
|
||||
IndexedPointCloudNode n0( 0, 0, 0, 0 );
|
||||
IndexedPointCloudNode n1( 1, 0, 0, 0 );
|
||||
IndexedPointCloudNode n2( 2, 2, 2, 0 );
|
||||
IndexedPointCloudNode n4( 4, 6, 7, 1 );
|
||||
IndexedPointCloudNode n5( 5, 14, 14, 3 );
|
||||
QVector<qint32> nodeData = pc->nodePositionDataAsInt32( n1, scale, offset, db );
|
||||
|
||||
imgW = 256;
|
||||
imgH = imgW;
|
||||
img = QImage( imgW, imgH, QImage::Format_RGB32 );
|
||||
img.fill( Qt::black );
|
||||
mBounds = QgsPointCloudDataBounds( db );
|
||||
|
||||
QTime t;
|
||||
t.start();
|
||||
|
||||
// TODO: traverse with spatial filter
|
||||
|
||||
QList<IndexedPointCloudNode> nodes = _traverseTree( pc, n1, 2 );
|
||||
// qDebug() << nodes;
|
||||
|
||||
// drawing
|
||||
for ( auto n : nodes )
|
||||
{
|
||||
//drawImg.drawData( );
|
||||
drawData( pc->nodePositionDataAsInt32( n, scale, offset, db ) );
|
||||
}
|
||||
|
||||
qDebug() << "grand total incl loading " << t.elapsed() << "ms";
|
||||
|
||||
qDebug() << "totals:" << nodesDrawn << "nodes | " << pointsDrawn << " points | " << drawingTime << "ms";
|
||||
|
||||
//drawData( drawImg, nodeData );
|
||||
|
||||
// img.save( "/tmp/chmura.png" );
|
||||
|
||||
painter->restore();
|
||||
painter->drawImage( 0, 0, img, imgW, imgH );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -44,3 +130,34 @@ void QgsPointCloudRenderer::readXml( const QDomElement &elem, const QgsReadWrite
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QgsPointCloudRenderer::drawData( const QVector<qint32> &data )
|
||||
{
|
||||
QTime tD;
|
||||
tD.start();
|
||||
QRgb *rgb = ( QRgb * ) img.bits();
|
||||
const qint32 *ptr = data.constData();
|
||||
int count = data.count() / 3;
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
qint32 ix = ptr[i * 3 + 0];
|
||||
qint32 iy = ptr[i * 3 + 1];
|
||||
qint32 iz = ptr[i * 3 + 2];
|
||||
int imgx = round( double( ix - mBounds.xMin() ) / ( mBounds.xMax() - mBounds.xMin() ) * ( imgW - 1 ) );
|
||||
int imgy = round( double( iy - mBounds.yMin() ) / ( mBounds.yMax() - mBounds.yMin() ) * ( imgH - 1 ) );
|
||||
int imgz = round( double( iz - mBounds.zMin() ) / ( mBounds.zMax() - mBounds.zMin() ) * 155 );
|
||||
int c = 100 + imgz;
|
||||
imgy = imgH - imgy - 1; // Y in QImage is 0 at the top an increases towards bottom - need to invert it
|
||||
if ( imgx >= 0 && imgx < imgW && imgy >= 0 && imgy < imgH )
|
||||
rgb[imgx + imgy * imgW] = qRgb( c, c, c );
|
||||
//qDebug() << imgx << imgy << imgz;
|
||||
//drawImg.img.setPixelColor( imgx, imgy, QColor( c, c, c ) ); // Qt::yellow );
|
||||
}
|
||||
|
||||
// stats
|
||||
++nodesDrawn;
|
||||
pointsDrawn += count;
|
||||
int timeDraw = tD.elapsed();
|
||||
drawingTime += timeDraw;
|
||||
qDebug() << "time draw" << timeDraw << "ms";
|
||||
}
|
||||
|
||||
@ -21,13 +21,16 @@
|
||||
#include "qgis_core.h"
|
||||
#include "qgsmaplayerrenderer.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgspointcloudindex.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
class QgsRenderContext;
|
||||
class QgsPointCloudLayer;
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
*
|
||||
@ -49,8 +52,19 @@ class CORE_EXPORT QgsPointCloudRenderer: public QgsMapLayerRenderer
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
|
||||
|
||||
protected:
|
||||
private:
|
||||
QgsPointCloudLayer *mLayer = nullptr;
|
||||
|
||||
QImage img;
|
||||
int imgW, imgH;
|
||||
QgsPointCloudDataBounds mBounds;
|
||||
|
||||
// some stats
|
||||
int nodesDrawn = 0;
|
||||
int pointsDrawn = 0;
|
||||
int drawingTime = 0; // in msec
|
||||
|
||||
void drawData( const QVector<qint32> &data );
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -17,6 +17,10 @@
|
||||
#ifndef QGSPOINTCLOUDSOURCESELECT_H
|
||||
#define QGSPOINTCLOUDSOURCESELECT_H
|
||||
|
||||
///@cond PRIVATE
|
||||
#include "qgis_sip.h"
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "ui_qgspointcloudsourceselectbase.h"
|
||||
#include "qgsabstractdatasourcewidget.h"
|
||||
#include "qgis_gui.h"
|
||||
@ -43,4 +47,6 @@ class QgsPointCloudSourceSelect : public QgsAbstractDataSourceWidget, private Ui
|
||||
|
||||
};
|
||||
|
||||
///@endcond
|
||||
///
|
||||
#endif // QGSPOINTCLOUDSOURCESELECT_H
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user