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