1 /***************************************************************************
2                          qgspointcloudlayer.cpp
3                          --------------------
4     begin                : October 2020
5     copyright            : (C) 2020 by Peter Petrik
6     email                : zilolv 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 "qgspointcloudlayer.h"
19 #include "qgspointcloudlayerrenderer.h"
20 #include "qgspointcloudindex.h"
21 #include "qgsrectangle.h"
22 #include "qgspointclouddataprovider.h"
23 #include "qgsproviderregistry.h"
24 #include "qgslogger.h"
25 #include "qgslayermetadataformatter.h"
26 #include "qgspointcloudrenderer.h"
27 #include "qgsruntimeprofiler.h"
28 #include "qgsapplication.h"
29 #include "qgspainting.h"
30 #include "qgspointcloudrendererregistry.h"
31 #include "qgspointcloudlayerelevationproperties.h"
32 #include "qgsmaplayerlegend.h"
33 #include "qgsxmlutils.h"
34 #include "qgsmaplayerfactory.h"
35 #include "qgsmaplayerutils.h"
36 
37 #include <QUrl>
38 
QgsPointCloudLayer(const QString & uri,const QString & baseName,const QString & providerLib,const QgsPointCloudLayer::LayerOptions & options)39 QgsPointCloudLayer::QgsPointCloudLayer( const QString &uri,
40                                         const QString &baseName,
41                                         const QString &providerLib,
42                                         const QgsPointCloudLayer::LayerOptions &options )
43   : QgsMapLayer( QgsMapLayerType::PointCloudLayer, baseName, uri )
44   , mElevationProperties( new QgsPointCloudLayerElevationProperties( this ) )
45 {
46   if ( !uri.isEmpty() && !providerLib.isEmpty() )
47   {
48     const QgsDataProvider::ProviderOptions providerOptions { options.transformContext };
49     QgsDataProvider::ReadFlags providerFlags = QgsDataProvider::ReadFlags();
50     if ( options.loadDefaultStyle )
51     {
52       providerFlags |= QgsDataProvider::FlagLoadDefaultStyle;
53     }
54     setDataSource( uri, baseName, providerLib, providerOptions, providerFlags );
55 
56     if ( !options.skipIndexGeneration && mDataProvider && mDataProvider->isValid() )
57       mDataProvider.get()->generateIndex();
58   }
59 
60   setLegend( QgsMapLayerLegend::defaultPointCloudLegend( this ) );
61 }
62 
63 QgsPointCloudLayer::~QgsPointCloudLayer() = default;
64 
clone() const65 QgsPointCloudLayer *QgsPointCloudLayer::clone() const
66 {
67   LayerOptions options;
68   options.loadDefaultStyle = false;
69   options.transformContext = transformContext();
70   options.skipCrsValidation = true;
71 
72   QgsPointCloudLayer *layer = new QgsPointCloudLayer( source(), name(), mProviderKey, options );
73   QgsMapLayer::clone( layer );
74 
75   if ( mRenderer )
76     layer->setRenderer( mRenderer->clone() );
77 
78   return layer;
79 }
80 
extent() const81 QgsRectangle QgsPointCloudLayer::extent() const
82 {
83   if ( !mDataProvider )
84     return QgsRectangle();
85 
86   return mDataProvider->extent();
87 }
88 
createMapRenderer(QgsRenderContext & rendererContext)89 QgsMapLayerRenderer *QgsPointCloudLayer::createMapRenderer( QgsRenderContext &rendererContext )
90 {
91   return new QgsPointCloudLayerRenderer( this, rendererContext );
92 }
93 
dataProvider()94 QgsPointCloudDataProvider *QgsPointCloudLayer::dataProvider()
95 {
96   return mDataProvider.get();
97 }
98 
dataProvider() const99 const QgsPointCloudDataProvider *QgsPointCloudLayer::dataProvider() const
100 {
101   return mDataProvider.get();
102 }
103 
readXml(const QDomNode & layerNode,QgsReadWriteContext & context)104 bool QgsPointCloudLayer::readXml( const QDomNode &layerNode, QgsReadWriteContext &context )
105 {
106   // create provider
107   const QDomNode pkeyNode = layerNode.namedItem( QStringLiteral( "provider" ) );
108   mProviderKey = pkeyNode.toElement().text();
109 
110   if ( !( mReadFlags & QgsMapLayer::FlagDontResolveLayers ) )
111   {
112     const QgsDataProvider::ProviderOptions providerOptions { context.transformContext() };
113     QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags();
114     // read extent
115     if ( mReadFlags & QgsMapLayer::FlagReadExtentFromXml )
116     {
117       const QDomNode extentNode = layerNode.namedItem( QStringLiteral( "extent" ) );
118       if ( !extentNode.isNull() )
119       {
120         // get the extent
121         const QgsRectangle mbr = QgsXmlUtils::readRectangle( extentNode.toElement() );
122 
123         // store the extent
124         setExtent( mbr );
125 
126         // skip get extent
127         flags |= QgsDataProvider::SkipGetExtent;
128       }
129     }
130     if ( mReadFlags & QgsMapLayer::FlagTrustLayerMetadata )
131     {
132       flags |= QgsDataProvider::FlagTrustDataSource;
133     }
134     setDataSource( mDataSource, mLayerName, mProviderKey, providerOptions, flags );
135   }
136 
137   if ( !isValid() )
138   {
139     return false;
140   }
141 
142   QString errorMsg;
143   if ( !readSymbology( layerNode, errorMsg, context ) )
144     return false;
145 
146   readStyleManager( layerNode );
147   return true;
148 }
149 
writeXml(QDomNode & layerNode,QDomDocument & doc,const QgsReadWriteContext & context) const150 bool QgsPointCloudLayer::writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const
151 {
152   QDomElement mapLayerNode = layerNode.toElement();
153   mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( QgsMapLayerType::PointCloudLayer ) );
154 
155   if ( mDataProvider )
156   {
157     QDomElement provider  = doc.createElement( QStringLiteral( "provider" ) );
158     const QDomText providerText = doc.createTextNode( providerType() );
159     provider.appendChild( providerText );
160     layerNode.appendChild( provider );
161   }
162 
163   writeStyleManager( layerNode, doc );
164 
165   QString errorMsg;
166   return writeSymbology( layerNode, doc, errorMsg, context );
167 }
168 
readSymbology(const QDomNode & node,QString & errorMessage,QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories)169 bool QgsPointCloudLayer::readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
170 {
171   const QDomElement elem = node.toElement();
172 
173   readCommonStyle( elem, context, categories );
174 
175   readStyle( node, errorMessage, context, categories );
176 
177   if ( categories.testFlag( CustomProperties ) )
178     readCustomProperties( node, QStringLiteral( "variable" ) );
179 
180   return true;
181 }
182 
readStyle(const QDomNode & node,QString &,QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories)183 bool QgsPointCloudLayer::readStyle( const QDomNode &node, QString &, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
184 {
185   bool result = true;
186 
187   if ( categories.testFlag( Symbology ) )
188   {
189     QDomElement rendererElement = node.firstChildElement( QStringLiteral( "renderer" ) );
190     if ( !rendererElement.isNull() )
191     {
192       std::unique_ptr< QgsPointCloudRenderer > r( QgsPointCloudRenderer::load( rendererElement, context ) );
193       if ( r )
194       {
195         setRenderer( r.release() );
196       }
197       else
198       {
199         result = false;
200       }
201     }
202     // make sure layer has a renderer - if none exists, fallback to a default renderer
203     if ( !mRenderer )
204     {
205       setRenderer( QgsApplication::pointCloudRendererRegistry()->defaultRenderer( mDataProvider.get() ) );
206     }
207   }
208 
209   if ( categories.testFlag( Symbology ) )
210   {
211     // get and set the blend mode if it exists
212     const QDomNode blendModeNode = node.namedItem( QStringLiteral( "blendMode" ) );
213     if ( !blendModeNode.isNull() )
214     {
215       const QDomElement e = blendModeNode.toElement();
216       setBlendMode( QgsPainting::getCompositionMode( static_cast< QgsPainting::BlendMode >( e.text().toInt() ) ) );
217     }
218   }
219 
220   // get and set the layer transparency and scale visibility if they exists
221   if ( categories.testFlag( Rendering ) )
222   {
223     const QDomNode layerOpacityNode = node.namedItem( QStringLiteral( "layerOpacity" ) );
224     if ( !layerOpacityNode.isNull() )
225     {
226       const QDomElement e = layerOpacityNode.toElement();
227       setOpacity( e.text().toDouble() );
228     }
229 
230     const bool hasScaleBasedVisibiliy { node.attributes().namedItem( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).nodeValue() == '1' };
231     setScaleBasedVisibility( hasScaleBasedVisibiliy );
232     bool ok;
233     const double maxScale { node.attributes().namedItem( QStringLiteral( "maxScale" ) ).nodeValue().toDouble( &ok ) };
234     if ( ok )
235     {
236       setMaximumScale( maxScale );
237     }
238     const double minScale { node.attributes().namedItem( QStringLiteral( "minScale" ) ).nodeValue().toDouble( &ok ) };
239     if ( ok )
240     {
241       setMinimumScale( minScale );
242     }
243   }
244   return result;
245 }
246 
writeSymbology(QDomNode & node,QDomDocument & doc,QString & errorMessage,const QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories) const247 bool QgsPointCloudLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage,
248     const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
249 {
250   Q_UNUSED( errorMessage )
251 
252   QDomElement elem = node.toElement();
253   writeCommonStyle( elem, doc, context, categories );
254 
255   ( void )writeStyle( node, doc, errorMessage, context, categories );
256 
257   return true;
258 }
259 
writeStyle(QDomNode & node,QDomDocument & doc,QString &,const QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories) const260 bool QgsPointCloudLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
261 {
262   QDomElement mapLayerNode = node.toElement();
263 
264   if ( categories.testFlag( Symbology ) )
265   {
266     if ( mRenderer )
267     {
268       const QDomElement rendererElement = mRenderer->save( doc, context );
269       node.appendChild( rendererElement );
270     }
271   }
272 
273   //save customproperties
274   if ( categories.testFlag( CustomProperties ) )
275   {
276     writeCustomProperties( node, doc );
277   }
278 
279   if ( categories.testFlag( Symbology ) )
280   {
281     // add the blend mode field
282     QDomElement blendModeElem  = doc.createElement( QStringLiteral( "blendMode" ) );
283     const QDomText blendModeText = doc.createTextNode( QString::number( QgsPainting::getBlendModeEnum( blendMode() ) ) );
284     blendModeElem.appendChild( blendModeText );
285     node.appendChild( blendModeElem );
286   }
287 
288   // add the layer opacity and scale visibility
289   if ( categories.testFlag( Rendering ) )
290   {
291     QDomElement layerOpacityElem = doc.createElement( QStringLiteral( "layerOpacity" ) );
292     const QDomText layerOpacityText = doc.createTextNode( QString::number( opacity() ) );
293     layerOpacityElem.appendChild( layerOpacityText );
294     node.appendChild( layerOpacityElem );
295 
296     mapLayerNode.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 );
297     mapLayerNode.setAttribute( QStringLiteral( "maxScale" ), maximumScale() );
298     mapLayerNode.setAttribute( QStringLiteral( "minScale" ), minimumScale() );
299   }
300 
301   return true;
302 }
303 
setTransformContext(const QgsCoordinateTransformContext & transformContext)304 void QgsPointCloudLayer::setTransformContext( const QgsCoordinateTransformContext &transformContext )
305 {
306   if ( mDataProvider )
307     mDataProvider->setTransformContext( transformContext );
308   invalidateWgs84Extent();
309 }
310 
setDataSourcePrivate(const QString & dataSource,const QString & baseName,const QString & provider,const QgsDataProvider::ProviderOptions & options,QgsDataProvider::ReadFlags flags)311 void QgsPointCloudLayer::setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider,
312     const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
313 {
314   if ( mDataProvider )
315   {
316     disconnect( mDataProvider.get(), &QgsPointCloudDataProvider::dataChanged, this, &QgsPointCloudLayer::dataChanged );
317     disconnect( mDataProvider.get(), &QgsPointCloudDataProvider::indexGenerationStateChanged, this, &QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged );
318   }
319 
320   setName( baseName );
321   mProviderKey = provider;
322   mDataSource = dataSource;
323 
324   mDataProvider.reset( qobject_cast<QgsPointCloudDataProvider *>( QgsProviderRegistry::instance()->createProvider( provider, dataSource, options, flags ) ) );
325   if ( !mDataProvider )
326   {
327     QgsDebugMsg( QStringLiteral( "Unable to get point cloud data provider" ) );
328     setValid( false );
329     return;
330   }
331 
332   mDataProvider->setParent( this );
333   QgsDebugMsgLevel( QStringLiteral( "Instantiated the point cloud data provider plugin" ), 2 );
334 
335   setValid( mDataProvider->isValid() );
336   if ( !isValid() )
337   {
338     QgsDebugMsg( QStringLiteral( "Invalid point cloud provider plugin %1" ).arg( QString( mDataSource.toUtf8() ) ) );
339     return;
340   }
341 
342   connect( mDataProvider.get(), &QgsPointCloudDataProvider::indexGenerationStateChanged, this, &QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged );
343   connect( mDataProvider.get(), &QgsPointCloudDataProvider::dataChanged, this, &QgsPointCloudLayer::dataChanged );
344 
345   // Load initial extent, crs and renderer
346   setCrs( mDataProvider->crs() );
347   if ( !( flags & QgsDataProvider::SkipGetExtent ) )
348   {
349     setExtent( mDataProvider->extent() );
350   }
351 
352   bool loadDefaultStyleFlag = false;
353   if ( flags & QgsDataProvider::FlagLoadDefaultStyle )
354   {
355     loadDefaultStyleFlag = true;
356   }
357 
358   if ( !mRenderer || loadDefaultStyleFlag )
359   {
360     std::unique_ptr< QgsScopedRuntimeProfile > profile;
361     if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
362       profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Load layer style" ), QStringLiteral( "projectload" ) );
363 
364     bool defaultLoadedFlag = false;
365 
366     if ( loadDefaultStyleFlag && isSpatial() && mDataProvider->capabilities() & QgsPointCloudDataProvider::CreateRenderer )
367     {
368       // first try to create a renderer directly from the data provider
369       std::unique_ptr< QgsPointCloudRenderer > defaultRenderer( mDataProvider->createRenderer() );
370       if ( defaultRenderer )
371       {
372         defaultLoadedFlag = true;
373         setRenderer( defaultRenderer.release() );
374       }
375     }
376 
377     if ( !defaultLoadedFlag && loadDefaultStyleFlag )
378     {
379       loadDefaultStyle( defaultLoadedFlag );
380     }
381 
382     if ( !defaultLoadedFlag )
383     {
384       // all else failed, create default renderer
385       setRenderer( QgsApplication::pointCloudRendererRegistry()->defaultRenderer( mDataProvider.get() ) );
386     }
387   }
388 }
389 
encodedSource(const QString & source,const QgsReadWriteContext & context) const390 QString QgsPointCloudLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
391 {
392   QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( providerType(), source );
393   if ( parts.contains( QStringLiteral( "path" ) ) )
394   {
395     parts.insert( QStringLiteral( "path" ), context.pathResolver().writePath( parts.value( QStringLiteral( "path" ) ).toString() ) );
396     return QgsProviderRegistry::instance()->encodeUri( providerType(), parts );
397   }
398   else
399   {
400     return source;
401   }
402 }
403 
decodedSource(const QString & source,const QString & dataProvider,const QgsReadWriteContext & context) const404 QString QgsPointCloudLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const
405 {
406   QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( dataProvider, source );
407   if ( parts.contains( QStringLiteral( "path" ) ) )
408   {
409     parts.insert( QStringLiteral( "path" ), context.pathResolver().readPath( parts.value( QStringLiteral( "path" ) ).toString() ) );
410     return QgsProviderRegistry::instance()->encodeUri( dataProvider, parts );
411   }
412   else
413   {
414     return source;
415   }
416 }
417 
onPointCloudIndexGenerationStateChanged(QgsPointCloudDataProvider::PointCloudIndexGenerationState state)418 void QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged( QgsPointCloudDataProvider::PointCloudIndexGenerationState state )
419 {
420   if ( state == QgsPointCloudDataProvider::Indexed )
421   {
422     mDataProvider.get()->loadIndex();
423     if ( mRenderer->type() == QLatin1String( "extent" ) )
424     {
425       setRenderer( QgsApplication::pointCloudRendererRegistry()->defaultRenderer( mDataProvider.get() ) );
426     }
427     triggerRepaint();
428 
429     emit rendererChanged();
430   }
431 }
432 
loadDefaultStyle(bool & resultFlag)433 QString QgsPointCloudLayer::loadDefaultStyle( bool &resultFlag )
434 {
435   if ( mDataProvider->capabilities() & QgsPointCloudDataProvider::CreateRenderer )
436   {
437     // first try to create a renderer directly from the data provider
438     std::unique_ptr< QgsPointCloudRenderer > defaultRenderer( mDataProvider->createRenderer() );
439     if ( defaultRenderer )
440     {
441       resultFlag = true;
442       setRenderer( defaultRenderer.release() );
443       return QString();
444     }
445   }
446 
447   return QgsMapLayer::loadDefaultStyle( resultFlag );
448 }
449 
htmlMetadata() const450 QString QgsPointCloudLayer::htmlMetadata() const
451 {
452   const QgsLayerMetadataFormatter htmlFormatter( metadata() );
453   QString myMetadata = QStringLiteral( "<html>\n<body>\n" );
454 
455   myMetadata += generalHtmlMetadata();
456 
457   // Begin Provider section
458   myMetadata += QStringLiteral( "<h1>" ) + tr( "Information from provider" ) + QStringLiteral( "</h1>\n<hr>\n" );
459   myMetadata += QLatin1String( "<table class=\"list-view\">\n" );
460 
461   // Extent
462   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Extent" ) + QStringLiteral( "</td><td>" ) + extent().toString() + QStringLiteral( "</td></tr>\n" );
463 
464   // feature count
465   QLocale locale = QLocale();
466   locale.setNumberOptions( locale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
467   const qint64 pointCount = mDataProvider ? mDataProvider->pointCount() : -1;
468   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
469                 + tr( "Point count" ) + QStringLiteral( "</td><td>" )
470                 + ( pointCount < 0 ? tr( "unknown" ) : locale.toString( static_cast<qlonglong>( pointCount ) ) )
471                 + QStringLiteral( "</td></tr>\n" );
472   myMetadata += QLatin1String( "</table>\n<br><br>" );
473 
474   // CRS
475   myMetadata += crsHtmlMetadata();
476 
477   // provider metadata section
478   myMetadata += QStringLiteral( "<h1>" ) + tr( "Metadata" ) + QStringLiteral( "</h1>\n<hr>\n" ) + QStringLiteral( "<table class=\"list-view\">\n" );
479   const QVariantMap originalMetadata = mDataProvider ? mDataProvider->originalMetadata() : QVariantMap();
480 
481   if ( originalMetadata.value( QStringLiteral( "creation_year" ) ).toInt() > 0 && originalMetadata.contains( QStringLiteral( "creation_doy" ) ) )
482   {
483     QDate creationDate( originalMetadata.value( QStringLiteral( "creation_year" ) ).toInt(), 1, 1 );
484     creationDate = creationDate.addDays( originalMetadata.value( QStringLiteral( "creation_doy" ) ).toInt() );
485 
486     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
487                   + tr( "Creation date" ) + QStringLiteral( "</td><td>" )
488                   + creationDate.toString( Qt::ISODate )
489                   + QStringLiteral( "</td></tr>\n" );
490   }
491   if ( originalMetadata.contains( QStringLiteral( "major_version" ) ) && originalMetadata.contains( QStringLiteral( "minor_version" ) ) )
492   {
493     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
494                   + tr( "Version" ) + QStringLiteral( "</td><td>" )
495                   + QStringLiteral( "%1.%2" ).arg( originalMetadata.value( QStringLiteral( "major_version" ) ).toString(),
496                       originalMetadata.value( QStringLiteral( "minor_version" ) ).toString() )
497                   + QStringLiteral( "</td></tr>\n" );
498   }
499 
500   if ( !originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toString().isEmpty() )
501   {
502     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
503                   + tr( "Data format" ) + QStringLiteral( "</td><td>" )
504                   + QStringLiteral( "%1 (%2)" ).arg( QgsPointCloudDataProvider::translatedDataFormatIds().value( originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toInt() ),
505                       originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toString() ).trimmed()
506                   + QStringLiteral( "</td></tr>\n" );
507   }
508 
509   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
510                 + tr( "Scale X" ) + QStringLiteral( "</td><td>" )
511                 + QString::number( originalMetadata.value( QStringLiteral( "scale_x" ) ).toDouble() )
512                 + QStringLiteral( "</td></tr>\n" );
513   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
514                 + tr( "Scale Y" ) + QStringLiteral( "</td><td>" )
515                 + QString::number( originalMetadata.value( QStringLiteral( "scale_y" ) ).toDouble() )
516                 + QStringLiteral( "</td></tr>\n" );
517   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
518                 + tr( "Scale Z" ) + QStringLiteral( "</td><td>" )
519                 + QString::number( originalMetadata.value( QStringLiteral( "scale_z" ) ).toDouble() )
520                 + QStringLiteral( "</td></tr>\n" );
521 
522   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
523                 + tr( "Offset X" ) + QStringLiteral( "</td><td>" )
524                 + QString::number( originalMetadata.value( QStringLiteral( "offset_x" ) ).toDouble() )
525                 + QStringLiteral( "</td></tr>\n" );
526   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
527                 + tr( "Offset Y" ) + QStringLiteral( "</td><td>" )
528                 + QString::number( originalMetadata.value( QStringLiteral( "offset_y" ) ).toDouble() )
529                 + QStringLiteral( "</td></tr>\n" );
530   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
531                 + tr( "Offset Z" ) + QStringLiteral( "</td><td>" )
532                 + QString::number( originalMetadata.value( QStringLiteral( "offset_z" ) ).toDouble() )
533                 + QStringLiteral( "</td></tr>\n" );
534 
535   if ( !originalMetadata.value( QStringLiteral( "project_id" ) ).toString().isEmpty() )
536   {
537     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
538                   + tr( "Project ID" ) + QStringLiteral( "</td><td>" )
539                   + originalMetadata.value( QStringLiteral( "project_id" ) ).toString()
540                   + QStringLiteral( "</td></tr>\n" );
541   }
542 
543   if ( !originalMetadata.value( QStringLiteral( "system_id" ) ).toString().isEmpty() )
544   {
545     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
546                   + tr( "System ID" ) + QStringLiteral( "</td><td>" )
547                   + originalMetadata.value( QStringLiteral( "system_id" ) ).toString()
548                   + QStringLiteral( "</td></tr>\n" );
549   }
550 
551   if ( !originalMetadata.value( QStringLiteral( "software_id" ) ).toString().isEmpty() )
552   {
553     myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
554                   + tr( "Software ID" ) + QStringLiteral( "</td><td>" )
555                   + originalMetadata.value( QStringLiteral( "software_id" ) ).toString()
556                   + QStringLiteral( "</td></tr>\n" );
557   }
558 
559   // End Provider section
560   myMetadata += QLatin1String( "</table>\n<br><br>" );
561 
562   // identification section
563   myMetadata += QStringLiteral( "<h1>" ) + tr( "Identification" ) + QStringLiteral( "</h1>\n<hr>\n" );
564   myMetadata += htmlFormatter.identificationSectionHtml( );
565   myMetadata += QLatin1String( "<br><br>\n" );
566 
567   // extent section
568   myMetadata += QStringLiteral( "<h1>" ) + tr( "Extent" ) + QStringLiteral( "</h1>\n<hr>\n" );
569   myMetadata += htmlFormatter.extentSectionHtml( isSpatial() );
570   myMetadata += QLatin1String( "<br><br>\n" );
571 
572   // Start the Access section
573   myMetadata += QStringLiteral( "<h1>" ) + tr( "Access" ) + QStringLiteral( "</h1>\n<hr>\n" );
574   myMetadata += htmlFormatter.accessSectionHtml( );
575   myMetadata += QLatin1String( "<br><br>\n" );
576 
577   // Attributes section
578   myMetadata += QStringLiteral( "<h1>" ) + tr( "Attributes" ) + QStringLiteral( "</h1>\n<hr>\n<table class=\"list-view\">\n" );
579 
580   const QgsPointCloudAttributeCollection attrs = attributes();
581 
582   // count attributes
583   myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Count" ) + QStringLiteral( "</td><td>" ) + QString::number( attrs.count() ) + QStringLiteral( "</td></tr>\n" );
584 
585   myMetadata += QLatin1String( "</table>\n<br><table width=\"100%\" class=\"tabular-view\">\n" );
586   myMetadata += QLatin1String( "<tr><th>" ) + tr( "Attribute" ) + QLatin1String( "</th><th>" ) + tr( "Type" ) + QLatin1String( "</th></tr>\n" );
587 
588   for ( int i = 0; i < attrs.count(); ++i )
589   {
590     const QgsPointCloudAttribute attribute = attrs.at( i );
591     QString rowClass;
592     if ( i % 2 )
593       rowClass = QStringLiteral( "class=\"odd-row\"" );
594     myMetadata += QLatin1String( "<tr " ) + rowClass + QLatin1String( "><td>" ) + attribute.name() + QLatin1String( "</td><td>" ) + attribute.displayType() + QLatin1String( "</td></tr>\n" );
595   }
596 
597   //close field list
598   myMetadata += QLatin1String( "</table>\n<br><br>" );
599 
600 
601   // Start the contacts section
602   myMetadata += QStringLiteral( "<h1>" ) + tr( "Contacts" ) + QStringLiteral( "</h1>\n<hr>\n" );
603   myMetadata += htmlFormatter.contactsSectionHtml( );
604   myMetadata += QLatin1String( "<br><br>\n" );
605 
606   // Start the links section
607   myMetadata += QStringLiteral( "<h1>" ) + tr( "Links" ) + QStringLiteral( "</h1>\n<hr>\n" );
608   myMetadata += htmlFormatter.linksSectionHtml( );
609   myMetadata += QLatin1String( "<br><br>\n" );
610 
611   // Start the history section
612   myMetadata += QStringLiteral( "<h1>" ) + tr( "History" ) + QStringLiteral( "</h1>\n<hr>\n" );
613   myMetadata += htmlFormatter.historySectionHtml( );
614   myMetadata += QLatin1String( "<br><br>\n" );
615 
616   myMetadata += QLatin1String( "\n</body>\n</html>\n" );
617   return myMetadata;
618 }
619 
elevationProperties()620 QgsMapLayerElevationProperties *QgsPointCloudLayer::elevationProperties()
621 {
622   return mElevationProperties;
623 }
624 
attributes() const625 QgsPointCloudAttributeCollection QgsPointCloudLayer::attributes() const
626 {
627   return mDataProvider ? mDataProvider->attributes() : QgsPointCloudAttributeCollection();
628 }
629 
pointCount() const630 qint64 QgsPointCloudLayer::pointCount() const
631 {
632   return mDataProvider ? mDataProvider->pointCount() : 0;
633 }
634 
renderer()635 QgsPointCloudRenderer *QgsPointCloudLayer::renderer()
636 {
637   return mRenderer.get();
638 }
639 
renderer() const640 const QgsPointCloudRenderer *QgsPointCloudLayer::renderer() const
641 {
642   return mRenderer.get();
643 }
644 
setRenderer(QgsPointCloudRenderer * renderer)645 void QgsPointCloudLayer::setRenderer( QgsPointCloudRenderer *renderer )
646 {
647   if ( renderer == mRenderer.get() )
648     return;
649 
650   mRenderer.reset( renderer );
651   emit rendererChanged();
652   emitStyleChanged();
653 }
654