QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsrasterlayertemporalpropertieswidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrasterlayertemporalpropertieswidget.cpp
3 ------------------------------
4 begin : January 2020
5 copyright : (C) 2020 by Samweli Mwakisambwe
6 email : samweli at kartoza 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
20#include "qgsrasterlayer.h"
23#include "qgsdatetimeedit.h"
26#include <QMenu>
27#include <QAction>
28
30 : QWidget( parent )
31 , mLayer( layer )
32{
33 Q_ASSERT( mLayer );
34 setupUi( this );
35
36 // make a useful default expression for per band ranges, just to give users some hints about how to do this...
37 mFixedRangeLowerExpression = QStringLiteral( "make_datetime(%1,1,1,0,0,0) + make_interval(days:=@band)" ).arg( QDate::currentDate().year() );
38 mFixedRangeUpperExpression = QStringLiteral( "make_datetime(%1,1,1,23,59,59) + make_interval(days:=@band)" ).arg( QDate::currentDate().year() );
39
40 mExtraWidgetLayout = new QVBoxLayout();
41 mExtraWidgetLayout->setContentsMargins( 0, 0, 0, 0 );
42 mExtraWidgetLayout->addStretch();
43 mExtraWidgetContainer->setLayout( mExtraWidgetLayout );
44
46 {
47 mModeComboBox->addItem( tr( "Automatic" ), QVariant::fromValue( Qgis::RasterTemporalMode::TemporalRangeFromDataProvider ) );
48 }
49 mModeComboBox->addItem( tr( "Fixed Time Range" ), QVariant::fromValue( Qgis::RasterTemporalMode::FixedTemporalRange ) );
50 mModeComboBox->addItem( tr( "Fixed Time Range Per Band" ), QVariant::fromValue( Qgis::RasterTemporalMode::FixedRangePerBand ) );
51 mModeComboBox->addItem( tr( "Redraw Layer Only" ), QVariant::fromValue( Qgis::RasterTemporalMode::RedrawLayerOnly ) );
52
53 mStackedWidget->setSizeMode( QgsStackedWidget::SizeMode::CurrentPageOnly );
54
55 mFixedRangePerBandModel = new QgsRasterBandFixedTemporalRangeModel( this );
56 mBandRangesTable->verticalHeader()->setVisible( false );
57 mBandRangesTable->setModel( mFixedRangePerBandModel );
58 QgsFixedTemporalRangeDelegate *tableDelegate = new QgsFixedTemporalRangeDelegate( mBandRangesTable );
59 mBandRangesTable->setItemDelegateForColumn( 1, tableDelegate );
60 mBandRangesTable->setItemDelegateForColumn( 2, tableDelegate );
61
62 connect( mModeComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsRasterLayerTemporalPropertiesWidget::modeChanged );
63
64 connect( mTemporalGroupBox, &QGroupBox::toggled, this, &QgsRasterLayerTemporalPropertiesWidget::temporalGroupBoxChecked );
65
66 mStartTemporalDateTimeEdit->setDisplayFormat( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
67 mEndTemporalDateTimeEdit->setDisplayFormat( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
68
69 QMenu *calculateFixedRangePerBandMenu = new QMenu( mCalculateFixedRangePerBandButton );
70 mCalculateFixedRangePerBandButton->setMenu( calculateFixedRangePerBandMenu );
71 mCalculateFixedRangePerBandButton->setPopupMode( QToolButton::InstantPopup );
72 QAction *calculateLowerAction = new QAction( "Calculate Beginning by Expression…", calculateFixedRangePerBandMenu );
73 calculateFixedRangePerBandMenu->addAction( calculateLowerAction );
74 connect( calculateLowerAction, &QAction::triggered, this, [this]
75 {
76 calculateRangeByExpression( false );
77 } );
78 QAction *calculateUpperAction = new QAction( "Calculate End by Expression…", calculateFixedRangePerBandMenu );
79 calculateFixedRangePerBandMenu->addAction( calculateUpperAction );
80 connect( calculateUpperAction, &QAction::triggered, this, [this]
81 {
82 calculateRangeByExpression( true );
83 } );
84
85
87}
88
90{
91 mLayer->temporalProperties()->setIsActive( mTemporalGroupBox->isChecked() );
92
93 QgsRasterLayerTemporalProperties *temporalProperties = qobject_cast< QgsRasterLayerTemporalProperties * >( mLayer->temporalProperties() );
94
95 temporalProperties->setMode( mModeComboBox->currentData().value< Qgis::RasterTemporalMode >() );
96
97 const QgsDateTimeRange normalRange = QgsDateTimeRange( mStartTemporalDateTimeEdit->dateTime(),
98 mEndTemporalDateTimeEdit->dateTime() );
99 temporalProperties->setFixedTemporalRange( normalRange );
100
101 temporalProperties->setFixedRangePerBand( mFixedRangePerBandModel->rangeData() );
102
103 for ( QgsMapLayerConfigWidget *widget : std::as_const( mExtraWidgets ) )
104 {
105 widget->apply();
106 }
107}
108
110{
111 const QgsRasterLayerTemporalProperties *temporalProperties = qobject_cast< const QgsRasterLayerTemporalProperties * >( mLayer->temporalProperties() );
112 mModeComboBox->setCurrentIndex( mModeComboBox->findData( QVariant::fromValue( temporalProperties->mode() ) ) );
113 switch ( temporalProperties->mode() )
114 {
116 mStackedWidget->setCurrentWidget( mPageAutomatic );
117 break;
119 mStackedWidget->setCurrentWidget( mPageFixedRange );
120 break;
122 mStackedWidget->setCurrentWidget( mPageRedrawOnly );
123 break;
125 mStackedWidget->setCurrentWidget( mPageFixedRangePerBand );
126 break;
127 }
128
129 mStartTemporalDateTimeEdit->setDateTime( temporalProperties->fixedTemporalRange().begin() );
130 mEndTemporalDateTimeEdit->setDateTime( temporalProperties->fixedTemporalRange().end() );
131
132 mFixedRangePerBandModel->setLayerData( mLayer, temporalProperties->fixedRangePerBand() );
133 mBandRangesTable->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch );
134 mBandRangesTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch );
135 mBandRangesTable->horizontalHeader()->setSectionResizeMode( 2, QHeaderView::Stretch );
136
137 mTemporalGroupBox->setChecked( temporalProperties->isActive() );
138
139 for ( QgsMapLayerConfigWidget *widget : std::as_const( mExtraWidgets ) )
140 {
141 widget->syncToLayer( mLayer );
142 }
143}
144
146{
147 mExtraWidgets << widget;
148 mExtraWidgetLayout->insertWidget( mExtraWidgetLayout->count() - 1, widget );
149}
150
151void QgsRasterLayerTemporalPropertiesWidget::temporalGroupBoxChecked( bool checked )
152{
153 for ( QgsMapLayerConfigWidget *widget : std::as_const( mExtraWidgets ) )
154 {
155 widget->emit dynamicTemporalControlToggled( checked );
156 }
157}
158
159void QgsRasterLayerTemporalPropertiesWidget::modeChanged()
160{
161 if ( mModeComboBox->currentData().isValid() )
162 {
163 switch ( mModeComboBox->currentData().value< Qgis::RasterTemporalMode >() )
164 {
166 mStackedWidget->setCurrentWidget( mPageAutomatic );
167 break;
169 mStackedWidget->setCurrentWidget( mPageFixedRange );
170 break;
172 mStackedWidget->setCurrentWidget( mPageRedrawOnly );
173 break;
175 mStackedWidget->setCurrentWidget( mPageFixedRangePerBand );
176 break;
177 }
178 }
179}
180
181void QgsRasterLayerTemporalPropertiesWidget::calculateRangeByExpression( bool isUpper )
182{
183 QgsExpressionContext expressionContext;
185 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band" ), 1, true, false, tr( "Band number" ) ) );
186 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band_name" ), mLayer->dataProvider()->displayBandName( 1 ), true, false, tr( "Band name" ) ) );
187 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band_description" ), mLayer->dataProvider()->bandDescription( 1 ), true, false, tr( "Band description" ) ) );
188
189 expressionContext.appendScope( bandScope );
190 expressionContext.setHighlightedVariables( { QStringLiteral( "band" ), QStringLiteral( "band_name" ), QStringLiteral( "band_description" )} );
191
192 QgsExpressionBuilderDialog dlg = QgsExpressionBuilderDialog( nullptr, isUpper ? mFixedRangeUpperExpression : mFixedRangeLowerExpression, this, QStringLiteral( "generic" ), expressionContext );
193 dlg.setExpectedOutputFormat( !isUpper ? tr( "Temporal range start date / time" ) : tr( "Temporal range end date / time" ) );
194
195 QList<QPair<QString, QVariant> > bandChoices;
196 for ( int band = 1; band <= mLayer->bandCount(); ++band )
197 {
198 bandChoices << qMakePair( mLayer->dataProvider()->displayBandName( band ), band );
199 }
200 dlg.expressionBuilder()->setCustomPreviewGenerator( tr( "Band" ), bandChoices, [this]( const QVariant & value )-> QgsExpressionContext
201 {
202 return createExpressionContextForBand( value.toInt() );
203 } );
204
205 if ( dlg.exec() )
206 {
207 if ( isUpper )
208 mFixedRangeUpperExpression = dlg.expressionText();
209 else
210 mFixedRangeLowerExpression = dlg.expressionText();
211
212 QgsExpression exp( dlg.expressionText() );
213 exp.prepare( &expressionContext );
214 for ( int band = 1; band <= mLayer->bandCount(); ++band )
215 {
216 bandScope->setVariable( QStringLiteral( "band" ), band );
217 bandScope->setVariable( QStringLiteral( "band_name" ), mLayer->dataProvider()->displayBandName( band ) );
218 bandScope->setVariable( QStringLiteral( "band_description" ), mLayer->dataProvider()->bandDescription( band ) );
219
220 const QVariant res = exp.evaluate( &expressionContext );
221 mFixedRangePerBandModel->setData( mFixedRangePerBandModel->index( band - 1, isUpper ? 2 : 1 ), res, Qt::EditRole );
222 }
223 }
224}
225
226QgsExpressionContext QgsRasterLayerTemporalPropertiesWidget::createExpressionContextForBand( int band ) const
227{
228 QgsExpressionContext context;
231 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band" ), band, true, false, tr( "Band number" ) ) );
232 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band_name" ), ( mLayer && mLayer->dataProvider() ) ? mLayer->dataProvider()->displayBandName( band ) : QString(), true, false, tr( "Band name" ) ) );
233 bandScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "band_description" ), ( mLayer && mLayer->dataProvider() ) ? mLayer->dataProvider()->bandDescription( band ) : QString(), true, false, tr( "Band description" ) ) );
234 context.appendScope( bandScope );
235 context.setHighlightedVariables( { QStringLiteral( "band" ), QStringLiteral( "band_name" ), QStringLiteral( "band_description" )} );
236 return context;
237}
238
240
241//
242// QgsRasterBandFixedTemporalRangeModel
243//
244
245QgsRasterBandFixedTemporalRangeModel::QgsRasterBandFixedTemporalRangeModel( QObject *parent )
246 : QAbstractItemModel( parent )
247{
248
249}
250
251int QgsRasterBandFixedTemporalRangeModel::columnCount( const QModelIndex & ) const
252{
253 return 3;
254}
255
256int QgsRasterBandFixedTemporalRangeModel::rowCount( const QModelIndex &parent ) const
257{
258 if ( parent.isValid() )
259 return 0;
260 return mBandCount;
261}
262
263QModelIndex QgsRasterBandFixedTemporalRangeModel::index( int row, int column, const QModelIndex &parent ) const
264{
265 if ( hasIndex( row, column, parent ) )
266 {
267 return createIndex( row, column, row );
268 }
269
270 return QModelIndex();
271}
272
273QModelIndex QgsRasterBandFixedTemporalRangeModel::parent( const QModelIndex &child ) const
274{
275 Q_UNUSED( child )
276 return QModelIndex();
277}
278
279Qt::ItemFlags QgsRasterBandFixedTemporalRangeModel::flags( const QModelIndex &index ) const
280{
281 if ( !index.isValid() )
282 return Qt::ItemFlags();
283
284 if ( index.row() < 0 || index.row() >= mBandCount || index.column() < 0 || index.column() >= columnCount() )
285 return Qt::ItemFlags();
286
287 switch ( index.column() )
288 {
289 case 0:
290 return Qt::ItemFlag::ItemIsEnabled;
291 case 1:
292 case 2:
293 return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsEditable | Qt::ItemFlag::ItemIsSelectable;
294 default:
295 break;
296 }
297
298 return Qt::ItemFlags();
299}
300
301QVariant QgsRasterBandFixedTemporalRangeModel::data( const QModelIndex &index, int role ) const
302{
303 if ( !index.isValid() )
304 return QVariant();
305
306 if ( index.row() < 0 || index.row() >= mBandCount || index.column() < 0 || index.column() >= columnCount() )
307 return QVariant();
308
309 const int band = index.row() + 1;
310 const QgsDateTimeRange range = mRanges.value( band );
311
312 switch ( role )
313 {
314 case Qt::DisplayRole:
315 case Qt::EditRole:
316 case Qt::ToolTipRole:
317 {
318 switch ( index.column() )
319 {
320 case 0:
321 return mBandNames.value( band, QString::number( band ) );
322
323 case 1:
324 return range.begin().isValid() ? range.begin() : QVariant();
325
326 case 2:
327 return range.end().isValid() ? range.end() : QVariant();
328
329 default:
330 break;
331 }
332 break;
333 }
334
335 case Qt::TextAlignmentRole:
336 {
337 switch ( index.column() )
338 {
339 case 0:
340 return static_cast<Qt::Alignment::Int>( Qt::AlignLeft | Qt::AlignVCenter );
341
342 case 1:
343 case 2:
344 return static_cast<Qt::Alignment::Int>( Qt::AlignRight | Qt::AlignVCenter );
345 default:
346 break;
347 }
348 break;
349 }
350
351 default:
352 break;
353 }
354 return QVariant();
355}
356
357QVariant QgsRasterBandFixedTemporalRangeModel::headerData( int section, Qt::Orientation orientation, int role ) const
358{
359 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
360 {
361 switch ( section )
362 {
363 case 0:
364 return tr( "Band" );
365 case 1:
366 return tr( "Begin" );
367 case 2:
368 return tr( "End" );
369 default:
370 break;
371 }
372 }
373 return QAbstractItemModel::headerData( section, orientation, role );
374}
375
376bool QgsRasterBandFixedTemporalRangeModel::setData( const QModelIndex &index, const QVariant &value, int role )
377{
378 if ( !index.isValid() )
379 return false;
380
381 if ( index.row() > mBandCount || index.row() < 0 )
382 return false;
383
384 const int band = index.row() + 1;
385 const QgsDateTimeRange range = mRanges.value( band );
386
387 switch ( role )
388 {
389 case Qt::EditRole:
390 {
391 const QDateTime newValue = value.toDateTime();
392 if ( !newValue.isValid() )
393 return false;
394
395 switch ( index.column() )
396 {
397 case 1:
398 {
399 mRanges[band] = QgsDateTimeRange( newValue, range.end(), range.includeBeginning(), range.includeEnd() );
400 emit dataChanged( index, index, QVector<int>() << role );
401 break;
402 }
403
404 case 2:
405 mRanges[band] = QgsDateTimeRange( range.begin(), newValue, range.includeBeginning(), range.includeEnd() );
406 emit dataChanged( index, index, QVector<int>() << role );
407 break;
408
409 default:
410 break;
411 }
412 return true;
413 }
414
415 default:
416 break;
417 }
418
419 return false;
420}
421
422void QgsRasterBandFixedTemporalRangeModel::setLayerData( QgsRasterLayer *layer, const QMap<int, QgsDateTimeRange> &ranges )
423{
424 beginResetModel();
425
426 mBandCount = layer->bandCount();
427 mRanges = ranges;
428
429 mBandNames.clear();
430 for ( int band = 1; band <= mBandCount; ++band )
431 {
432 mBandNames[band] = layer->dataProvider()->displayBandName( band );
433 }
434
435 endResetModel();
436}
437
438//
439// QgsFixedTemporalRangeDelegate
440//
441
442QgsFixedTemporalRangeDelegate::QgsFixedTemporalRangeDelegate( QObject *parent )
443 : QStyledItemDelegate( parent )
444{
445
446}
447
448QWidget *QgsFixedTemporalRangeDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
449{
450 QgsDateTimeEdit *editor = new QgsDateTimeEdit( parent );
451 editor->setAllowNull( true );
452 return editor;
453}
454
455void QgsFixedTemporalRangeDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
456{
457 if ( QgsDateTimeEdit *dateTimeEdit = qobject_cast< QgsDateTimeEdit * >( editor ) )
458 {
459 model->setData( index, dateTimeEdit->dateTime() );
460 }
461}
RasterTemporalMode
Raster layer temporal modes.
Definition: qgis.h:2143
@ RedrawLayerOnly
Redraw the layer when temporal range changes, but don't apply any filtering. Useful when raster symbo...
@ FixedRangePerBand
Layer has a fixed temporal range per band (since QGIS 3.38)
@ TemporalRangeFromDataProvider
Mode when raster layer delegates temporal range handling to the dataprovider.
@ FixedTemporalRange
Mode when temporal properties have fixed start and end datetimes.
bool hasTemporalCapabilities() const
Returns true if the provider has temporal capabilities available.
The QgsDateTimeEdit class is a QDateTimeEdit with the capability of setting/reading null date/times.
void setAllowNull(bool allowNull)
Determines if the widget allows setting null date/time.
A generic dialog for building expression strings.
void setExpectedOutputFormat(const QString &expected)
Set the expected format string, which is shown in the dialog.
QgsExpressionBuilderWidget * expressionBuilder()
The builder widget that is used by the dialog.
void setCustomPreviewGenerator(const QString &label, const QList< QPair< QString, QVariant > > &choices, const std::function< QgsExpressionContext(const QVariant &) > &previewContextGenerator)
Sets the widget to run using a custom preview generator.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
A panel widget that can be shown in the map style dock.
QgsRasterDataProviderTemporalCapabilities * temporalCapabilities() override
Returns the provider's temporal capabilities.
virtual QString bandDescription(int bandNumber)
Returns the description for band bandNumber, or an empty string if the band is not valid or has not d...
QString displayBandName(int bandNumber) const
Generates a friendly, descriptive name for the specified bandNumber.
void addWidget(QgsMapLayerConfigWidget *widget)
Adds a child widget to the properties widget.
void syncToLayer()
Updates the widget state to match the current layer state.
void saveTemporalProperties()
Save widget temporal properties inputs.
QgsRasterLayerTemporalPropertiesWidget(QWidget *parent=nullptr, QgsRasterLayer *layer=nullptr)
Constructor for QgsRasterLayerTemporalPropertiesWidget.
Implementation of map layer temporal properties for raster layers.
Qgis::RasterTemporalMode mode() const
Returns the temporal properties mode.
void setMode(Qgis::RasterTemporalMode mode)
Sets the temporal properties mode.
void setFixedTemporalRange(const QgsDateTimeRange &range)
Sets a temporal range to apply to the whole layer.
void setFixedRangePerBand(const QMap< int, QgsDateTimeRange > &ranges)
Sets the fixed temporal range for each band.
QMap< int, QgsDateTimeRange > fixedRangePerBand() const
Returns the fixed temporal range for each band.
const QgsDateTimeRange & fixedTemporalRange() const
Returns the fixed temporal range for the layer.
Represents a raster layer.
int bandCount() const
Returns the number of bands in this layer.
QgsMapLayerTemporalProperties * temporalProperties() override
Returns the layer's temporal properties.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
@ CurrentPageOnly
Only the size of the current page is considered when calculating the stacked widget size.
bool isActive() const
Returns true if the temporal property is active.
void setIsActive(bool active)
Sets whether the temporal property is active.
T begin() const
Returns the beginning of the range.
Definition: qgsrange.h:444
T end() const
Returns the upper bound of the range.
Definition: qgsrange.h:451
bool includeEnd() const
Returns true if the end is inclusive, or false if the end is exclusive.
Definition: qgsrange.h:466
bool includeBeginning() const
Returns true if the beginning is inclusive, or false if the beginning is exclusive.
Definition: qgsrange.h:459
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition: qgsrange.h:742
Single variable definition for use within a QgsExpressionContextScope.