1 /***************************************************************************
2 qgspointcloudclassifiedrenderer.h
3 --------------------
4 begin : October 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson 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 "qgspointcloudclassifiedrenderer.h"
19 #include "qgspointcloudblock.h"
20 #include "qgsstyle.h"
21 #include "qgscolorramp.h"
22 #include "qgssymbollayerutils.h"
23 #include "qgslayertreemodellegendnode.h"
24 #include "qgspointclouddataprovider.h"
25
QgsPointCloudCategory(const int value,const QColor & color,const QString & label,bool render)26 QgsPointCloudCategory::QgsPointCloudCategory( const int value, const QColor &color, const QString &label, bool render )
27 : mValue( value )
28 , mColor( color )
29 , mLabel( label )
30 , mRender( render )
31 {
32 }
33
34
35 //
36 // QgsPointCloudClassifiedRenderer
37 //
38
QgsPointCloudClassifiedRenderer()39 QgsPointCloudClassifiedRenderer::QgsPointCloudClassifiedRenderer()
40 : mCategories( defaultCategories() )
41 {
42 }
43
type() const44 QString QgsPointCloudClassifiedRenderer::type() const
45 {
46 return QStringLiteral( "classified" );
47 }
48
clone() const49 QgsPointCloudRenderer *QgsPointCloudClassifiedRenderer::clone() const
50 {
51 std::unique_ptr< QgsPointCloudClassifiedRenderer > res = std::make_unique< QgsPointCloudClassifiedRenderer >();
52 res->mAttribute = mAttribute;
53 res->mCategories = mCategories;
54
55 copyCommonProperties( res.get() );
56
57 return res.release();
58 }
59
renderBlock(const QgsPointCloudBlock * block,QgsPointCloudRenderContext & context)60 void QgsPointCloudClassifiedRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context )
61 {
62 const QgsRectangle visibleExtent = context.renderContext().extent();
63
64 const char *ptr = block->data();
65 int count = block->pointCount();
66 const QgsPointCloudAttributeCollection request = block->attributes();
67
68 const std::size_t recordSize = request.pointRecordSize();
69 int attributeOffset = 0;
70 const QgsPointCloudAttribute *attribute = request.find( mAttribute, attributeOffset );
71 if ( !attribute )
72 return;
73 const QgsPointCloudAttribute::DataType attributeType = attribute->type();
74
75 const QgsDoubleRange zRange = context.renderContext().zRange();
76 const bool considerZ = !zRange.isInfinite();
77
78 int rendered = 0;
79 double x = 0;
80 double y = 0;
81 double z = 0;
82 const QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
83 const bool reproject = ct.isValid();
84
85 QHash< int, QColor > colors;
86 for ( const QgsPointCloudCategory &category : std::as_const( mCategories ) )
87 {
88 if ( !category.renderState() )
89 continue;
90
91 colors.insert( category.value(), category.color() );
92 }
93
94 for ( int i = 0; i < count; ++i )
95 {
96 if ( context.renderContext().renderingStopped() )
97 {
98 break;
99 }
100
101 if ( considerZ )
102 {
103 // z value filtering is cheapest, if we're doing it...
104 z = pointZ( context, ptr, i );
105 if ( !zRange.contains( z ) )
106 continue;
107 }
108
109 int attributeValue = 0;
110 context.getAttribute( ptr, i * recordSize + attributeOffset, attributeType, attributeValue );
111 const QColor color = colors.value( attributeValue );
112 if ( !color.isValid() )
113 continue;
114
115 pointXY( context, ptr, i, x, y );
116 if ( visibleExtent.contains( x, y ) )
117 {
118 if ( reproject )
119 {
120 try
121 {
122 ct.transformInPlace( x, y, z );
123 }
124 catch ( QgsCsException & )
125 {
126 continue;
127 }
128 }
129
130 drawPoint( x, y, color, context );
131 rendered++;
132 }
133 }
134 context.incrementPointsRendered( rendered );
135 }
136
willRenderPoint(const QVariantMap & pointAttributes)137 bool QgsPointCloudClassifiedRenderer::willRenderPoint( const QVariantMap &pointAttributes )
138 {
139 if ( !pointAttributes.contains( mAttribute ) )
140 return false;
141 bool parsedCorrectly;
142 int attributeInt = pointAttributes[ mAttribute ].toInt( &parsedCorrectly );
143 if ( !parsedCorrectly )
144 return false;
145 for ( const QgsPointCloudCategory &category : std::as_const( mCategories ) )
146 {
147 if ( category.value() == attributeInt )
148 return category.renderState();
149 }
150 return false;
151 }
152
create(QDomElement & element,const QgsReadWriteContext & context)153 QgsPointCloudRenderer *QgsPointCloudClassifiedRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
154 {
155 std::unique_ptr< QgsPointCloudClassifiedRenderer > r = std::make_unique< QgsPointCloudClassifiedRenderer >();
156
157 r->setAttribute( element.attribute( QStringLiteral( "attribute" ), QStringLiteral( "Classification" ) ) );
158
159 QgsPointCloudCategoryList categories;
160 const QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
161 if ( !catsElem.isNull() )
162 {
163 QDomElement catElem = catsElem.firstChildElement();
164 while ( !catElem.isNull() )
165 {
166 if ( catElem.tagName() == QLatin1String( "category" ) )
167 {
168 const int value = catElem.attribute( QStringLiteral( "value" ) ).toInt();
169 const QString label = catElem.attribute( QStringLiteral( "label" ) );
170 const bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
171 const QColor color = QgsSymbolLayerUtils::decodeColor( catElem.attribute( QStringLiteral( "color" ) ) );
172 categories.append( QgsPointCloudCategory( value, color, label, render ) );
173 }
174 catElem = catElem.nextSiblingElement();
175 }
176 r->setCategories( categories );
177 }
178
179 r->restoreCommonProperties( element, context );
180
181 return r.release();
182 }
183
defaultCategories()184 QgsPointCloudCategoryList QgsPointCloudClassifiedRenderer::defaultCategories()
185 {
186 return QgsPointCloudCategoryList() << QgsPointCloudCategory( 1, QColor( "#AAAAAA" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 1 ) )
187 << QgsPointCloudCategory( 2, QColor( "#AA5500" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 2 ) )
188 << QgsPointCloudCategory( 3, QColor( "#00AAAA" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 3 ) )
189 << QgsPointCloudCategory( 4, QColor( "#55FF55" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 4 ) )
190 << QgsPointCloudCategory( 5, QColor( "#00AA00" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 5 ) )
191 << QgsPointCloudCategory( 6, QColor( "#FF5555" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 6 ) )
192 << QgsPointCloudCategory( 7, QColor( "#AA0000" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 7 ) )
193 << QgsPointCloudCategory( 8, QColor( "#555555" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 8 ) )
194 << QgsPointCloudCategory( 9, QColor( "#55FFFF" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 9 ) )
195 << QgsPointCloudCategory( 10, QColor( "#AA00AA" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 10 ) )
196 << QgsPointCloudCategory( 11, QColor( "#000000" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 11 ) )
197 << QgsPointCloudCategory( 12, QColor( "#555555" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 12 ) )
198 << QgsPointCloudCategory( 13, QColor( "#FFFF55" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 13 ) )
199 << QgsPointCloudCategory( 14, QColor( "#FFFF55" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 14 ) )
200 << QgsPointCloudCategory( 15, QColor( "#FF55FF" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 15 ) )
201 << QgsPointCloudCategory( 16, QColor( "#FFFF55" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 16 ) )
202 << QgsPointCloudCategory( 17, QColor( "#5555FF" ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 17 ) )
203 << QgsPointCloudCategory( 18, QColor( 100, 100, 100 ), QgsPointCloudDataProvider::translatedLasClassificationCodes().value( 18 ) );
204 }
205
save(QDomDocument & doc,const QgsReadWriteContext & context) const206 QDomElement QgsPointCloudClassifiedRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
207 {
208 QDomElement rendererElem = doc.createElement( QStringLiteral( "renderer" ) );
209
210 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "classified" ) );
211 rendererElem.setAttribute( QStringLiteral( "attribute" ), mAttribute );
212
213 // categories
214 QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
215 for ( const QgsPointCloudCategory &category : mCategories )
216 {
217 QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
218 catElem.setAttribute( QStringLiteral( "value" ), QString::number( category.value() ) );
219 catElem.setAttribute( QStringLiteral( "label" ), category.label() );
220 catElem.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( category.color() ) );
221 catElem.setAttribute( QStringLiteral( "render" ), category.renderState() ? "true" : "false" );
222 catsElem.appendChild( catElem );
223 }
224 rendererElem.appendChild( catsElem );
225
226 saveCommonProperties( rendererElem, context );
227
228 return rendererElem;
229 }
230
usedAttributes(const QgsPointCloudRenderContext &) const231 QSet<QString> QgsPointCloudClassifiedRenderer::usedAttributes( const QgsPointCloudRenderContext & ) const
232 {
233 QSet<QString> res;
234 res << mAttribute;
235 return res;
236 }
237
createLegendNodes(QgsLayerTreeLayer * nodeLayer)238 QList<QgsLayerTreeModelLegendNode *> QgsPointCloudClassifiedRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
239 {
240 QList<QgsLayerTreeModelLegendNode *> nodes;
241
242 for ( const QgsPointCloudCategory &category : std::as_const( mCategories ) )
243 {
244 nodes << new QgsRasterSymbolLegendNode( nodeLayer, category.color(), category.label(), nullptr, true, QString::number( category.value() ) );
245 }
246
247 return nodes;
248 }
249
legendRuleKeys() const250 QStringList QgsPointCloudClassifiedRenderer::legendRuleKeys() const
251 {
252 QStringList res;
253 for ( const QgsPointCloudCategory &category : std::as_const( mCategories ) )
254 {
255 res << QString::number( category.value() );
256 }
257 return res;
258 }
259
legendItemChecked(const QString & key)260 bool QgsPointCloudClassifiedRenderer::legendItemChecked( const QString &key )
261 {
262 bool ok = false;
263 const int value = key.toInt( &ok );
264 if ( !ok )
265 return false;
266
267 for ( const QgsPointCloudCategory &category : std::as_const( mCategories ) )
268 {
269 if ( category.value() == value )
270 return category.renderState();
271 }
272 return false;
273 }
274
checkLegendItem(const QString & key,bool state)275 void QgsPointCloudClassifiedRenderer::checkLegendItem( const QString &key, bool state )
276 {
277 bool ok = false;
278 const int value = key.toInt( &ok );
279 if ( !ok )
280 return;
281
282 for ( auto it = mCategories.begin(); it != mCategories.end(); ++it )
283 {
284 if ( it->value() == value )
285 {
286 it->setRenderState( state );
287 return;
288 }
289 }
290 }
291
attribute() const292 QString QgsPointCloudClassifiedRenderer::attribute() const
293 {
294 return mAttribute;
295 }
296
setAttribute(const QString & attribute)297 void QgsPointCloudClassifiedRenderer::setAttribute( const QString &attribute )
298 {
299 mAttribute = attribute;
300 }
301
categories() const302 QgsPointCloudCategoryList QgsPointCloudClassifiedRenderer::categories() const
303 {
304 return mCategories;
305 }
306
setCategories(const QgsPointCloudCategoryList & categories)307 void QgsPointCloudClassifiedRenderer::setCategories( const QgsPointCloudCategoryList &categories )
308 {
309 mCategories = categories;
310 }
311
addCategory(const QgsPointCloudCategory & category)312 void QgsPointCloudClassifiedRenderer::addCategory( const QgsPointCloudCategory &category )
313 {
314 mCategories.append( category );
315 }
316
317