port the basic renderer from Martin's prototype

This commit is contained in:
Peter Petrik 2020-10-16 12:05:06 +02:00 committed by Nyall Dawson
parent 13ecb8c452
commit 8a42c5759f
16 changed files with 766 additions and 53 deletions

View File

@ -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
View 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
View 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)

View File

@ -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 *
************************************************************************/

View File

@ -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:
};

View File

@ -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

View File

@ -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}
)

View 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;
}

View 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

View File

@ -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;
}

View File

@ -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
};

View File

@ -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() );
}

View File

@ -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;

View File

@ -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";
}

View File

@ -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 );
};

View File

@ -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