• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MerginMaps / input / 6655250127

26 Oct 2023 02:02PM UTC coverage: 62.234% (-0.07%) from 62.308%
6655250127

push

github

web-flow
Merge pull request #2867 from MerginMaps/other_elems

HTML, Text, Spacer "other form widgets" + "show label"

7483 of 12024 relevant lines covered (62.23%)

122.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.38
/app/inpututils.cpp
1
/***************************************************************************
2
 *                                                                         *
3
 *   This program is free software; you can redistribute it and/or modify  *
4
 *   it under the terms of the GNU General Public License as published by  *
5
 *   the Free Software Foundation; either version 2 of the License, or     *
6
 *   (at your option) any later version.                                   *
7
 *                                                                         *
8
 ***************************************************************************/
9

10
#include "inpututils.h"
11

12
#include <QWindow>
13
#include <QScreen>
14
#include <QApplication>
15

16
#include "qgsruntimeprofiler.h"
17
#include "qcoreapplication.h"
18
#include "qgsgeometrycollection.h"
19
#include "qgslinestring.h"
20
#include "qgspolygon.h"
21
#include "qgsvectorlayer.h"
22
#include "inputmaptransform.h"
23
#include "coreutils.h"
24
#include "qgis.h"
25
#include "qgscoordinatereferencesystem.h"
26
#include "qgscoordinatetransform.h"
27
#include "qgsdistancearea.h"
28
#include "qgslogger.h"
29
#include "qgsvectorlayer.h"
30
#include "qgsfeature.h"
31
#include "qgsapplication.h"
32
#include "qgsvaluerelationfieldformatter.h"
33
#include "qgsdatetimefieldformatter.h"
34
#include "qgslayertree.h"
35
#include "qgsprojectviewsettings.h"
36
#include "qgsvectorlayerutils.h"
37
#include "qgslinestring.h"
38
#include "qgspolygon.h"
39
#include "qgsmultipoint.h"
40
#include "qgsmultilinestring.h"
41
#include "qgsmultipolygon.h"
42

43
#include "featurelayerpair.h"
44
#include "inputmapsettings.h"
45
#include "qgsunittypes.h"
46
#include "qgsfeatureid.h"
47

48
#include "imageutils.h"
49
#include "variablesmanager.h"
50

51
#include <Qt>
52
#include <QDir>
53
#include <QFile>
54
#include <QFileInfo>
55
#include <QRegularExpression>
56
#include <algorithm>
57
#include <limits>
58
#include <math.h>
59
#include <iostream>
60
#include <QStandardPaths>
61

62
static const QString DATE_TIME_FORMAT = QStringLiteral( "yyMMdd-hhmmss" );
63
static const QString INVALID_DATETIME_STR = QStringLiteral( "Invalid datetime" );
64

65
InputUtils::InputUtils( QObject *parent )
8✔
66
  : QObject( parent )
8✔
67
{
68
}
8✔
69

70
InputUtils::InputUtils( AndroidUtils *au, QObject *parent )
17✔
71
  : QObject( parent )
72
  , mAndroidUtils( au )
17✔
73
{
74
}
17✔
75

76
bool InputUtils::removeFile( const QString &filePath )
10✔
77
{
78
  QFile file( filePath );
10✔
79
  return file.remove( filePath );
20✔
80
}
10✔
81

82
bool InputUtils::copyFile( const QString &srcPath, const QString &dstPath )
5✔
83
{
84
  QString modSrcPath = srcPath;
5✔
85
  if ( srcPath.startsWith( "file://" ) )
5✔
86
  {
87
    modSrcPath = modSrcPath.replace( "file://", "" );
×
88
  }
89

90
  QFileInfo fi( dstPath );
5✔
91
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
5✔
92
  {
93
    return false;
×
94
  }
95

96
  // https://github.com/MerginMaps/input/issues/418
97
  // does not work for iOS files with format
98
  // file:assets-library://asset/asset.PNG%3Fid=A53AB989-6354-433A-9CB9-958179B7C14D&ext=PNG
99

100
  return QFile::copy( modSrcPath, dstPath );
5✔
101
}
5✔
102

103
bool InputUtils::createDirectory( const QString &path )
6✔
104
{
105
  QDir dir;
6✔
106
  return dir.mkpath( path );
12✔
107
}
6✔
108

109
QString InputUtils::getFileName( const QString &filePath )
×
110
{
111
  QFileInfo fileInfo( sanitizeName( filePath ) );
×
112
  return fileInfo.fileName();
×
113
}
×
114

115
QString InputUtils::sanitizeName( const QString &path )
×
116
{
117
#ifdef Q_OS_IOS
118
  QRegularExpression reAbs( "(.+)\\/asset\\.PNG%.Fid=(\\S+)&ext=" );
119
  QRegularExpressionMatch matchAbs = reAbs.match( path );
120
  if ( matchAbs.hasMatch() )
121
  {
122
    QString base = matchAbs.captured( 1 );
123
    QString name = matchAbs.captured( 2 );
124
    return base + "/" + name + ".png";
125
  }
126

127
  QRegularExpression reRel( "asset\\.PNG%.Fid=(\\S+)&ext=" );
128
  QRegularExpressionMatch matchRel = reRel.match( path );
129
  if ( matchRel.hasMatch() )
130
  {
131
    QString matched = matchRel.captured( 1 );
132
    return matched + ".png";
133
  }
134
#endif
135
  return path;
×
136
}
137

138
QString InputUtils::formatProjectName( const QString &fullProjectName )
×
139
{
140
  if ( fullProjectName.contains( "/" ) )
×
141
  {
142
    QStringList list = fullProjectName.split( "/" );
×
143
    if ( list.at( 0 ).isEmpty() )
×
144
    {
145
      return QString( "<b>%1</b>" ).arg( list.at( 1 ) );
×
146
    }
147
    else
148
    {
149
      return QString( "%1/<b>%2</b>" ).arg( list.at( 0 ) ).arg( list.at( 1 ) );
×
150
    }
151
  }
×
152
  else
153
  {
154
    return QString( "<b>%1</b>" ).arg( fullProjectName );
×
155
  }
156
}
157

158
QString InputUtils::formatNumber( const double number, int precision )
2✔
159
{
160
  return QString::number( number, 'f', precision );
2✔
161
}
162

163
QString InputUtils::formatDistanceInProjectUnit( const double distanceInMeters, int precision, Qgis::DistanceUnit destUnit )
10✔
164
{
165
  Qgis::DistanceUnit distUnit = destUnit;
10✔
166

167
  if ( distUnit == Qgis::DistanceUnit::Unknown )
10✔
168
  {
169
    distUnit = QgsProject::instance()->distanceUnits();
×
170
  }
171

172
  if ( distUnit == Qgis::DistanceUnit::Unknown )
10✔
173
  {
174
    return QString::number( distanceInMeters, 'f', precision );
×
175
  }
176

177
  double factor = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, distUnit );
10✔
178
  double distance = distanceInMeters * factor;
10✔
179
  QString abbreviation = QgsUnitTypes::toAbbreviatedString( distUnit );
10✔
180

181
  return QString( "%1 %2" ).arg( QString::number( distance, 'f', precision ), abbreviation );
20✔
182
}
10✔
183

184
QString InputUtils::formatDateTimeDiff( const QDateTime &tMin, const QDateTime &tMax )
15✔
185
{
186
  qint64 daysDiff = tMin.daysTo( tMax );
15✔
187

188
  // datetime is invalid
189
  if ( daysDiff < 0 )
15✔
190
  {
191
    return INVALID_DATETIME_STR;
×
192
  }
193

194
  // diff is maximum one day
195
  // Note that difference from 23:55 to 0:05 the next day counts as one day
196
  if ( daysDiff == 0 || daysDiff == 1 )
15✔
197
  {
198
    qint64 secsDiff = tMin.secsTo( tMax );
8✔
199
    if ( secsDiff < 0 )
8✔
200
    {
201
      return INVALID_DATETIME_STR;
1✔
202
    }
203
    else if ( secsDiff < 60 )
7✔
204
    {
205
      return tr( "just now" );
2✔
206
    }
207
    else if ( secsDiff < 60 * 60 )
5✔
208
    {
209
      int period = secsDiff / 60 ;
2✔
210
      return ( period > 1 ) ? tr( "%1 minutes ago" ).arg( period ) : tr( "%1 minute ago" ).arg( period );
2✔
211
    }
212
    else if ( secsDiff < 60 * 60 * 24 )
3✔
213
    {
214
      int period = secsDiff / ( 60 * 60 );
2✔
215
      return ( period > 1 ) ? tr( "%1 hours ago" ).arg( period ) : tr( "%1 hour ago" ).arg( period );
2✔
216
    }
217
    else
218
    {
219
      return ( daysDiff > 1 ) ? tr( "%1 days ago" ).arg( daysDiff ) : tr( "%1 day ago" ).arg( daysDiff );
1✔
220
    }
221
  }
222
  else if ( daysDiff < 7 )
7✔
223
  {
224
    return ( daysDiff > 1 ) ? tr( "%1 days ago" ).arg( daysDiff ) : tr( "%1 day ago" ).arg( daysDiff );
1✔
225
  }
226
  else if ( daysDiff < 31 )
6✔
227
  {
228
    int period = daysDiff / 7;
2✔
229
    return ( period > 1 ) ? tr( "%1 weeks ago" ).arg( period ) : tr( "%1 week ago" ).arg( period );
2✔
230
  }
231
  else if ( daysDiff < 365 )
4✔
232
  {
233
    int period = daysDiff / 31;
2✔
234
    return ( period > 1 ) ? tr( "%1 months ago" ).arg( period ) : tr( "%1 month ago" ).arg( period );
2✔
235
  }
236
  else
237
  {
238
    int period = daysDiff / 365;
2✔
239
    return ( period > 1 ) ? tr( "%1 years ago" ).arg( period ) : tr( "%1 year ago" ).arg( period );
2✔
240
  }
241

242
  return INVALID_DATETIME_STR;
243
}
244

245
void InputUtils::setExtentToFeature( const FeatureLayerPair &pair, InputMapSettings *mapSettings, double panelOffsetRatio )
×
246
{
247

248
  if ( !mapSettings )
×
249
    return;
×
250

251
  if ( !pair.layer() )
×
252
    return;
×
253

254
  if ( !pair.feature().isValid() )
×
255
    return;
×
256

257
  QgsGeometry geom = pair.feature().geometry();
×
258
  if ( geom.isNull() || !geom.constGet() )
×
259
    return;
×
260

261
  QgsRectangle bbox = mapSettings->mapSettings().layerExtentToOutputExtent( pair.layer(), geom.boundingBox() );
×
262
  QgsRectangle currentExtent = mapSettings->mapSettings().extent();
×
263
  QgsPointXY currentExtentCenter = currentExtent.center();
×
264
  QgsPointXY featureCenter = bbox.center();
×
265

266
  double panelOffset = ( currentExtent.yMaximum() - currentExtent.yMinimum() ) * panelOffsetRatio / 2;
×
267
  double offsetX = currentExtentCenter.x() - featureCenter.x();
×
268
  double offsetY = currentExtentCenter.y() - featureCenter.y();
×
269
  currentExtent.setXMinimum( currentExtent.xMinimum() - offsetX );
×
270
  currentExtent.setXMaximum( currentExtent.xMaximum() - offsetX );
×
271
  currentExtent.setYMinimum( currentExtent.yMinimum() - offsetY - panelOffset );
×
272
  currentExtent.setYMaximum( currentExtent.yMaximum() - offsetY - panelOffset );
×
273
  mapSettings->setExtent( currentExtent );
×
274
}
×
275

276
double InputUtils::convertCoordinateString( const QString &rationalValue )
×
277
{
278
  QStringList values = rationalValue.split( "," );
×
279
  if ( values.size() != 3 ) return 0;
×
280

281
  double degrees = ratherZeroThanNaN( convertRationalNumber( values.at( 0 ) ) );
×
282
  double minutes = ratherZeroThanNaN( convertRationalNumber( values.at( 1 ) ) );
×
283
  double seconds = ratherZeroThanNaN( convertRationalNumber( values.at( 2 ) ) );
×
284

285
  double result = degrees + minutes / 60 + seconds / 3600;
×
286
  return result;
×
287
}
×
288

289
QString InputUtils::degreesString( const QgsPoint &point )
×
290
{
291
  if ( point.isEmpty() )
×
292
  {
293
    return QLatin1String();
×
294
  }
295

296
  // QGeoCoordinate formatter uses lat/long order, but we (and QGIS) use long/lat order,
297
  // so here we need to first pass y and then x.
298
  return QGeoCoordinate( point.y(), point.x() ).toString( QGeoCoordinate::DegreesMinutesWithHemisphere );
×
299
}
300

301
double InputUtils::convertRationalNumber( const QString &rationalValue )
×
302
{
303
  if ( rationalValue.isEmpty() )
×
304
    return std::numeric_limits<double>::quiet_NaN();
×
305

306
  QStringList number = rationalValue.split( "/" );
×
307
  if ( number.size() != 2 )
×
308
    return std::numeric_limits<double>::quiet_NaN();
×
309

310
  double numerator = number.at( 0 ).toDouble();
×
311
  double denominator = number.at( 1 ).toDouble();
×
312
  if ( denominator == 0 )
×
313
    return denominator;
×
314

315
  return numerator / denominator;
×
316
}
×
317

318
double InputUtils::mapSettingsScale( InputMapSettings *ms )
×
319
{
320
  if ( !ms ) return 1;
×
321
  return 1 / ms->mapUnitsPerPixel();
×
322
}
323

324
double InputUtils::mapSettingsOffsetX( InputMapSettings *ms )
×
325
{
326
  if ( !ms ) return 0;
×
327
  return -ms->visibleExtent().xMinimum();
×
328
}
329

330
double InputUtils::mapSettingsOffsetY( InputMapSettings *ms )
×
331
{
332
  if ( !ms ) return 0;
×
333
  return -ms->visibleExtent().yMaximum();
×
334
}
335

336
double InputUtils::mapSettingsDPR( InputMapSettings *ms )
×
337
{
338
  if ( !ms ) return 1;
×
339
  return ms->devicePixelRatio();
×
340
}
341

342
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, const QgsCoordinateReferenceSystem &destinationCRS, const QgsCoordinateTransformContext &context )
1✔
343
{
344
  QgsGeometry g( geometry );
1✔
345

346
  QgsCoordinateTransform ct( sourceCRS, destinationCRS, context );
1✔
347
  if ( !ct.isShortCircuited() )
1✔
348
  {
349
    try
350
    {
351
      g.transform( ct );
×
352
    }
353
    catch ( QgsCsException &e )
×
354
    {
355
      Q_UNUSED( e )
356
      return QgsGeometry();
×
357
    }
×
358
  }
359

360
  return g;
1✔
361
}
1✔
362

363
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, QgsVectorLayer *targetLayer )
1✔
364
{
365
  if ( !targetLayer || !targetLayer->isValid() )
1✔
366
  {
367
    return QgsGeometry();
×
368
  }
369

370
  return transformGeometry( geometry, sourceCRS, targetLayer->crs(), targetLayer->transformContext() );
2✔
371
}
372

373
QgsGeometry InputUtils::transformGeometryToMapWithLayer( const QgsGeometry &geometry, QgsVectorLayer *sourceLayer, InputMapSettings *targetSettings )
×
374
{
375
  if ( !sourceLayer || !sourceLayer->isValid() || !targetSettings )
×
376
  {
377
    return QgsGeometry();
×
378
  }
379

380
  return transformGeometry( geometry, sourceLayer->crs(), targetSettings->destinationCrs(), targetSettings->transformContext() );
×
381
}
382

383
QgsGeometry InputUtils::transformGeometryToMapWithCRS( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, InputMapSettings *targetSettings )
×
384
{
385
  if ( !targetSettings )
×
386
  {
387
    return QgsGeometry();
×
388
  }
389

390
  return transformGeometry( geometry, sourceCRS, targetSettings->destinationCrs(), targetSettings->transformContext() );
×
391
}
392

393
QgsGeometry InputUtils::extractGeometry( const FeatureLayerPair &pair )
×
394
{
395
  if ( !pair.isValid() )
×
396
    return QgsGeometry();
×
397

398
  return pair.feature().geometry();
×
399
}
400

401
QString InputUtils::geometryLengthAsString( const QgsGeometry &geometry )
×
402
{
403
  QgsDistanceArea distanceArea;
×
404
  distanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
×
405
  distanceArea.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() );
×
406

407
  qreal length = distanceArea.measureLength( geometry );
×
408

409
  if ( qgsDoubleNear( length, 0 ) )
×
410
  {
411
    return "0 m";
×
412
  }
413

414
  return distanceArea.formatDistance( length, 2, distanceArea.lengthUnits() );
×
415
}
×
416

417
static void addLineString( const QgsLineString *line, QVector<double> &data )
×
418
{
419
  data << line->numPoints();
×
420
  const double *x = line->xData();
×
421
  const double *y = line->yData();
×
422
  for ( int i = 0; i < line->numPoints(); ++i )
×
423
  {
424
    data << x[i] << y[i];
×
425
  }
426
}
×
427

428
static void addSingleGeometry( const QgsAbstractGeometry *geom, Qgis::GeometryType type, QVector<double> &data )
×
429
{
430
  switch ( type )
×
431
  {
432
    case Qgis::GeometryType::Point:
×
433
    {
434
      const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( geom );
×
435
      if ( point )
×
436
      {
437
        data << 0 << point->x() << point->y();
×
438
      }
439
      break;
×
440
    }
441

442
    case Qgis::GeometryType::Line:
×
443
    {
444
      const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom );
×
445
      if ( line )
×
446
      {
447
        data << 1;
×
448
        addLineString( line, data );
×
449
      }
450
      break;
×
451
    }
452

453
    case Qgis::GeometryType::Polygon:
×
454
    {
455
      const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( geom );
×
456
      if ( poly )
×
457
      {
458
        if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->exteriorRing() ) )
×
459
        {
460
          data << 2;
×
461
          addLineString( line, data );
×
462
        }
463
        for ( int i = 0; i < poly->numInteriorRings(); ++i )
×
464
        {
465
          if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->interiorRing( i ) ) )
×
466
          {
467
            data << 2;
×
468
            addLineString( line, data );
×
469
          }
470
        }
471
      }
472
      break;
×
473
    }
474

475
    case Qgis::GeometryType::Unknown:
×
476
    case Qgis::GeometryType::Null:
477
      break;
×
478
  }
479
}
×
480

481
QVector<double> InputUtils::extractGeometryCoordinates( const QgsGeometry &geometry )
×
482
{
483
  if ( geometry.isNull() )
×
484
    return QVector<double>();
×
485

486
  QVector<double> data;
×
487

488
  const QgsAbstractGeometry *geom = geometry.constGet();
×
489
  Qgis::GeometryType geomType = geometry.type();
×
490
  const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
×
491
  if ( collection && !collection->isEmpty() )
×
492
  {
493
    for ( int i = 0; i < collection->numGeometries(); ++i )
×
494
    {
495
      addSingleGeometry( collection->geometryN( i ), geomType, data );
×
496
    }
497
  }
498
  else
499
  {
500
    addSingleGeometry( geom, geomType, data );
×
501
  }
502

503
  return data;
×
504
}
×
505

506
QString InputUtils::filesToString( QList<MerginFile> files )
×
507
{
508
  QStringList resultList;
×
509
  for ( MerginFile file : files )
×
510
  {
511
    resultList << file.path;
×
512
  }
×
513
  return resultList.join( ", " );
×
514
}
×
515

516
QString InputUtils::bytesToHumanSize( double bytes )
×
517
{
518
  const int precision = 1;
×
519
  if ( bytes < 1e-5 )
×
520
  {
521
    return "0.0";
×
522
  }
523
  else if ( bytes < 1024.0 * 1024.0 )
×
524
  {
525
    return QString::number( bytes / 1024.0, 'f', precision ) + " KB";
×
526
  }
527
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 )
×
528
  {
529
    return QString::number( bytes / 1024.0 / 1024.0, 'f', precision ) + " MB";
×
530
  }
531
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 * 1024.0 )
×
532
  {
533
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " GB";
×
534
  }
535
  else
536
  {
537
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " TB";
×
538
  }
539
}
540

541
bool InputUtils::acquireCameraPermission()
×
542
{
543
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
544
  {
545
    return mAndroidUtils->requestCameraPermission();
×
546
  }
547
  return true;
×
548
}
549

550
bool InputUtils::isBluetoothTurnedOn()
×
551
{
552
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
553
  {
554
    return mAndroidUtils->isBluetoothTurnedOn();
×
555
  }
556
  return true;
×
557
}
558

559
void InputUtils::turnBluetoothOn()
×
560
{
561
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
562
  {
563
    mAndroidUtils->turnBluetoothOn();
×
564
  }
565
}
×
566

567
void InputUtils::quitApp()
×
568
{
569
  if ( appPlatform() == QStringLiteral( "android" ) )
×
570
  {
571
    AndroidUtils::quitApp();
×
572
  }
573
  else
574
  {
575
    QCoreApplication::quit();
×
576
  }
577
}
×
578

579
QString InputUtils::appPlatform()
21✔
580
{
581
#if defined( ANDROID )
582
  const QString platform = "android";
583
#elif defined( Q_OS_IOS )
584
  const QString platform = "ios";
585
#elif defined( Q_OS_WIN32 )
586
  const QString platform = "win";
587
#elif defined( Q_OS_LINUX )
588
  const QString platform = "linux";
21✔
589
#elif defined( Q_OS_MAC )
590
  const QString platform = "macos";
591
#else
592
  const QString platform = "unknown";
593
#endif
594
  return platform;
21✔
595
}
596

597
bool InputUtils::isMobilePlatform()
3✔
598
{
599
  QString platform = appPlatform();
3✔
600
  return platform == QStringLiteral( "android" ) || platform == QStringLiteral( "ios" );
12✔
601
}
3✔
602

603
void InputUtils::onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level )
20✔
604
{
605
  QString levelStr;
20✔
606
  switch ( level )
20✔
607
  {
608
    case Qgis::MessageLevel::Warning:
20✔
609
      levelStr = "Warning";
20✔
610
      break;
20✔
611
    case Qgis::MessageLevel::Critical:
×
612
      levelStr = "Error";
×
613
      break;
×
614
    default:
×
615
      break;
×
616
  }
617

618
  CoreUtils::log( "QGIS " + tag, levelStr + ": " + message );
20✔
619
}
20✔
620

621
bool InputUtils::cpDir( const QString &srcPath, const QString &dstPath, bool onlyDiffable )
58✔
622
{
623
  bool result  = true;
58✔
624
  QDir parentDstDir( QFileInfo( dstPath ).path() );
116✔
625
  if ( !parentDstDir.mkpath( dstPath ) )
58✔
626
  {
627
    CoreUtils::log( "cpDir", QString( "Cannot make path %1" ).arg( dstPath ) );
×
628
    return false;
×
629
  }
630

631
  QDir srcDir( srcPath );
58✔
632
  const QFileInfoList fileInfoList = srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden );
58✔
633
  foreach ( const QFileInfo &info, fileInfoList )
569✔
634
  {
635
    QString fileName = info.fileName();
511✔
636

637
#ifdef ANDROID
638
    // https://bugreports.qt.io/browse/QTBUG-114219
639
    if ( fileName.startsWith( "assets:/" ) )
640
    {
641
      fileName.remove( 0, 8 );
642
    }
643
#endif
644

645
    QString srcItemPath = srcPath + "/" + fileName;
511✔
646
    QString dstItemPath = dstPath + "/" + fileName;
511✔
647

648
    if ( info.isDir() )
511✔
649
    {
650
      if ( !cpDir( srcItemPath, dstItemPath ) )
2✔
651
      {
652
        CoreUtils::log( "cpDir", QString( "Cannot copy a dir from %1 to %2" ).arg( srcItemPath ).arg( dstItemPath ) );
×
653
        result = false;
×
654
      }
655
    }
656
    else if ( info.isFile() )
509✔
657
    {
658
      if ( onlyDiffable && !MerginApi::isFileDiffable( fileName ) )
509✔
659
        continue;
×
660

661
      if ( !QFile::copy( srcItemPath, dstItemPath ) )
509✔
662
      {
663
        if ( !QFile::remove( dstItemPath ) )
×
664
        {
665
          CoreUtils::log( "cpDir", QString( "Cannot remove a file from %1" ).arg( dstItemPath ) );
×
666
          result =  false;
×
667
        }
668
        if ( !QFile::copy( srcItemPath, dstItemPath ) )
×
669
        {
670
          CoreUtils::log( "cpDir", QString( "Cannot overwrite a file %1 with %2" ).arg( dstItemPath ).arg( dstItemPath ) );
×
671
          result =  false;
×
672
        }
673
      }
674
      QFile::setPermissions( dstItemPath, QFile::ReadUser | QFile::WriteUser | QFile::ReadOwner | QFile::WriteOwner );
509✔
675
    }
676
    else
677
    {
678
      CoreUtils::log( "cpDir", QString( "Unhandled item %1 in cpDir" ).arg( info.filePath() ) );
×
679
    }
680
  }
569✔
681
  return result;
58✔
682
}
58✔
683

684
QString InputUtils::renameWithDateTime( const QString &srcPath, const QDateTime &dateTime )
×
685
{
686
  if ( QFile::exists( srcPath ) )
×
687
  {
688
    QFileInfo info( srcPath );
×
689
    QString timestamp = ( dateTime.isValid() ) ? dateTime.toString( DATE_TIME_FORMAT ) : QDateTime::currentDateTime().toString( DATE_TIME_FORMAT );
×
690
    QString newFilename = QString( "%1.%2" ).arg( timestamp ).arg( info.suffix() );
×
691
    QString newPath( info.absolutePath() + "/" + newFilename );
×
692

693
    if ( QFile::rename( srcPath, newPath ) ) return newPath;
×
694
  }
×
695

696
  return QString();
×
697
}
698

699
bool InputUtils::renameFile( const QString &srcPath, const QString &dstPath )
1✔
700
{
701
  QFileInfo fi( dstPath );
1✔
702
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
1✔
703
  {
704
    return false;
×
705
  }
706
  return QFile::rename( srcPath, dstPath );
1✔
707
}
1✔
708

709
void InputUtils::showNotification( const QString &message )
×
710
{
711
  emit showNotificationRequested( message );
×
712
}
×
713

714
double InputUtils::ratherZeroThanNaN( double d )
×
715
{
716
  return ( isnan( d ) ) ? 0.0 : d;
×
717
}
718

719
/**
720
 * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
721
 */
722
QgsCoordinateReferenceSystem InputUtils::coordinateReferenceSystemFromEpsgId( long epsg )
12✔
723
{
724
  return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
12✔
725
}
726

727
QgsPointXY InputUtils::pointXY( double x, double y )
1✔
728
{
729
  return QgsPointXY( x, y );
1✔
730
}
731

732
QgsPoint InputUtils::point( double x, double y, double z, double m )
2✔
733
{
734
  return QgsPoint( x, y, z, m );
2✔
735
}
736

737
QgsGeometry InputUtils::emptyGeometry()
×
738
{
739
  return QgsGeometry();
×
740
}
741

742
QgsFeature InputUtils::emptyFeature()
×
743
{
744
  return QgsFeature();
×
745
}
746

747
bool InputUtils::isEmptyGeometry( const QgsGeometry &geometry )
×
748
{
749
  return geometry.isEmpty();
×
750
}
751

752
QgsPoint InputUtils::coordinateToPoint( const QGeoCoordinate &coor )
×
753
{
754
  return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
×
755
}
756

757
QgsPointXY InputUtils::transformPointXY( const QgsCoordinateReferenceSystem &srcCrs,
27✔
758
    const QgsCoordinateReferenceSystem &destCrs,
759
    const QgsCoordinateTransformContext &context,
760
    const QgsPointXY &srcPoint )
761
{
762
  // we do not want to transform empty points,
763
  // QGIS would convert them to a valid (0, 0) points
764
  if ( srcPoint.isEmpty() )
27✔
765
  {
766
    return QgsPointXY();
×
767
  }
768

769
  try
770
  {
771
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
772
    if ( ct.isValid() )
27✔
773
    {
774
      if ( !ct.isShortCircuited() )
27✔
775
      {
776
        const QgsPointXY pt = ct.transform( srcPoint );
6✔
777
        return pt;
6✔
778
      }
779
      else
780
      {
781
        return srcPoint;
21✔
782
      }
783
    }
784
  }
27✔
785
  catch ( QgsCsException &cse )
×
786
  {
787
    Q_UNUSED( cse )
788
  }
×
789

790
  return QgsPointXY();
×
791
}
792

793
QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs,
28✔
794
                                     const QgsCoordinateReferenceSystem &destCrs,
795
                                     const QgsCoordinateTransformContext &context,
796
                                     const QgsPoint &srcPoint )
797
{
798
  // we do not want to transform empty points,
799
  // QGIS would convert them to a valid (0, 0) points
800
  if ( srcPoint.isEmpty() )
28✔
801
  {
802
    return QgsPoint();
1✔
803
  }
804

805
  try
806
  {
807
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
808
    if ( ct.isValid() )
27✔
809
    {
810
      if ( !ct.isShortCircuited() )
27✔
811
      {
812
        const QgsPointXY transformed = ct.transform( srcPoint.x(), srcPoint.y() );
6✔
813
        const QgsPoint pt( transformed.x(), transformed.y(), srcPoint.z(), srcPoint.m() );
6✔
814
        return pt;
6✔
815
      }
6✔
816
      else
817
      {
818
        return srcPoint;
21✔
819
      }
820
    }
821
  }
27✔
822
  catch ( QgsCsException &cse )
×
823
  {
824
    Q_UNUSED( cse )
825
  }
×
826

827
  return QgsPoint();
×
828
}
829

830
QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint )
×
831
{
832
  if ( !mapSettings || srcPoint.isEmpty() )
×
833
    return QPointF();
×
834

835
  QgsPoint mapcrsPoint = transformPoint( srcCrs, mapSettings->destinationCrs(), mapSettings->transformContext(), srcPoint );
×
836
  return mapSettings->coordinateToScreen( mapcrsPoint );
×
837
}
×
838

839
double InputUtils::screenUnitsToMeters( InputMapSettings *mapSettings, int baseLengthPixels )
30✔
840
{
841
  if ( mapSettings == nullptr ) return 0.0;
30✔
842

843
  QgsDistanceArea mDistanceArea;
30✔
844
  mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
30✔
845
  mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
30✔
846

847
  // calculate the geographic distance from the central point of extent
848
  // to the specified number of points on the right side
849
  QSize s = mapSettings->outputSize();
30✔
850
  QPoint pointCenter( s.width() / 2, s.height() / 2 );
30✔
851
  QgsPointXY p1 = mapSettings->screenToCoordinate( pointCenter );
30✔
852
  QgsPointXY p2 = mapSettings->screenToCoordinate( pointCenter + QPoint( baseLengthPixels, 0 ) );
30✔
853
  return mDistanceArea.measureLine( p1, p2 );
30✔
854
}
30✔
855

856
QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSettings )
5✔
857
{
858
  if ( !mapSettings )
5✔
859
    return QgsPoint();
×
860

861
  if ( mapPosition.isNull() )
5✔
862
    return QgsPoint();
×
863

864
  QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition );
5✔
865
  QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 );
5✔
866

867
  const QgsPointXY transformedXY = transformPoint(
10✔
868
                                     mapSettings->destinationCrs(),
10✔
869
                                     crsGPS,
870
                                     QgsCoordinateTransformContext(),
10✔
871
                                     positionMapCrs
872
                                   );
5✔
873

874
  if ( transformedXY.isEmpty() )
5✔
875
  {
876
    // point could not be transformed
877
    return QgsPoint();
1✔
878
  }
879

880
  return QgsPoint( transformedXY );
4✔
881
}
5✔
882

883
bool InputUtils::fileExists( const QString &path )
8✔
884
{
885
  QFileInfo check_file( path );
8✔
886
  // check if file exists and if yes: Is it really a file and no directory?
887
  return ( check_file.exists() && check_file.isFile() );
16✔
888
}
8✔
889

890
QString InputUtils::resolveTargetDir( const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
7✔
891
{
892
  QString expression;
7✔
893
  QMap<QString, QVariant> collection = config.value( QStringLiteral( "PropertyCollection" ) ).toMap();
21✔
894
  QMap<QString, QVariant> props = collection.value( QStringLiteral( "properties" ) ).toMap();
21✔
895

896
  if ( !props.isEmpty() )
7✔
897
  {
898
    QMap<QString, QVariant> propertyRootPath = props.value( QStringLiteral( "propertyRootPath" ) ).toMap();
3✔
899
    expression = propertyRootPath.value( QStringLiteral( "expression" ), QString() ).toString();
2✔
900
  }
1✔
901

902
  if ( !expression.isEmpty() )
7✔
903
  {
904
    return evaluateExpression( pair, activeProject, expression );
1✔
905
  }
906
  else
907
  {
908
    QString defaultRoot = config.value( QStringLiteral( "DefaultRoot" ) ).toString();
18✔
909
    if ( defaultRoot.isEmpty() )
6✔
910
    {
911
      return homePath;
5✔
912
    }
913
    else
914
    {
915
      return defaultRoot;
1✔
916
    }
917
  }
6✔
918
}
7✔
919

920
QString InputUtils::resolvePrefixForRelativePath( int relativeStorageMode, const QString &homePath, const QString &targetDir )
4✔
921
{
922
  if ( relativeStorageMode == 1 )
4✔
923
  {
924
    return homePath;
2✔
925
  }
926
  else if ( relativeStorageMode == 2 )
2✔
927
  {
928
    return targetDir;
1✔
929
  }
930
  else
931
  {
932
    return QString();
1✔
933
  }
934
}
935

936
QString InputUtils::getAbsolutePath( const QString &path, const QString &prefixPath )
5✔
937
{
938
  return ( prefixPath.isEmpty() ) ? path : QStringLiteral( "%1/%2" ).arg( prefixPath ).arg( path );
9✔
939
}
940

941
QString InputUtils::resolvePath( const QString &path, const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
3✔
942
{
943
  int relativeStorageMode = config.value( QStringLiteral( "RelativeStorage" ) ).toInt();
6✔
944
  QString targetDir = resolveTargetDir( homePath, config, pair, activeProject );
3✔
945
  QString prefixToRelativePath = resolvePrefixForRelativePath( relativeStorageMode, homePath, targetDir );
3✔
946

947
  return getAbsolutePath( path, prefixToRelativePath );
6✔
948
}
3✔
949

950
QString InputUtils::getRelativePath( const QString &path, const QString &prefixPath )
5✔
951
{
952
  QString modPath = path;
5✔
953
  QString filePrefix( "file://" );
5✔
954

955
  if ( path.startsWith( filePrefix ) )
5✔
956
  {
957
    modPath = modPath.replace( filePrefix, QString() );
1✔
958
  }
959

960
  if ( prefixPath.isEmpty() ) return modPath;
5✔
961

962
  // Do not use a canonical path for non-existing path
963
  if ( !QFileInfo( path ).exists() )
4✔
964
  {
965
    if ( !prefixPath.isEmpty() && modPath.startsWith( prefixPath ) )
4✔
966
    {
967
      return modPath.replace( prefixPath, QString() );
3✔
968
    }
969
  }
970
  else
971
  {
972
    QDir absoluteDir( modPath );
×
973
    QDir prefixDir( prefixPath );
×
974
    QString canonicalPath = absoluteDir.canonicalPath();
×
975
    QString prefixCanonicalPath = prefixDir.canonicalPath() + "/";
×
976

977
    if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
×
978
    {
979
      return canonicalPath.replace( prefixCanonicalPath, QString() );
×
980
    }
981
  }
×
982

983
  return QString();
1✔
984
}
5✔
985

986
void InputUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
×
987
{
988
  QgsMessageLog::logMessage( message, tag, level );
×
989
}
×
990

991
void InputUtils::log( const QString &context, const QString &message )
×
992
{
993
  CoreUtils::log( context, message );
×
994
}
×
995

996
const QUrl InputUtils::getThemeIcon( const QString &name )
1✔
997
{
998
  QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
1✔
999
  QgsDebugMsgLevel( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ), 2 );
1000
  return QUrl( path );
2✔
1001
}
1✔
1002

1003
const QUrl InputUtils::getEditorComponentSource( const QString &widgetName, const QVariantMap &config, const QgsField &field )
2✔
1004
{
1005
  QString path( "../editor/input%1.qml" );
2✔
1006

1007
  if ( widgetName == QStringLiteral( "range" ) )
2✔
1008
  {
1009
    if ( config.contains( "Style" ) )
×
1010
    {
1011
      if ( config["Style"] == QStringLiteral( "Slider" ) )
×
1012
      {
1013
        return QUrl( path.arg( QLatin1String( "rangeslider" ) ) );
×
1014
      }
1015
      else if ( config["Style"] == QStringLiteral( "SpinBox" ) )
×
1016
      {
1017
        return QUrl( path.arg( QLatin1String( "rangeeditable" ) ) );
×
1018
      }
1019
    }
1020
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1021
  }
1022

1023
  if ( field.name().contains( "qrcode", Qt::CaseInsensitive ) || field.alias().contains( "qrcode", Qt::CaseInsensitive ) )
2✔
1024
  {
1025
    return QUrl( path.arg( QStringLiteral( "qrcodereader" ) ) );
×
1026
  }
1027

1028
  if ( widgetName == QStringLiteral( "textedit" ) )
2✔
1029
  {
1030
    if ( config.value( "IsMultiline" ).toBool() )
×
1031
    {
1032
      return QUrl( path.arg( QStringLiteral( "texteditmultiline" ) ) );
×
1033
    }
1034
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1035
  }
1036

1037
  if ( widgetName == QStringLiteral( "valuerelation" ) )
2✔
1038
  {
1039
    const QgsMapLayer *referencedLayer = QgsProject::instance()->mapLayer( config.value( "Layer" ).toString() );
×
1040
    const QgsVectorLayer *layer = qobject_cast<const QgsVectorLayer *>( referencedLayer );
×
1041

1042
    if ( layer )
×
1043
    {
1044
      int featuresCount = layer->dataProvider()->featureCount();
×
1045
      if ( featuresCount > 4 )
×
1046
        return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1047
    }
1048

1049
    if ( config.value( "AllowMulti" ).toBool() )
×
1050
    {
1051
      return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1052
    }
1053

1054
    return QUrl( path.arg( QLatin1String( "valuerelationcombobox" ) ) );
×
1055
  }
1056

1057
  QStringList supportedWidgets = { QStringLiteral( "richtext" ),
×
1058
                                   QStringLiteral( "textedit" ),
2✔
1059
                                   QStringLiteral( "valuemap" ),
2✔
1060
                                   QStringLiteral( "valuerelation" ),
2✔
1061
                                   QStringLiteral( "checkbox" ),
2✔
1062
                                   QStringLiteral( "externalresource" ),
2✔
1063
                                   QStringLiteral( "datetime" ),
2✔
1064
                                   QStringLiteral( "range" ),
2✔
1065
                                   QStringLiteral( "relation" ),
2✔
1066
                                   QStringLiteral( "spacer" ),
2✔
1067
                                   QStringLiteral( "relationreference" )
2✔
1068
                                 };
46✔
1069
  if ( supportedWidgets.contains( widgetName ) )
2✔
1070
  {
1071
    return QUrl( path.arg( widgetName ) );
2✔
1072
  }
1073
  else
1074
  {
1075
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
2✔
1076
  }
1077
}
2✔
1078

1079
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field )
8✔
1080
{
1081
  if ( field.isNumeric() )
8✔
1082
    return getEditorWidgetSetup( field, QStringLiteral( "Range" ) );
12✔
1083
  else if ( field.isDateOrTime() )
4✔
1084
    return getEditorWidgetSetup( field, QStringLiteral( "DateTime" ) );
×
1085
  else if ( field.type() == QVariant::Bool )
4✔
1086
    return getEditorWidgetSetup( field, QStringLiteral( "CheckBox" ) );
×
1087
  else
1088
    return getEditorWidgetSetup( field, QStringLiteral( "TextEdit" ) );
12✔
1089
}
1090

1091
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field, const QString &widgetType, const QVariantMap &additionalArgs )
9✔
1092
{
1093
  if ( field.name() == QStringLiteral( "fid" ) )
9✔
1094
    return QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
4✔
1095

1096
  if ( widgetType.isEmpty() )
7✔
1097
  {
1098
    return QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() );
×
1099
  }
1100
  else
1101
  {
1102
    QMultiMap<QString, QVariant> config;
7✔
1103
    config = config.unite( QMultiMap( additionalArgs ) );
7✔
1104

1105
    if ( widgetType == QStringLiteral( "TextEdit" ) )
7✔
1106
    {
1107
      config.insert( QStringLiteral( "isMultiline" ), false );
8✔
1108
      config.insert( QStringLiteral( "UseHtml" ), false );
8✔
1109
    }
1110
    else if ( widgetType == QStringLiteral( "DateTime" ) )
3✔
1111
    {
1112
      config.insert( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::DATETIME_FORMAT );
×
1113
      config.insert( QStringLiteral( "display_format" ), QgsDateTimeFieldFormatter::DATETIME_FORMAT );
×
1114
    }
1115
    else if ( widgetType == QStringLiteral( "Range" ) )
3✔
1116
    {
1117
      config.insert( QStringLiteral( "Style" ), QStringLiteral( "SpinBox" ) );
4✔
1118
      config.insert( QStringLiteral( "Precision" ), QStringLiteral( "0" ) );
4✔
1119
      config.insert( QStringLiteral( "Min" ), QString::number( INT_MIN ) );
4✔
1120
      config.insert( QStringLiteral( "Max" ), QString::number( INT_MAX ) );
4✔
1121
      config.insert( QStringLiteral( "Step" ), 1 );
4✔
1122
    }
1123
    else if ( widgetType == QStringLiteral( "ExternalResource" ) )
1✔
1124
    {
1125
      config.insert( QStringLiteral( "RelativeStorage" ), QStringLiteral( "1" ) );
×
1126
      config.insert( QStringLiteral( "StorageMode" ), QStringLiteral( "0" ) );
×
1127
      config.insert( QStringLiteral( "PropertyCollection" ), QVariantMap() );
×
1128
      QgsPropertyCollection collection;
×
1129
      config.insert( QStringLiteral( "PropertyCollection" ), collection.toVariant( QgsPropertiesDefinition() ) );
×
1130
    }
×
1131
    else if ( widgetType == QStringLiteral( "RelationReference" ) )
1✔
1132
    {
1133
      config.insert( QStringLiteral( "AllowNULL" ), true );
2✔
1134
    }
1135

1136
    QVariantMap cfg;
7✔
1137
    QList<QString> keys = config.uniqueKeys();
7✔
1138
    for ( int i = 0; i < keys.size(); i++ )
28✔
1139
    {
1140
      cfg.insert( keys.at( i ), config.value( keys.at( i ) ) );
21✔
1141
    }
1142

1143
    return QgsEditorWidgetSetup( widgetType, cfg );
7✔
1144
  }
7✔
1145
}
1146

1147
QString InputUtils::geometryFromLayer( QgsVectorLayer *layer )
11✔
1148
{
1149
  if ( layer )
11✔
1150
  {
1151
    switch ( layer->geometryType() )
11✔
1152
    {
1153
      case Qgis::GeometryType::Point: return QStringLiteral( "point" );
20✔
1154
      case Qgis::GeometryType::Line: return QStringLiteral( "linestring" );
2✔
1155
      case Qgis::GeometryType::Polygon: return QStringLiteral( "polygon" );
×
1156
      case Qgis::GeometryType::Null: return QStringLiteral( "nullGeo" );
×
1157
      default: return QString();
×
1158
    }
1159
  }
1160
  return QString();
×
1161
}
1162

1163
bool InputUtils::isPointLayer( QgsVectorLayer *layer )
×
1164
{
1165
  return geometryFromLayer( layer ) == "point";
×
1166
}
1167

1168
bool InputUtils::isLineLayer( QgsVectorLayer *layer )
×
1169
{
1170
  return geometryFromLayer( layer ) == "linestring";
×
1171
}
1172

1173
bool InputUtils::isPolygonLayer( QgsVectorLayer *layer )
×
1174
{
1175
  return geometryFromLayer( layer ) == "polygon";
×
1176
}
1177

1178
bool InputUtils::isNoGeometryLayer( QgsVectorLayer *layer )
×
1179
{
1180
  return geometryFromLayer( layer ) == "nullGeo";
×
1181
}
1182

1183
bool InputUtils::isMultiPartLayer( QgsVectorLayer *layer )
×
1184
{
1185
  if ( !layer )
×
1186
  {
1187
    return false;
×
1188
  }
1189
  return QgsWkbTypes::isMultiType( layer->wkbType() );
×
1190
}
1191

1192
bool InputUtils::isSpatialLayer( QgsVectorLayer *layer )
×
1193
{
1194
  if ( !layer )
×
1195
  {
1196
    return false;
×
1197
  }
1198
  return layer->isSpatial();
×
1199
}
1200

1201
qreal InputUtils::calculateScreenDpr()
1✔
1202
{
1203
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1204

1205
  if ( !screens.isEmpty() )
1✔
1206
  {
1207
    QScreen *screen = screens.at( 0 );
1✔
1208
    double dpiX = screen->physicalDotsPerInchX();
1✔
1209
    double dpiY = screen->physicalDotsPerInchY();
1✔
1210

1211
    qreal realDpi = dpiX < dpiY ? dpiX : dpiY;
1✔
1212
    realDpi = realDpi * screen->devicePixelRatio();
1✔
1213

1214
    return realDpi / 160.;
1✔
1215
  }
1216

1217
  return 1;
×
1218
}
1✔
1219

1220
qreal InputUtils::calculateDpRatio()
1✔
1221
{
1222
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1223

1224
  if ( !screens.isEmpty() )
1✔
1225
  {
1226
    QScreen *screen = screens.at( 0 );
1✔
1227

1228
    qreal realDpr = calculateScreenDpr();
1✔
1229
    return realDpr / screen->devicePixelRatio();
1✔
1230
  }
1231

1232
  return 1;
×
1233
}
1✔
1234

1235
bool InputUtils::equals( const QPointF &a, const QPointF &b, double epsilon )
8✔
1236
{
1237
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
8✔
1238
}
1239

1240
bool InputUtils::equals( const QgsPointXY &a, const QgsPointXY &b, double epsilon )
42✔
1241
{
1242
  if ( a.isEmpty() && b.isEmpty() )
42✔
1243
    return true;
3✔
1244
  if ( a.isEmpty() != b.isEmpty() )
39✔
1245
    return false;
4✔
1246

1247
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
35✔
1248
}
1249

1250
bool InputUtils::equals( const QgsPoint &a, const QgsPoint &b, double epsilon )
316✔
1251
{
1252
  if ( a.isEmpty() && b.isEmpty() )
316✔
1253
    return true;
35✔
1254
  if ( a.isEmpty() != b.isEmpty() )
281✔
1255
    return false;
2✔
1256

1257
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
279✔
1258
}
1259

1260
QString InputUtils::formatPoint(
1✔
1261
  const QgsPoint &point,
1262
  QgsCoordinateFormatter::Format format,
1263
  int decimals,
1264
  QgsCoordinateFormatter::FormatFlags flags )
1265
{
1266
  return QgsCoordinateFormatter::format( point, format, decimals, flags );
1✔
1267
}
1268

1269
QString InputUtils::formatDistance( double distance,
10✔
1270
                                    Qgis::DistanceUnit units,
1271
                                    int decimals,
1272
                                    Qgis::SystemOfMeasurement destSystem )
1273
{
1274
  double destDistance;
1275
  Qgis::DistanceUnit destUnits;
1276

1277
  humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
10✔
1278

1279
  return QStringLiteral( "%1 %2" )
20✔
1280
         .arg( QString::number( destDistance, 'f', decimals ) )
20✔
1281
         .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
30✔
1282
}
1283

1284
void InputUtils::humanReadableDistance( double srcDistance, Qgis::DistanceUnit srcUnits,
14✔
1285
                                        Qgis::SystemOfMeasurement destSystem,
1286
                                        double &destDistance, Qgis::DistanceUnit &destUnits )
1287
{
1288
  if ( ( destSystem == Qgis::SystemOfMeasurement::Metric ) || ( destSystem == Qgis::SystemOfMeasurement::Unknown ) )
14✔
1289
  {
1290
    return formatToMetricDistance( srcDistance, srcUnits, destDistance, destUnits );
11✔
1291
  }
1292
  else if ( destSystem == Qgis::SystemOfMeasurement::Imperial )
3✔
1293
  {
1294
    return formatToImperialDistance( srcDistance, srcUnits, destDistance, destUnits );
2✔
1295
  }
1296
  else if ( destSystem == Qgis::SystemOfMeasurement::USCS )
1✔
1297
  {
1298
    return formatToUSCSDistance( srcDistance, srcUnits, destDistance, destUnits );
1✔
1299
  }
1300
  else
1301
  {
1302
    Q_ASSERT( false ); //should never happen
×
1303
  }
1304
}
1305

1306
void InputUtils::formatToMetricDistance( double srcDistance,
11✔
1307
    Qgis::DistanceUnit srcUnits,
1308
    double &destDistance,
1309
    Qgis::DistanceUnit &destUnits )
1310
{
1311
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Millimeters );
11✔
1312
  if ( dist < 0 )
11✔
1313
  {
1314
    destDistance = 0;
1✔
1315
    destUnits = Qgis::DistanceUnit::Millimeters;
1✔
1316
    return;
1✔
1317
  }
1318

1319
  double mmToKm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Kilometers, Qgis::DistanceUnit::Millimeters );
10✔
1320
  if ( dist > mmToKm )
10✔
1321
  {
1322
    destDistance = dist / mmToKm;
5✔
1323
    destUnits = Qgis::DistanceUnit::Kilometers;
5✔
1324
    return;
5✔
1325
  }
1326

1327
  double mmToM = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, Qgis::DistanceUnit::Millimeters );
5✔
1328
  if ( dist > mmToM )
5✔
1329
  {
1330
    destDistance = dist / mmToM;
4✔
1331
    destUnits = Qgis::DistanceUnit::Meters;
4✔
1332
    return;
4✔
1333
  }
1334

1335
  double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Centimeters, Qgis::DistanceUnit::Millimeters );
1✔
1336
  if ( dist > mmToCm )
1✔
1337
  {
1338
    destDistance = dist / mmToCm;
1✔
1339
    destUnits = Qgis::DistanceUnit::Centimeters;
1✔
1340
    return;
1✔
1341
  }
1342

1343
  destDistance = dist;
×
1344
  destUnits = Qgis::DistanceUnit::Millimeters;
×
1345
}
1346

1347
void InputUtils::formatToImperialDistance( double srcDistance,
2✔
1348
    Qgis::DistanceUnit srcUnits,
1349
    double &destDistance,
1350
    Qgis::DistanceUnit &destUnits )
1351
{
1352
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
2✔
1353
  if ( dist < 0 )
2✔
1354
  {
1355
    destDistance = 0;
×
1356
    destUnits = Qgis::DistanceUnit::Feet;
×
1357
    return;
×
1358
  }
1359

1360
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Miles, Qgis::DistanceUnit::Feet );
2✔
1361
  if ( dist > feetToMile )
2✔
1362
  {
1363
    destDistance = dist / feetToMile;
1✔
1364
    destUnits = Qgis::DistanceUnit::Miles;
1✔
1365
    return;
1✔
1366
  }
1367

1368
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
1✔
1369
  if ( dist > feetToYard )
1✔
1370
  {
1371
    destDistance = dist / feetToYard;
1✔
1372
    destUnits = Qgis::DistanceUnit::Yards;
1✔
1373
    return;
1✔
1374
  }
1375

1376
  destDistance = dist;
×
1377
  destUnits = Qgis::DistanceUnit::Feet;
×
1378
  return;
×
1379
}
1380

1381
void InputUtils::formatToUSCSDistance( double srcDistance,
1✔
1382
                                       Qgis::DistanceUnit srcUnits,
1383
                                       double &destDistance,
1384
                                       Qgis::DistanceUnit &destUnits )
1385
{
1386
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
1✔
1387
  if ( dist < 0 )
1✔
1388
  {
1389
    destDistance = 0;
×
1390
    destUnits = Qgis::DistanceUnit::Feet;
×
1391
    return;
×
1392
  }
1393

1394
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::NauticalMiles, Qgis::DistanceUnit::Feet );
1✔
1395
  if ( dist > feetToMile )
1✔
1396
  {
1397
    destDistance = dist / feetToMile;
1✔
1398
    destUnits = Qgis::DistanceUnit::NauticalMiles;
1✔
1399
    return;
1✔
1400
  }
1401

1402
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
×
1403
  if ( dist > feetToYard )
×
1404
  {
1405
    destDistance = dist / feetToYard;
×
1406
    destUnits = Qgis::DistanceUnit::Yards;
×
1407
    return;
×
1408
  }
1409

1410
  destDistance = dist;
×
1411
  destUnits = Qgis::DistanceUnit::Feet;
×
1412
  return;
×
1413
}
1414

1415
QString InputUtils::dumpScreenInfo() const
1✔
1416
{
1417
  QString msg;
1✔
1418
  // take the first top level window
1419
  const QWindowList windows = QGuiApplication::topLevelWindows();
1✔
1420
  if ( !windows.isEmpty() )
1✔
1421
  {
1422
    QScreen *screen = windows.at( 0 )->screen();
×
1423
    double dpiX = screen->physicalDotsPerInchX();
×
1424
    double dpiY = screen->physicalDotsPerInchY();
×
1425
    int height = screen->geometry().height();
×
1426
    int width = screen->geometry().width();
×
1427
    double sizeX = static_cast<double>( width ) / dpiX * 25.4;
×
1428
    double sizeY = static_cast<double>( height ) / dpiY * 25.4;
×
1429

1430
    msg += tr( "screen resolution: %1x%2 px\n" ).arg( width ).arg( height );
×
1431
    msg += tr( "screen DPI: %1x%2\n" ).arg( dpiX ).arg( dpiY );
×
1432
    msg += tr( "screen size: %1x%2 mm\n" ).arg( QString::number( sizeX, 'f', 0 ), QString::number( sizeY, 'f', 0 ) );
×
1433
    msg += tr( "reported device pixel ratio: %1\n" ).arg( screen->devicePixelRatio() );
×
1434
    msg += tr( "calculated device pixel ratio: %1\n" ).arg( calculateScreenDpr() );
×
1435
    msg += tr( "used dp scale: %1" ).arg( calculateDpRatio() );
×
1436
  }
1437
  else
1438
  {
1439
    msg += QLatin1String( "screen info: application is not initialized!" );
1✔
1440
  }
1441
  return msg;
2✔
1442
}
1✔
1443

1444
QString InputUtils::evaluateExpression( const FeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
1✔
1445
{
1446
  QList<QgsExpressionContextScope *> scopes;
1✔
1447
  scopes << QgsExpressionContextUtils::globalScope();
1✔
1448
  scopes << QgsExpressionContextUtils::projectScope( activeProject );
1✔
1449
  scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
1✔
1450

1451
  QgsExpressionContext context( scopes );
1✔
1452
  context.setFeature( pair.feature() );
1✔
1453
  QgsExpression expr( expression );
1✔
1454
  return expr.evaluate( &context ).toString();
3✔
1455
}
1✔
1456

1457
QString InputUtils::fieldType( const QgsField &field )
×
1458
{
1459
  return QVariant( field.type() ).typeName();
×
1460
}
1461

1462
QString InputUtils::dateTimeFieldFormat( const QString &fieldFormat )
×
1463
{
1464
  if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
×
1465
  {
1466
    return QString( "Date" );
×
1467
  }
1468
  else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
×
1469
  {
1470
    return QString( "Time" );
×
1471
  }
1472
  // cppcheck-suppress duplicateBranch
1473
  else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
×
1474
  {
1475
    return QString( "Date Time" );
×
1476
  }
1477
  else
1478
  {
1479
    return QString( "Date Time" );
×
1480
  }
1481
}
1482

1483
bool InputUtils::isFeatureIdValid( qint64 featureId )
2✔
1484
{
1485
  return !FID_IS_NEW( featureId ) && !FID_IS_NULL( featureId );
2✔
1486
}
1487

1488
QgsRectangle InputUtils::stakeoutPathExtent(
5✔
1489
  MapPosition *mapPosition,
1490
  const FeatureLayerPair &targetFeature,
1491
  InputMapSettings *mapSettings,
1492
  double mapExtentOffset
1493
)
1494
{
1495
  if ( !mapPosition || !mapSettings || !targetFeature.isValid() )
5✔
1496
    return QgsRectangle();
×
1497

1498
  QgsRectangle extent = mapSettings->extent();
5✔
1499

1500
  // We currently support only point geometries
1501
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
5✔
1502
    return extent;
×
1503

1504
  if ( !mapPosition->positionKit() || !mapPosition->mapSettings() )
5✔
1505
    return extent;
×
1506

1507
  //
1508
  // In order to compute stakeout extent, we first compute distance to target feature and
1509
  // based on that we update the extent and scale. Logic for scale computation is in distanceToScale function.
1510
  // Moreover, when distance to target point is lower then 1 meter, extent is centered to target point, otherwise
1511
  // it is centered to GPS position. This has been added in order to reduce "jumps" of canvas when user is near the target.
1512
  //
1513

1514
  QgsPoint gpsPointRaw = mapPosition->positionKit()->positionCoordinate();
5✔
1515

1516
  qreal distance = distanceBetweenGpsAndFeature( gpsPointRaw, targetFeature, mapSettings );
5✔
1517
  qreal scale = distanceToScale( distance );
5✔
1518
  qreal panelOffset = 0; // (px) used as an offset in order to center point in visible extent (we center to gpsPoint/target point + this offset)
5✔
1519

1520
  if ( mapExtentOffset > 0 )
5✔
1521
  {
1522
    panelOffset = mapExtentOffset / 2.0;
×
1523
  }
1524

1525
  if ( distance <= 1 )
5✔
1526
  {
1527
    // center to target point
1528
    QgsPoint targetPointRaw( extractPointFromFeature( targetFeature ) );
1✔
1529
    QgsPointXY targetPointInMapCRS = transformPoint(
2✔
1530
                                       targetFeature.layer()->crs(),
2✔
1531
                                       mapSettings->destinationCrs(),
2✔
1532
                                       mapSettings->transformContext(),
2✔
1533
                                       targetPointRaw
1534
                                     );
1✔
1535

1536
    if ( targetPointInMapCRS.isEmpty() )
1✔
1537
    {
1538
      // unsuccessful transform
1539
      return extent;
×
1540
    }
1541

1542
    QgsPointXY targetPointInCanvasXY = mapSettings->coordinateToScreen( QgsPoint( targetPointInMapCRS ) );
1✔
1543
    QgsPointXY centerInCanvasXY( targetPointInCanvasXY.x(), targetPointInCanvasXY.y() + panelOffset );
1✔
1544
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
1✔
1545

1546
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
1✔
1547
  }
1✔
1548
  else
1549
  {
1550
    // center to GPS position
1551
    QgsPointXY gpsPointInCanvasXY = mapPosition->screenPosition();
4✔
1552
    QgsPointXY centerInCanvasXY( gpsPointInCanvasXY.x(), gpsPointInCanvasXY.y() + panelOffset );
4✔
1553
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
4✔
1554

1555
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
4✔
1556
  }
1557

1558
  return extent;
5✔
1559
}
5✔
1560

1561
QgsGeometry InputUtils::stakeoutGeometry( const QgsPoint &mapPosition, const FeatureLayerPair &target, InputMapSettings *mapSettings )
×
1562
{
1563
  if ( !mapSettings || !target.isValid() )
×
1564
    return QgsGeometry();
×
1565

1566
  QgsPointXY targetInLayerCoordinates = target.feature().geometry().asPoint();
×
1567
  QgsPointXY t = transformPointXY( target.layer()->crs(), mapSettings->destinationCrs(), mapSettings->transformContext(), targetInLayerCoordinates );
×
1568

1569
  QVector<QgsPoint> points { mapPosition, QgsPoint( t ) };
×
1570

1571
  return QgsGeometry::fromPolyline( points );
×
1572
}
×
1573

1574
qreal InputUtils::distanceToScale( qreal distance )
29✔
1575
{
1576
  // Stakeout extent scale is computed based on these (empirically found) conditions:
1577
  //   - if distance is > 10m, use 1:205 scale (~ 5m on mobile)
1578
  //   - if distance is 3-10m, use 1:105 scale (~ 2m on mobile)
1579
  //   - if distance is 1-3m,  use 1:55 scale  (~ 1m on mobile)
1580
  //   - if distance is < 1m,  use 1:25 scale  (~ 0.5m on mobile)
1581

1582
  qreal scale = 205;
29✔
1583

1584
  if ( distance <= 1 )
29✔
1585
  {
1586
    scale = 25;
9✔
1587
  }
1588
  else if ( distance <= 3 && distance > 1 )
20✔
1589
  {
1590
    scale = 55;
7✔
1591
  }
1592
  else if ( distance <= 10 && distance > 3 )
13✔
1593
  {
1594
    scale = 105;
5✔
1595
  }
1596

1597
  return scale;
29✔
1598
}
1599

1600
qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
6✔
1601
{
1602
  if ( !mapSettings || !targetFeature.isValid() )
6✔
1603
    return -1;
×
1604

1605
  // We calculate distance only between points
1606
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
6✔
1607
    return -1;
×
1608

1609
  // Transform gps position to map CRS
1610
  QgsPointXY transformedPosition = transformPoint(
12✔
1611
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
12✔
1612
                                     mapSettings->destinationCrs(),
12✔
1613
                                     mapSettings->transformContext(),
12✔
1614
                                     gpsPosition
1615
                                   );
6✔
1616

1617
  if ( transformedPosition.isEmpty() )
6✔
1618
  {
1619
    return -1;
×
1620
  }
1621

1622
  // Transform target point to map CRS
1623
  QgsPoint target( extractPointFromFeature( targetFeature ) );
6✔
1624
  QgsPointXY transformedTarget = transformPoint(
12✔
1625
                                   targetFeature.layer()->crs(),
12✔
1626
                                   mapSettings->destinationCrs(),
12✔
1627
                                   mapSettings->transformContext(),
12✔
1628
                                   target
1629
                                 );
6✔
1630

1631
  if ( transformedTarget.isEmpty() )
6✔
1632
  {
1633
    return -1;
×
1634
  }
1635

1636
  QgsDistanceArea distanceArea;
6✔
1637
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
6✔
1638

1639
  qreal distance = distanceArea.measureLine( transformedPosition, transformedTarget );
6✔
1640
  distance = distanceArea.convertLengthMeasurement( distance, Qgis::DistanceUnit::Meters );
6✔
1641

1642
  return distance;
6✔
1643
}
6✔
1644

1645
qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
1✔
1646
{
1647
  if ( !mapSettings || !targetFeature.isValid() )
1✔
1648
    return -1;
×
1649

1650
  QgsVectorLayer *layer = targetFeature.layer();
1✔
1651
  QgsFeature f = targetFeature.feature();
1✔
1652

1653
  // Only points are supported
1654
  if ( layer->geometryType() != Qgis::GeometryType::Point )
1✔
1655
    return -1;
×
1656

1657
  // Transform gps position to map CRS
1658
  QgsPointXY transformedPosition = transformPoint(
2✔
1659
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
2✔
1660
                                     mapSettings->destinationCrs(),
2✔
1661
                                     mapSettings->transformContext(),
2✔
1662
                                     gpsPoint
1663
                                   );
1✔
1664

1665
  if ( transformedPosition.isEmpty() )
1✔
1666
  {
1667
    return -1;
×
1668
  }
1669

1670
  // Transform target point to map CRS
1671
  QgsPoint target( extractPointFromFeature( targetFeature ) );
1✔
1672
  QgsPointXY transformedTarget = transformPoint(
2✔
1673
                                   targetFeature.layer()->crs(),
2✔
1674
                                   mapSettings->destinationCrs(),
2✔
1675
                                   mapSettings->transformContext(),
2✔
1676
                                   target
1677
                                 );
1✔
1678

1679
  if ( transformedTarget.isEmpty() )
1✔
1680
  {
1681
    return -1;
×
1682
  }
1683

1684
  QgsDistanceArea distanceArea;
1✔
1685
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
1✔
1686

1687
  return distanceArea.bearing( transformedPosition, transformedTarget );
1✔
1688
}
1✔
1689

1690
QString InputUtils::featureTitle( const FeatureLayerPair &pair, QgsProject *project )
×
1691
{
1692
  if ( !project || !pair.isValid() )
×
1693
    return QString();
×
1694

1695
  QString title;
×
1696

1697
  QgsVectorLayer *layer = pair.layer();
×
1698

1699
  // can't use QgsExpressionContextUtils::globalProjectLayerScopes() because it uses QgsProject::instance()
1700
  QList<QgsExpressionContextScope *> scopes;
×
1701
  scopes << QgsExpressionContextUtils::globalScope();
×
1702
  scopes << QgsExpressionContextUtils::projectScope( project );
×
1703
  scopes << QgsExpressionContextUtils::layerScope( layer );
×
1704

1705
  QgsExpressionContext context( scopes );
×
1706
  context.setFeature( pair.feature() );
×
1707
  QgsExpression expr( pair.layer()->displayExpression() );
×
1708
  title = expr.evaluate( &context ).toString();
×
1709

1710
  if ( title.isEmpty() )
×
1711
    title = QStringLiteral( "Feature %1" ).arg( pair.feature().id() );
×
1712

1713
  return title;
×
1714
}
×
1715

1716
FeatureLayerPair InputUtils::createFeatureLayerPair(
1✔
1717
  QgsVectorLayer *layer,
1718
  const QgsGeometry &geometry,
1719
  VariablesManager *variablesmanager,
1720
  QgsExpressionContextScope *additionalScope )
1721
{
1722
  if ( !layer )
1✔
1723
    return FeatureLayerPair();
×
1724

1725
  QgsAttributes attrs( layer->fields().count() );
1✔
1726
  QgsExpressionContext context = layer->createExpressionContext();
1✔
1727

1728
  if ( variablesmanager )
1✔
1729
    context << variablesmanager->positionScope();
×
1730

1731
  if ( additionalScope )
1✔
1732
    context << additionalScope;
1✔
1733

1734
  QgsFeature feat = QgsVectorLayerUtils::createFeature( layer, geometry, attrs.toMap(), &context );
1✔
1735
  return FeatureLayerPair( feat, layer );
1✔
1736
}
1✔
1737

1738
void InputUtils::createEditBuffer( QgsVectorLayer *layer )
×
1739
{
1740
  if ( layer )
×
1741
  {
1742
    if ( !layer->editBuffer() )
×
1743
    {
1744
      layer->startEditing();
×
1745
    }
1746
  }
1747
}
×
1748

1749
FeatureLayerPair InputUtils::changeFeaturePairGeometry( FeatureLayerPair featurePair, const QgsGeometry &geometry )
×
1750
{
1751
  QgsVectorLayer *vlayer = featurePair.layer();
×
1752
  if ( vlayer )
×
1753
  {
1754
    InputUtils::createEditBuffer( vlayer );
×
1755
    QgsGeometry g( geometry );
×
1756
    vlayer->changeGeometry( featurePair.feature().id(), g );
×
1757
    vlayer->triggerRepaint();
×
1758
    QgsFeature f = vlayer->getFeature( featurePair.feature().id() );
×
1759
    return FeatureLayerPair( f, featurePair.layer() );
×
1760
  }
×
1761
  else
1762
  {
1763
    // invalid pair
1764
    return FeatureLayerPair();
×
1765
  }
1766
}
1767

1768
QgsPointXY InputUtils::extractPointFromFeature( const FeatureLayerPair &feature )
11✔
1769
{
1770
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
11✔
1771
    return QgsPointXY();
2✔
1772

1773
  QgsFeature f = feature.feature();
9✔
1774
  const QgsAbstractGeometry *g = f.geometry().constGet();
9✔
1775

1776
  return QgsPoint( dynamic_cast< const QgsPoint * >( g )->toQPointF() );
18✔
1777
}
9✔
1778

1779
bool InputUtils::isPointLayerFeature( const FeatureLayerPair &feature )
4✔
1780
{
1781
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
4✔
1782
    return false;
3✔
1783
  const QgsAbstractGeometry *g = feature.feature().geometry().constGet();
1✔
1784
  const QgsPoint *point = dynamic_cast< const QgsPoint * >( g );
1✔
1785
  return point != nullptr;
1✔
1786
}
1787

1788
void InputUtils::zoomToProject( QgsProject *qgsProject, InputMapSettings *mapSettings )
×
1789
{
1790
  if ( !qgsProject || !mapSettings )
×
1791
  {
1792
    qDebug() << "Cannot zoom to extent, MapSettings or QgsProject is not defined";
×
1793
    return;
×
1794
  }
1795
  QgsRectangle extent;
×
1796

1797
  QgsProjectViewSettings *viewSettings = qgsProject->viewSettings();
×
1798
  extent = viewSettings->presetFullExtent();
×
1799
  if ( extent.isNull() )
×
1800
  {
1801
    bool hasWMS;
1802
    QStringList WMSExtent = qgsProject->readListEntry( "WMSExtent", QStringLiteral( "/" ), QStringList(), &hasWMS );
×
1803

1804
    if ( hasWMS && ( WMSExtent.length() == 4 ) )
×
1805
    {
1806
      extent.set( WMSExtent[0].toDouble(), WMSExtent[1].toDouble(), WMSExtent[2].toDouble(), WMSExtent[3].toDouble() );
×
1807
    }
1808
    else // set layers extent
1809
    {
1810
      const QVector<QgsMapLayer *> layers = qgsProject->layers<QgsMapLayer *>();
×
1811
      for ( const QgsMapLayer *layer : layers )
×
1812
      {
1813
        QgsRectangle layerExtent = mapSettings->mapSettings().layerExtentToOutputExtent( layer, layer->extent() );
×
1814
        extent.combineExtentWith( layerExtent );
×
1815
      }
1816
    }
×
1817
  }
×
1818

1819
  if ( extent.isEmpty() )
×
1820
  {
1821
    extent.grow( qgsProject->crs().isGeographic() ? 0.01 : 1000.0 );
×
1822
  }
1823
  extent.scale( 1.05 );
×
1824
  mapSettings->setExtent( extent );
×
1825
}
1826

1827
QString InputUtils::loadIconFromLayer( QgsMapLayer *layer )
18✔
1828
{
1829
  if ( !layer )
18✔
1830
    return QString();
×
1831

1832
  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
18✔
1833

1834
  if ( vectorLayer )
18✔
1835
  {
1836
    Qgis::GeometryType geometry = vectorLayer->geometryType();
17✔
1837
    return iconFromGeometry( geometry );
17✔
1838
  }
1839
  else
1840
    return QString( "qrc:/mIconRasterLayer.svg" );
1✔
1841
}
1842

1843
QString InputUtils::loadIconFromFeature( QgsFeature feature )
4✔
1844
{
1845
  return iconFromGeometry( feature.geometry().type() );
8✔
1846
}
1847

1848
QString InputUtils::iconFromGeometry( const Qgis::GeometryType &geometry )
21✔
1849
{
1850
  switch ( geometry )
21✔
1851
  {
1852
    case Qgis::GeometryType::Point: return QString( "qrc:/mIconPointLayer.svg" );
6✔
1853
    case Qgis::GeometryType::Line: return QString( "qrc:/mIconLineLayer.svg" );
6✔
1854
    case Qgis::GeometryType::Polygon: return QString( "qrc:/mIconPolygonLayer.svg" );
6✔
1855
    default: return QString( "qrc:/mIconTableLayer.svg" );
3✔
1856
  }
1857
}
1858

1859
bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
×
1860
{
1861
  int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
×
1862
  return ImageUtils::rescale( path, quality );
×
1863
}
1864

1865
QgsGeometry InputUtils::createGeometryForLayer( QgsVectorLayer *layer )
36✔
1866
{
1867
  QgsGeometry geometry;
36✔
1868

1869
  if ( !layer )
36✔
1870
  {
1871
    return geometry;
×
1872
  }
1873

1874
  bool isMulti = QgsWkbTypes::isMultiType( layer->wkbType() );
36✔
1875

1876
  switch ( layer->geometryType() )
36✔
1877
  {
1878
    case Qgis::GeometryType::Point:
15✔
1879
    {
1880
      if ( isMulti )
15✔
1881
      {
1882
        QgsMultiPoint *multiPoint = new QgsMultiPoint();
3✔
1883
        geometry.set( multiPoint );
3✔
1884
      }
1885
      else
1886
      {
1887
        QgsPoint *point = new QgsPoint();
12✔
1888
        geometry.set( point );
12✔
1889
      }
1890
      break;
15✔
1891
    }
1892

1893
    case Qgis::GeometryType::Line:
10✔
1894
    {
1895
      if ( isMulti )
10✔
1896
      {
1897
        QgsMultiLineString *multiLine = new QgsMultiLineString();
4✔
1898
        geometry.set( multiLine );
4✔
1899
      }
1900
      else
1901
      {
1902
        QgsLineString *line = new QgsLineString();
6✔
1903
        geometry.set( line );
6✔
1904
      }
1905
      break;
10✔
1906
    }
1907

1908
    case Qgis::GeometryType::Polygon:
11✔
1909
    {
1910
      if ( isMulti )
11✔
1911
      {
1912
        QgsLineString *line = new QgsLineString();
2✔
1913
        QgsPolygon *polygon = new QgsPolygon( line );
2✔
1914
        QgsMultiPolygon *multiPolygon = new QgsMultiPolygon();
2✔
1915
        multiPolygon->addGeometry( polygon );
2✔
1916
        geometry.set( multiPolygon );
2✔
1917
      }
1918
      else
1919
      {
1920
        QgsLineString *line = new QgsLineString();
9✔
1921
        QgsPolygon *polygon = new QgsPolygon( line );
9✔
1922
        geometry.set( polygon );
9✔
1923
      }
1924
      break;
11✔
1925
    }
1926

1927
    default:
×
1928
      break;
×
1929
  }
1930

1931
  if ( QgsWkbTypes::hasZ( layer->wkbType() ) )
36✔
1932
  {
1933
    geometry.get()->addZValue( 0 );
12✔
1934
  }
1935

1936
  if ( QgsWkbTypes::hasM( layer->wkbType() ) )
36✔
1937
  {
1938
    geometry.get()->addMValue( 0 );
7✔
1939
  }
1940

1941
  return geometry;
36✔
1942
}
×
1943

1944
QString InputUtils::invalidGeometryWarning( QgsVectorLayer *layer )
6✔
1945
{
1946
  QString msg;
6✔
1947
  if ( !layer )
6✔
1948
  {
1949
    return msg;
×
1950
  }
1951

1952
  int nPoints = 1;
6✔
1953
  if ( layer->geometryType() == Qgis::GeometryType::Line )
6✔
1954
  {
1955
    nPoints = 2;
2✔
1956
  }
1957
  else if ( layer->geometryType() == Qgis::GeometryType::Polygon )
4✔
1958
  {
1959
    nPoints = 3;
2✔
1960
  }
1961

1962
  if ( QgsWkbTypes::isMultiType( layer->wkbType() ) )
6✔
1963
  {
1964
    return tr( "You need to add at least %1 point(s) to every part." ).arg( nPoints );
3✔
1965
  }
1966
  else
1967
  {
1968
    return tr( "You need to add at least %1 point(s)." ).arg( nPoints );
3✔
1969
  }
1970
}
6✔
1971

1972
void InputUtils::updateFeature( const FeatureLayerPair &pair )
×
1973
{
1974
  if ( !pair.layer() )
×
1975
  {
1976
    return;
×
1977
  }
1978

1979
  if ( !pair.feature().isValid() )
×
1980
  {
1981
    return;
×
1982
  }
1983

1984
  if ( !pair.layer()->isEditable() )
×
1985
  {
1986
    pair.layer()->startEditing();
×
1987
  }
1988

1989
  QgsFeature f( pair.feature() );
×
1990
  pair.layer()->updateFeature( f );
×
1991
  pair.layer()->commitChanges();
×
1992
  pair.layer()->triggerRepaint();
×
1993
}
×
1994

1995
QString InputUtils::imageGalleryLocation()
×
1996
{
1997
  QStringList galleryPaths = QStandardPaths::standardLocations( QStandardPaths::PicturesLocation );
×
1998

1999
  if ( galleryPaths.isEmpty() )
×
2000
  {
2001
    CoreUtils::log( QStringLiteral( "Image Picker" ), QStringLiteral( "Could not find standard path to image gallery" ) );
×
2002
    return QString();
×
2003
  }
2004

2005
  return galleryPaths.last();
×
2006
}
×
2007

2008
QString InputUtils::layerAttribution( QgsMapLayer *layer )
2✔
2009
{
2010
  if ( !layer || !layer->isValid() )
2✔
2011
  {
2012
    return QString();
×
2013
  }
2014

2015
  QStringList rights = layer->metadata().rights();
2✔
2016
  if ( !rights.isEmpty() )
2✔
2017
  {
2018
    return rights.join( QStringLiteral( ", " ) );
2✔
2019
  }
2020

2021
  return QString();
1✔
2022
}
2✔
2023

2024
const double PROFILER_THRESHOLD = 0.001;
2025
static double qgsRuntimeProfilerExtractModelAsText( QStringList &lines, const QString &group, const QModelIndex &parent, int level )
×
2026
{
2027
  double total_elapsed = 0.0;
×
2028

2029
  const int rc = QgsApplication::profiler()->rowCount( parent );
×
2030
  for ( int r = 0; r < rc; r++ )
×
2031
  {
2032
    QModelIndex rowIndex = QgsApplication::profiler()->index( r, 0, parent );
×
2033
    if ( QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Group ).toString() != group )
×
2034
      continue;
×
2035
    bool ok;
2036
    double elapsed = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Elapsed ).toDouble( &ok );
×
2037
    if ( !ok )
×
2038
      elapsed = 0.0;
×
2039
    total_elapsed += elapsed;
×
2040

2041
    if ( elapsed > PROFILER_THRESHOLD )
×
2042
    {
2043
      QString name = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Name ).toString();
×
2044
      lines << QStringLiteral( "  %1 %2: %3 sec" ).arg( QStringLiteral( ">" ).repeated( level + 1 ),  name, QString::number( elapsed, 'f', 3 ) );
×
2045
    }
×
2046
    total_elapsed += qgsRuntimeProfilerExtractModelAsText( lines, group, rowIndex, level + 1 );
×
2047
  }
2048
  return total_elapsed;
×
2049
}
2050

2051
QVector<QString> InputUtils::qgisProfilerLog()
×
2052
{
2053
  QVector<QString> lines;
×
2054
  const QString project = QgsProject::instance()->fileName();
×
2055

2056
  if ( !project.isEmpty() )
×
2057
  {
2058
    lines << QStringLiteral( "QgsProject filename: %1" ).arg( project );
×
2059
  }
2060

2061
  lines << QStringLiteral( "List of QgsRuntimeProfiler events above %1 sec" ).arg( QString::number( PROFILER_THRESHOLD, 'f', 3 ) );
×
2062

2063
  const auto groups = QgsApplication::profiler()->groups();
×
2064
  for ( const QString &g : groups )
×
2065
  {
2066
    QVector<QString> groupLines;
×
2067
    double elapsed = qgsRuntimeProfilerExtractModelAsText( groupLines, g, QModelIndex(), 0 );
×
2068
    if ( elapsed > PROFILER_THRESHOLD )
×
2069
    {
2070
      lines << QStringLiteral( "  %1: total %2 sec" ).arg( g, QString::number( elapsed, 'f', 3 ) );
×
2071
      lines << groupLines;
×
2072
    }
2073
  }
×
2074
  return lines;
×
2075
}
×
2076

2077
QList<QgsPoint> InputUtils::parsePositionUpdates( const QString &data )
8✔
2078
{
2079
  QList<QgsPoint> parsedUpdates;
8✔
2080
  QStringList positions = data.split( '\n', Qt::SkipEmptyParts );
8✔
2081

2082
  if ( positions.isEmpty() )
8✔
2083
  {
2084
    return parsedUpdates;
2✔
2085
  }
2086

2087
  for ( int ix = 0; ix < positions.size(); ix++ )
14✔
2088
  {
2089
    QStringList coordinates = positions[ix].split( ' ', Qt::SkipEmptyParts );
8✔
2090

2091
    if ( coordinates.size() != 4 )
8✔
2092
    {
2093
      continue;
4✔
2094
    }
2095

2096
    QgsPoint geop;
4✔
2097
    geop.setX( coordinates[0].toDouble() ); // long
4✔
2098
    geop.setY( coordinates[1].toDouble() ); // lat
4✔
2099
    geop.setZ( coordinates[2].toDouble() ); // alt
4✔
2100
    geop.setM( coordinates[3].toDouble() ); // UTC time in secs
4✔
2101
    parsedUpdates << geop;
4✔
2102
  }
8✔
2103

2104
  return parsedUpdates;
6✔
2105
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc