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