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

MerginMaps / input / 6274148729

22 Sep 2023 12:18PM UTC coverage: 61.904% (-0.1%) from 62.05%
6274148729

Pull #2797

github

PeterPetrik
fix code
Pull Request #2797: Profiler

7577 of 12240 relevant lines covered (61.9%)

102.31 hits per line

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

53.66
/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 )
7✔
77
{
78
  QFile file( filePath );
7✔
79
  return file.remove( filePath );
14✔
80
}
7✔
81

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

90
  QFileInfo fi( dstPath );
3✔
91
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
3✔
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 );
3✔
101
}
3✔
102

103
bool InputUtils::createDirectory( const QString &path )
4✔
104
{
105
  QDir dir;
4✔
106
  return dir.mkpath( path );
8✔
107
}
4✔
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::formatDateTimeDiff( const QDateTime &tMin, const QDateTime &tMax )
15✔
164
{
165
  qint64 daysDiff = tMin.daysTo( tMax );
15✔
166

167
  // datetime is invalid
168
  if ( daysDiff < 0 )
15✔
169
  {
170
    return INVALID_DATETIME_STR;
×
171
  }
172

173
  // diff is maximum one day
174
  // Note that difference from 23:55 to 0:05 the next day counts as one day
175
  if ( daysDiff == 0 || daysDiff == 1 )
15✔
176
  {
177
    qint64 secsDiff = tMin.secsTo( tMax );
8✔
178
    if ( secsDiff < 0 )
8✔
179
    {
180
      return INVALID_DATETIME_STR;
1✔
181
    }
182
    else if ( secsDiff < 60 )
7✔
183
    {
184
      return tr( "just now" );
2✔
185
    }
186
    else if ( secsDiff < 60 * 60 )
5✔
187
    {
188
      int period = secsDiff / 60 ;
2✔
189
      return ( period > 1 ) ? tr( "%1 minutes ago" ).arg( period ) : tr( "%1 minute ago" ).arg( period );
2✔
190
    }
191
    else if ( secsDiff < 60 * 60 * 24 )
3✔
192
    {
193
      int period = secsDiff / ( 60 * 60 );
2✔
194
      return ( period > 1 ) ? tr( "%1 hours ago" ).arg( period ) : tr( "%1 hour ago" ).arg( period );
2✔
195
    }
196
    else
197
    {
198
      return ( daysDiff > 1 ) ? tr( "%1 days ago" ).arg( daysDiff ) : tr( "%1 day ago" ).arg( daysDiff );
1✔
199
    }
200
  }
201
  else if ( daysDiff < 7 )
7✔
202
  {
203
    return ( daysDiff > 1 ) ? tr( "%1 days ago" ).arg( daysDiff ) : tr( "%1 day ago" ).arg( daysDiff );
1✔
204
  }
205
  else if ( daysDiff < 31 )
6✔
206
  {
207
    int period = daysDiff / 7;
2✔
208
    return ( period > 1 ) ? tr( "%1 weeks ago" ).arg( period ) : tr( "%1 week ago" ).arg( period );
2✔
209
  }
210
  else if ( daysDiff < 365 )
4✔
211
  {
212
    int period = daysDiff / 31;
2✔
213
    return ( period > 1 ) ? tr( "%1 months ago" ).arg( period ) : tr( "%1 month ago" ).arg( period );
2✔
214
  }
215
  else
216
  {
217
    int period = daysDiff / 365;
2✔
218
    return ( period > 1 ) ? tr( "%1 years ago" ).arg( period ) : tr( "%1 year ago" ).arg( period );
2✔
219
  }
220

221
  return INVALID_DATETIME_STR;
222
}
223

224
void InputUtils::setExtentToFeature( const FeatureLayerPair &pair, InputMapSettings *mapSettings, double panelOffsetRatio )
×
225
{
226

227
  if ( !mapSettings )
×
228
    return;
×
229

230
  if ( !pair.layer() )
×
231
    return;
×
232

233
  if ( !pair.feature().isValid() )
×
234
    return;
×
235

236
  QgsGeometry geom = pair.feature().geometry();
×
237
  if ( geom.isNull() || !geom.constGet() )
×
238
    return;
×
239

240
  QgsRectangle bbox = mapSettings->mapSettings().layerExtentToOutputExtent( pair.layer(), geom.boundingBox() );
×
241
  QgsRectangle currentExtent = mapSettings->mapSettings().extent();
×
242
  QgsPointXY currentExtentCenter = currentExtent.center();
×
243
  QgsPointXY featureCenter = bbox.center();
×
244

245
  double panelOffset = ( currentExtent.yMaximum() - currentExtent.yMinimum() ) * panelOffsetRatio / 2;
×
246
  double offsetX = currentExtentCenter.x() - featureCenter.x();
×
247
  double offsetY = currentExtentCenter.y() - featureCenter.y();
×
248
  currentExtent.setXMinimum( currentExtent.xMinimum() - offsetX );
×
249
  currentExtent.setXMaximum( currentExtent.xMaximum() - offsetX );
×
250
  currentExtent.setYMinimum( currentExtent.yMinimum() - offsetY - panelOffset );
×
251
  currentExtent.setYMaximum( currentExtent.yMaximum() - offsetY - panelOffset );
×
252
  mapSettings->setExtent( currentExtent );
×
253
}
×
254

255
double InputUtils::convertCoordinateString( const QString &rationalValue )
×
256
{
257
  QStringList values = rationalValue.split( "," );
×
258
  if ( values.size() != 3 ) return 0;
×
259

260
  double degrees = ratherZeroThanNaN( convertRationalNumber( values.at( 0 ) ) );
×
261
  double minutes = ratherZeroThanNaN( convertRationalNumber( values.at( 1 ) ) );
×
262
  double seconds = ratherZeroThanNaN( convertRationalNumber( values.at( 2 ) ) );
×
263

264
  double result = degrees + minutes / 60 + seconds / 3600;
×
265
  return result;
×
266
}
×
267

268
QString InputUtils::degreesString( const QgsPoint &point )
×
269
{
270
  if ( point.isEmpty() )
×
271
  {
272
    return QLatin1String();
×
273
  }
274

275
  // QGeoCoordinate formatter uses lat/long order, but we (and QGIS) use long/lat order,
276
  // so here we need to first pass y and then x.
277
  return QGeoCoordinate( point.y(), point.x() ).toString( QGeoCoordinate::DegreesMinutesWithHemisphere );
×
278
}
279

280
double InputUtils::convertRationalNumber( const QString &rationalValue )
×
281
{
282
  if ( rationalValue.isEmpty() )
×
283
    return std::numeric_limits<double>::quiet_NaN();
×
284

285
  QStringList number = rationalValue.split( "/" );
×
286
  if ( number.size() != 2 )
×
287
    return std::numeric_limits<double>::quiet_NaN();
×
288

289
  double numerator = number.at( 0 ).toDouble();
×
290
  double denominator = number.at( 1 ).toDouble();
×
291
  if ( denominator == 0 )
×
292
    return denominator;
×
293

294
  return numerator / denominator;
×
295
}
×
296

297
double InputUtils::mapSettingsScale( InputMapSettings *ms )
×
298
{
299
  if ( !ms ) return 1;
×
300
  return 1 / ms->mapUnitsPerPixel();
×
301
}
302

303
double InputUtils::mapSettingsOffsetX( InputMapSettings *ms )
×
304
{
305
  if ( !ms ) return 0;
×
306
  return -ms->visibleExtent().xMinimum();
×
307
}
308

309
double InputUtils::mapSettingsOffsetY( InputMapSettings *ms )
×
310
{
311
  if ( !ms ) return 0;
×
312
  return -ms->visibleExtent().yMaximum();
×
313
}
314

315
double InputUtils::mapSettingsDPR( InputMapSettings *ms )
×
316
{
317
  if ( !ms ) return 1;
×
318
  return ms->devicePixelRatio();
×
319
}
320

321
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, const QgsCoordinateReferenceSystem &destinationCRS, const QgsCoordinateTransformContext &context )
1✔
322
{
323
  QgsGeometry g( geometry );
1✔
324

325
  QgsCoordinateTransform ct( sourceCRS, destinationCRS, context );
1✔
326
  if ( !ct.isShortCircuited() )
1✔
327
  {
328
    try
329
    {
330
      g.transform( ct );
×
331
    }
332
    catch ( QgsCsException &e )
×
333
    {
334
      Q_UNUSED( e )
335
      return QgsGeometry();
×
336
    }
×
337
  }
338

339
  return g;
1✔
340
}
1✔
341

342
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, QgsVectorLayer *targetLayer )
1✔
343
{
344
  if ( !targetLayer || !targetLayer->isValid() )
1✔
345
  {
346
    return QgsGeometry();
×
347
  }
348

349
  return transformGeometry( geometry, sourceCRS, targetLayer->crs(), targetLayer->transformContext() );
2✔
350
}
351

352
QgsGeometry InputUtils::transformGeometryToMapWithLayer( const QgsGeometry &geometry, QgsVectorLayer *sourceLayer, InputMapSettings *targetSettings )
×
353
{
354
  if ( !sourceLayer || !sourceLayer->isValid() || !targetSettings )
×
355
  {
356
    return QgsGeometry();
×
357
  }
358

359
  return transformGeometry( geometry, sourceLayer->crs(), targetSettings->destinationCrs(), targetSettings->transformContext() );
×
360
}
361

362
QgsGeometry InputUtils::transformGeometryToMapWithCRS( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, InputMapSettings *targetSettings )
×
363
{
364
  if ( !targetSettings )
×
365
  {
366
    return QgsGeometry();
×
367
  }
368

369
  return transformGeometry( geometry, sourceCRS, targetSettings->destinationCrs(), targetSettings->transformContext() );
×
370
}
371

372
QgsGeometry InputUtils::extractGeometry( const FeatureLayerPair &pair )
×
373
{
374
  if ( !pair.isValid() )
×
375
    return QgsGeometry();
×
376

377
  return pair.feature().geometry();
×
378
}
379

380
QString InputUtils::geometryLengthAsString( const QgsGeometry &geometry )
×
381
{
382
  QgsDistanceArea distanceArea;
×
383
  distanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
×
384
  distanceArea.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() );
×
385

386
  qreal length = distanceArea.measureLength( geometry );
×
387

388
  if ( qgsDoubleNear( length, 0 ) )
×
389
  {
390
    return "0 m";
×
391
  }
392

393
  return distanceArea.formatDistance( length, 2, distanceArea.lengthUnits() );
×
394
}
×
395

396
static void addLineString( const QgsLineString *line, QVector<double> &data )
×
397
{
398
  data << line->numPoints();
×
399
  const double *x = line->xData();
×
400
  const double *y = line->yData();
×
401
  for ( int i = 0; i < line->numPoints(); ++i )
×
402
  {
403
    data << x[i] << y[i];
×
404
  }
405
}
×
406

407
static void addSingleGeometry( const QgsAbstractGeometry *geom, Qgis::GeometryType type, QVector<double> &data )
×
408
{
409
  switch ( type )
×
410
  {
411
    case Qgis::GeometryType::Point:
×
412
    {
413
      const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( geom );
×
414
      if ( point )
×
415
      {
416
        data << 0 << point->x() << point->y();
×
417
      }
418
      break;
×
419
    }
420

421
    case Qgis::GeometryType::Line:
×
422
    {
423
      const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom );
×
424
      if ( line )
×
425
      {
426
        data << 1;
×
427
        addLineString( line, data );
×
428
      }
429
      break;
×
430
    }
431

432
    case Qgis::GeometryType::Polygon:
×
433
    {
434
      const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( geom );
×
435
      if ( poly )
×
436
      {
437
        if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->exteriorRing() ) )
×
438
        {
439
          data << 2;
×
440
          addLineString( line, data );
×
441
        }
442
        for ( int i = 0; i < poly->numInteriorRings(); ++i )
×
443
        {
444
          if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->interiorRing( i ) ) )
×
445
          {
446
            data << 2;
×
447
            addLineString( line, data );
×
448
          }
449
        }
450
      }
451
      break;
×
452
    }
453

454
    case Qgis::GeometryType::Unknown:
×
455
    case Qgis::GeometryType::Null:
456
      break;
×
457
  }
458
}
×
459

460
QVector<double> InputUtils::extractGeometryCoordinates( const QgsGeometry &geometry )
×
461
{
462
  if ( geometry.isNull() )
×
463
    return QVector<double>();
×
464

465
  QVector<double> data;
×
466

467
  const QgsAbstractGeometry *geom = geometry.constGet();
×
468
  Qgis::GeometryType geomType = geometry.type();
×
469
  const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
×
470
  if ( collection && !collection->isEmpty() )
×
471
  {
472
    for ( int i = 0; i < collection->numGeometries(); ++i )
×
473
    {
474
      addSingleGeometry( collection->geometryN( i ), geomType, data );
×
475
    }
476
  }
477
  else
478
  {
479
    addSingleGeometry( geom, geomType, data );
×
480
  }
481

482
  return data;
×
483
}
×
484

485
QString InputUtils::filesToString( QList<MerginFile> files )
×
486
{
487
  QStringList resultList;
×
488
  for ( MerginFile file : files )
×
489
  {
490
    resultList << file.path;
×
491
  }
×
492
  return resultList.join( ", " );
×
493
}
×
494

495
QString InputUtils::bytesToHumanSize( double bytes )
8✔
496
{
497
  const int precision = 1;
8✔
498
  if ( bytes < 1e-5 )
8✔
499
  {
500
    return "0.0";
×
501
  }
502
  else if ( bytes < 1024.0 * 1024.0 )
8✔
503
  {
504
    return QString::number( bytes / 1024.0, 'f', precision ) + " KB";
×
505
  }
506
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 )
8✔
507
  {
508
    return QString::number( bytes / 1024.0 / 1024.0, 'f', precision ) + " MB";
×
509
  }
510
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 * 1024.0 )
8✔
511
  {
512
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " GB";
16✔
513
  }
514
  else
515
  {
516
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " TB";
×
517
  }
518
}
519

520
bool InputUtils::acquireCameraPermission()
×
521
{
522
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
523
  {
524
    return mAndroidUtils->requestCameraPermission();
×
525
  }
526
  return true;
×
527
}
528

529
bool InputUtils::isBluetoothTurnedOn()
×
530
{
531
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
532
  {
533
    return mAndroidUtils->isBluetoothTurnedOn();
×
534
  }
535
  return true;
×
536
}
537

538
void InputUtils::turnBluetoothOn()
×
539
{
540
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
541
  {
542
    mAndroidUtils->turnBluetoothOn();
×
543
  }
544
}
×
545

546
void InputUtils::quitApp()
×
547
{
548
  if ( appPlatform() == QStringLiteral( "android" ) )
×
549
  {
550
    AndroidUtils::quitApp();
×
551
  }
552
  else
553
  {
554
    QCoreApplication::quit();
×
555
  }
556
}
×
557

558
QString InputUtils::appPlatform()
21✔
559
{
560
#if defined( ANDROID )
561
  const QString platform = "android";
562
#elif defined( Q_OS_IOS )
563
  const QString platform = "ios";
564
#elif defined( Q_OS_WIN32 )
565
  const QString platform = "win";
566
#elif defined( Q_OS_LINUX )
567
  const QString platform = "linux";
21✔
568
#elif defined( Q_OS_MAC )
569
  const QString platform = "macos";
570
#else
571
  const QString platform = "unknown";
572
#endif
573
  return platform;
21✔
574
}
575

576
bool InputUtils::isMobilePlatform()
3✔
577
{
578
  QString platform = appPlatform();
3✔
579
  return platform == QStringLiteral( "android" ) || platform == QStringLiteral( "ios" );
12✔
580
}
3✔
581

582
void InputUtils::onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level )
17✔
583
{
584
  QString levelStr;
17✔
585
  switch ( level )
17✔
586
  {
587
    case Qgis::MessageLevel::Warning:
17✔
588
      levelStr = "Warning";
17✔
589
      break;
17✔
590
    case Qgis::MessageLevel::Critical:
×
591
      levelStr = "Error";
×
592
      break;
×
593
    default:
×
594
      break;
×
595
  }
596

597
  CoreUtils::log( "QGIS " + tag, levelStr + ": " + message );
17✔
598
}
17✔
599

600
bool InputUtils::cpDir( const QString &srcPath, const QString &dstPath, bool onlyDiffable )
57✔
601
{
602
  bool result  = true;
57✔
603
  QDir parentDstDir( QFileInfo( dstPath ).path() );
114✔
604
  if ( !parentDstDir.mkpath( dstPath ) )
57✔
605
  {
606
    CoreUtils::log( "cpDir", QString( "Cannot make path %1" ).arg( dstPath ) );
×
607
    return false;
×
608
  }
609

610
  QDir srcDir( srcPath );
57✔
611
  const QFileInfoList fileInfoList = srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden );
57✔
612
  foreach ( const QFileInfo &info, fileInfoList )
546✔
613
  {
614
    QString fileName = info.fileName();
489✔
615

616
#ifdef ANDROID
617
    // https://bugreports.qt.io/browse/QTBUG-114219
618
    if ( fileName.startsWith( "assets:/" ) )
619
    {
620
      fileName.remove( 0, 8 );
621
    }
622
#endif
623

624
    QString srcItemPath = srcPath + "/" + fileName;
489✔
625
    QString dstItemPath = dstPath + "/" + fileName;
489✔
626

627
    if ( info.isDir() )
489✔
628
    {
629
      if ( !cpDir( srcItemPath, dstItemPath ) )
2✔
630
      {
631
        CoreUtils::log( "cpDir", QString( "Cannot copy a dir from %1 to %2" ).arg( srcItemPath ).arg( dstItemPath ) );
×
632
        result = false;
×
633
      }
634
    }
635
    else if ( info.isFile() )
487✔
636
    {
637
      if ( onlyDiffable && !MerginApi::isFileDiffable( fileName ) )
487✔
638
        continue;
×
639

640
      if ( !QFile::copy( srcItemPath, dstItemPath ) )
487✔
641
      {
642
        if ( !QFile::remove( dstItemPath ) )
×
643
        {
644
          CoreUtils::log( "cpDir", QString( "Cannot remove a file from %1" ).arg( dstItemPath ) );
×
645
          result =  false;
×
646
        }
647
        if ( !QFile::copy( srcItemPath, dstItemPath ) )
×
648
        {
649
          CoreUtils::log( "cpDir", QString( "Cannot overwrite a file %1 with %2" ).arg( dstItemPath ).arg( dstItemPath ) );
×
650
          result =  false;
×
651
        }
652
      }
653
      QFile::setPermissions( dstItemPath, QFile::ReadUser | QFile::WriteUser | QFile::ReadOwner | QFile::WriteOwner );
487✔
654
    }
655
    else
656
    {
657
      CoreUtils::log( "cpDir", QString( "Unhandled item %1 in cpDir" ).arg( info.filePath() ) );
×
658
    }
659
  }
546✔
660
  return result;
57✔
661
}
57✔
662

663
QString InputUtils::renameWithDateTime( const QString &srcPath, const QDateTime &dateTime )
×
664
{
665
  if ( QFile::exists( srcPath ) )
×
666
  {
667
    QFileInfo info( srcPath );
×
668
    QString timestamp = ( dateTime.isValid() ) ? dateTime.toString( DATE_TIME_FORMAT ) : QDateTime::currentDateTime().toString( DATE_TIME_FORMAT );
×
669
    QString newFilename = QString( "%1.%2" ).arg( timestamp ).arg( info.suffix() );
×
670
    QString newPath( info.absolutePath() + "/" + newFilename );
×
671

672
    if ( QFile::rename( srcPath, newPath ) ) return newPath;
×
673
  }
×
674

675
  return QString();
×
676
}
677

678
bool InputUtils::renameFile( const QString &srcPath, const QString &dstPath )
1✔
679
{
680
  QFileInfo fi( dstPath );
1✔
681
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
1✔
682
  {
683
    return false;
×
684
  }
685
  return QFile::rename( srcPath, dstPath );
1✔
686
}
1✔
687

688
void InputUtils::showNotification( const QString &message )
×
689
{
690
  emit showNotificationRequested( message );
×
691
}
×
692

693
double InputUtils::ratherZeroThanNaN( double d )
×
694
{
695
  return ( isnan( d ) ) ? 0.0 : d;
×
696
}
697

698
/**
699
 * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
700
 */
701
QgsCoordinateReferenceSystem InputUtils::coordinateReferenceSystemFromEpsgId( long epsg )
12✔
702
{
703
  return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
12✔
704
}
705

706
QgsPointXY InputUtils::pointXY( double x, double y )
1✔
707
{
708
  return QgsPointXY( x, y );
1✔
709
}
710

711
QgsPoint InputUtils::point( double x, double y, double z, double m )
2✔
712
{
713
  return QgsPoint( x, y, z, m );
2✔
714
}
715

716
QgsGeometry InputUtils::emptyGeometry()
×
717
{
718
  return QgsGeometry();
×
719
}
720

721
QgsFeature InputUtils::emptyFeature()
×
722
{
723
  return QgsFeature();
×
724
}
725

726
bool InputUtils::isEmptyGeometry( const QgsGeometry &geometry )
×
727
{
728
  return geometry.isEmpty();
×
729
}
730

731
QgsPoint InputUtils::coordinateToPoint( const QGeoCoordinate &coor )
×
732
{
733
  return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
×
734
}
735

736
QgsPointXY InputUtils::transformPointXY( const QgsCoordinateReferenceSystem &srcCrs,
27✔
737
    const QgsCoordinateReferenceSystem &destCrs,
738
    const QgsCoordinateTransformContext &context,
739
    const QgsPointXY &srcPoint )
740
{
741
  // we do not want to transform empty points,
742
  // QGIS would convert them to a valid (0, 0) points
743
  if ( srcPoint.isEmpty() )
27✔
744
  {
745
    return QgsPointXY();
×
746
  }
747

748
  try
749
  {
750
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
751
    if ( ct.isValid() )
27✔
752
    {
753
      if ( !ct.isShortCircuited() )
27✔
754
      {
755
        const QgsPointXY pt = ct.transform( srcPoint );
6✔
756
        return pt;
6✔
757
      }
758
      else
759
      {
760
        return srcPoint;
21✔
761
      }
762
    }
763
  }
27✔
764
  catch ( QgsCsException &cse )
×
765
  {
766
    Q_UNUSED( cse )
767
  }
×
768

769
  return QgsPointXY();
×
770
}
771

772
QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs,
28✔
773
                                     const QgsCoordinateReferenceSystem &destCrs,
774
                                     const QgsCoordinateTransformContext &context,
775
                                     const QgsPoint &srcPoint )
776
{
777
  // we do not want to transform empty points,
778
  // QGIS would convert them to a valid (0, 0) points
779
  if ( srcPoint.isEmpty() )
28✔
780
  {
781
    return QgsPoint();
1✔
782
  }
783

784
  try
785
  {
786
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
787
    if ( ct.isValid() )
27✔
788
    {
789
      if ( !ct.isShortCircuited() )
27✔
790
      {
791
        const QgsPointXY transformed = ct.transform( srcPoint.x(), srcPoint.y() );
6✔
792
        const QgsPoint pt( transformed.x(), transformed.y(), srcPoint.z(), srcPoint.m() );
6✔
793
        return pt;
6✔
794
      }
6✔
795
      else
796
      {
797
        return srcPoint;
21✔
798
      }
799
    }
800
  }
27✔
801
  catch ( QgsCsException &cse )
×
802
  {
803
    Q_UNUSED( cse )
804
  }
×
805

806
  return QgsPoint();
×
807
}
808

809
QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint )
×
810
{
811
  if ( !mapSettings || srcPoint.isEmpty() )
×
812
    return QPointF();
×
813

814
  QgsPoint mapcrsPoint = transformPoint( srcCrs, mapSettings->destinationCrs(), mapSettings->transformContext(), srcPoint );
×
815
  return mapSettings->coordinateToScreen( mapcrsPoint );
×
816
}
×
817

818
double InputUtils::screenUnitsToMeters( InputMapSettings *mapSettings, int baseLengthPixels )
29✔
819
{
820
  if ( mapSettings == nullptr ) return 0.0;
29✔
821

822
  QgsDistanceArea mDistanceArea;
29✔
823
  mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
29✔
824
  mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
29✔
825

826
  // calculate the geographic distance from the central point of extent
827
  // to the specified number of points on the right side
828
  QSize s = mapSettings->outputSize();
29✔
829
  QPoint pointCenter( s.width() / 2, s.height() / 2 );
29✔
830
  QgsPointXY p1 = mapSettings->screenToCoordinate( pointCenter );
29✔
831
  QgsPointXY p2 = mapSettings->screenToCoordinate( pointCenter + QPoint( baseLengthPixels, 0 ) );
29✔
832
  return mDistanceArea.measureLine( p1, p2 );
29✔
833
}
29✔
834

835
QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSettings )
5✔
836
{
837
  if ( !mapSettings )
5✔
838
    return QgsPoint();
×
839

840
  if ( mapPosition.isNull() )
5✔
841
    return QgsPoint();
×
842

843
  QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition );
5✔
844
  QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 );
5✔
845

846
  const QgsPointXY transformedXY = transformPoint(
10✔
847
                                     mapSettings->destinationCrs(),
10✔
848
                                     crsGPS,
849
                                     QgsCoordinateTransformContext(),
10✔
850
                                     positionMapCrs
851
                                   );
5✔
852

853
  if ( transformedXY.isEmpty() )
5✔
854
  {
855
    // point could not be transformed
856
    return QgsPoint();
1✔
857
  }
858

859
  return QgsPoint( transformedXY );
4✔
860
}
5✔
861

862
bool InputUtils::fileExists( const QString &path )
8✔
863
{
864
  QFileInfo check_file( path );
8✔
865
  // check if file exists and if yes: Is it really a file and no directory?
866
  return ( check_file.exists() && check_file.isFile() );
16✔
867
}
8✔
868

869
QString InputUtils::resolveTargetDir( const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
7✔
870
{
871
  QString expression;
7✔
872
  QMap<QString, QVariant> collection = config.value( QStringLiteral( "PropertyCollection" ) ).toMap();
21✔
873
  QMap<QString, QVariant> props = collection.value( QStringLiteral( "properties" ) ).toMap();
21✔
874

875
  if ( !props.isEmpty() )
7✔
876
  {
877
    QMap<QString, QVariant> propertyRootPath = props.value( QStringLiteral( "propertyRootPath" ) ).toMap();
3✔
878
    expression = propertyRootPath.value( QStringLiteral( "expression" ), QString() ).toString();
2✔
879
  }
1✔
880

881
  if ( !expression.isEmpty() )
7✔
882
  {
883
    return evaluateExpression( pair, activeProject, expression );
1✔
884
  }
885
  else
886
  {
887
    QString defaultRoot = config.value( QStringLiteral( "DefaultRoot" ) ).toString();
18✔
888
    if ( defaultRoot.isEmpty() )
6✔
889
    {
890
      return homePath;
5✔
891
    }
892
    else
893
    {
894
      return defaultRoot;
1✔
895
    }
896
  }
6✔
897
}
7✔
898

899
QString InputUtils::resolvePrefixForRelativePath( int relativeStorageMode, const QString &homePath, const QString &targetDir )
4✔
900
{
901
  if ( relativeStorageMode == 1 )
4✔
902
  {
903
    return homePath;
2✔
904
  }
905
  else if ( relativeStorageMode == 2 )
2✔
906
  {
907
    return targetDir;
1✔
908
  }
909
  else
910
  {
911
    return QString();
1✔
912
  }
913
}
914

915
QString InputUtils::getAbsolutePath( const QString &path, const QString &prefixPath )
5✔
916
{
917
  return ( prefixPath.isEmpty() ) ? path : QStringLiteral( "%1/%2" ).arg( prefixPath ).arg( path );
9✔
918
}
919

920
QString InputUtils::resolvePath( const QString &path, const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
3✔
921
{
922
  int relativeStorageMode = config.value( QStringLiteral( "RelativeStorage" ) ).toInt();
6✔
923
  QString targetDir = resolveTargetDir( homePath, config, pair, activeProject );
3✔
924
  QString prefixToRelativePath = resolvePrefixForRelativePath( relativeStorageMode, homePath, targetDir );
3✔
925

926
  return getAbsolutePath( path, prefixToRelativePath );
6✔
927
}
3✔
928

929
QString InputUtils::getRelativePath( const QString &path, const QString &prefixPath )
5✔
930
{
931
  QString modPath = path;
5✔
932
  QString filePrefix( "file://" );
5✔
933

934
  if ( path.startsWith( filePrefix ) )
5✔
935
  {
936
    modPath = modPath.replace( filePrefix, QString() );
1✔
937
  }
938

939
  if ( prefixPath.isEmpty() ) return modPath;
5✔
940

941
  // Do not use a canonical path for non-existing path
942
  if ( !QFileInfo( path ).exists() )
4✔
943
  {
944
    if ( !prefixPath.isEmpty() && modPath.startsWith( prefixPath ) )
4✔
945
    {
946
      return modPath.replace( prefixPath, QString() );
3✔
947
    }
948
  }
949
  else
950
  {
951
    QDir absoluteDir( modPath );
×
952
    QDir prefixDir( prefixPath );
×
953
    QString canonicalPath = absoluteDir.canonicalPath();
×
954
    QString prefixCanonicalPath = prefixDir.canonicalPath() + "/";
×
955

956
    if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
×
957
    {
958
      return canonicalPath.replace( prefixCanonicalPath, QString() );
×
959
    }
960
  }
×
961

962
  return QString();
1✔
963
}
5✔
964

965
void InputUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
×
966
{
967
  QgsMessageLog::logMessage( message, tag, level );
×
968
}
×
969

970
void InputUtils::log( const QString &context, const QString &message )
×
971
{
972
  CoreUtils::log( context, message );
×
973
}
×
974

975
const QUrl InputUtils::getThemeIcon( const QString &name )
1✔
976
{
977
  QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
1✔
978
  QgsDebugMsgLevel( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ), 2 );
979
  return QUrl( path );
2✔
980
}
1✔
981

982
const QUrl InputUtils::getEditorComponentSource( const QString &widgetName, const QVariantMap &config, const QgsField &field )
2✔
983
{
984
  QString path( "../editor/input%1.qml" );
2✔
985

986
  if ( widgetName == QStringLiteral( "range" ) )
2✔
987
  {
988
    if ( config.contains( "Style" ) )
×
989
    {
990
      if ( config["Style"] == QStringLiteral( "Slider" ) )
×
991
      {
992
        return QUrl( path.arg( QLatin1String( "rangeslider" ) ) );
×
993
      }
994
      else if ( config["Style"] == QStringLiteral( "SpinBox" ) )
×
995
      {
996
        return QUrl( path.arg( QLatin1String( "rangeeditable" ) ) );
×
997
      }
998
    }
999
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1000
  }
1001

1002
  if ( field.name().contains( "qrcode", Qt::CaseInsensitive ) || field.alias().contains( "qrcode", Qt::CaseInsensitive ) )
2✔
1003
  {
1004
    return QUrl( path.arg( QStringLiteral( "qrcodereader" ) ) );
×
1005
  }
1006

1007
  if ( widgetName == QStringLiteral( "textedit" ) )
2✔
1008
  {
1009
    if ( config.value( "IsMultiline" ).toBool() )
×
1010
    {
1011
      return QUrl( path.arg( QStringLiteral( "texteditmultiline" ) ) );
×
1012
    }
1013
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1014
  }
1015

1016
  if ( widgetName == QStringLiteral( "valuerelation" ) )
2✔
1017
  {
1018
    const QgsMapLayer *referencedLayer = QgsProject::instance()->mapLayer( config.value( "Layer" ).toString() );
×
1019
    const QgsVectorLayer *layer = qobject_cast<const QgsVectorLayer *>( referencedLayer );
×
1020

1021
    if ( layer )
×
1022
    {
1023
      int featuresCount = layer->dataProvider()->featureCount();
×
1024
      if ( featuresCount > 4 )
×
1025
        return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1026
    }
1027

1028
    if ( config.value( "AllowMulti" ).toBool() )
×
1029
    {
1030
      return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1031
    }
1032

1033
    return QUrl( path.arg( QLatin1String( "valuerelationcombobox" ) ) );
×
1034
  }
1035

1036
  QStringList supportedWidgets = { QStringLiteral( "textedit" ),
×
1037
                                   QStringLiteral( "valuemap" ),
2✔
1038
                                   QStringLiteral( "valuerelation" ),
2✔
1039
                                   QStringLiteral( "checkbox" ),
2✔
1040
                                   QStringLiteral( "externalresource" ),
2✔
1041
                                   QStringLiteral( "datetime" ),
2✔
1042
                                   QStringLiteral( "range" ),
2✔
1043
                                   QStringLiteral( "relation" ),
2✔
1044
                                   QStringLiteral( "relationreference" )
2✔
1045
                                 };
38✔
1046
  if ( supportedWidgets.contains( widgetName ) )
2✔
1047
  {
1048
    return QUrl( path.arg( widgetName ) );
2✔
1049
  }
1050
  else
1051
  {
1052
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
2✔
1053
  }
1054
}
2✔
1055

1056
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field )
8✔
1057
{
1058
  if ( field.isNumeric() )
8✔
1059
    return getEditorWidgetSetup( field, QStringLiteral( "Range" ) );
12✔
1060
  else if ( field.isDateOrTime() )
4✔
1061
    return getEditorWidgetSetup( field, QStringLiteral( "DateTime" ) );
×
1062
  else if ( field.type() == QVariant::Bool )
4✔
1063
    return getEditorWidgetSetup( field, QStringLiteral( "CheckBox" ) );
×
1064
  else
1065
    return getEditorWidgetSetup( field, QStringLiteral( "TextEdit" ) );
12✔
1066
}
1067

1068
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field, const QString &widgetType, const QVariantMap &additionalArgs )
9✔
1069
{
1070
  if ( field.name() == QStringLiteral( "fid" ) )
9✔
1071
    return QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
4✔
1072

1073
  if ( widgetType.isEmpty() )
7✔
1074
  {
1075
    return QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() );
×
1076
  }
1077
  else
1078
  {
1079
    QMultiMap<QString, QVariant> config;
7✔
1080
    config = config.unite( QMultiMap( additionalArgs ) );
7✔
1081

1082
    if ( widgetType == QStringLiteral( "TextEdit" ) )
7✔
1083
    {
1084
      config.insert( QStringLiteral( "isMultiline" ), false );
8✔
1085
      config.insert( QStringLiteral( "UseHtml" ), false );
8✔
1086
    }
1087
    else if ( widgetType == QStringLiteral( "DateTime" ) )
3✔
1088
    {
1089
      config.insert( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::DATETIME_FORMAT );
×
1090
      config.insert( QStringLiteral( "display_format" ), QgsDateTimeFieldFormatter::DATETIME_FORMAT );
×
1091
    }
1092
    else if ( widgetType == QStringLiteral( "Range" ) )
3✔
1093
    {
1094
      config.insert( QStringLiteral( "Style" ), QStringLiteral( "SpinBox" ) );
4✔
1095
      config.insert( QStringLiteral( "Precision" ), QStringLiteral( "0" ) );
4✔
1096
      config.insert( QStringLiteral( "Min" ), QString::number( INT_MIN ) );
4✔
1097
      config.insert( QStringLiteral( "Max" ), QString::number( INT_MAX ) );
4✔
1098
      config.insert( QStringLiteral( "Step" ), 1 );
4✔
1099
    }
1100
    else if ( widgetType == QStringLiteral( "ExternalResource" ) )
1✔
1101
    {
1102
      config.insert( QStringLiteral( "RelativeStorage" ), QStringLiteral( "1" ) );
×
1103
      config.insert( QStringLiteral( "StorageMode" ), QStringLiteral( "0" ) );
×
1104
      config.insert( QStringLiteral( "PropertyCollection" ), QVariantMap() );
×
1105
      QgsPropertyCollection collection;
×
1106
      config.insert( QStringLiteral( "PropertyCollection" ), collection.toVariant( QgsPropertiesDefinition() ) );
×
1107
    }
×
1108
    else if ( widgetType == QStringLiteral( "RelationReference" ) )
1✔
1109
    {
1110
      config.insert( QStringLiteral( "AllowNULL" ), true );
2✔
1111
    }
1112

1113
    QVariantMap cfg;
7✔
1114
    QList<QString> keys = config.uniqueKeys();
7✔
1115
    for ( int i = 0; i < keys.size(); i++ )
28✔
1116
    {
1117
      cfg.insert( keys.at( i ), config.value( keys.at( i ) ) );
21✔
1118
    }
1119

1120
    return QgsEditorWidgetSetup( widgetType, cfg );
7✔
1121
  }
7✔
1122
}
1123

1124
QString InputUtils::geometryFromLayer( QgsVectorLayer *layer )
11✔
1125
{
1126
  if ( layer )
11✔
1127
  {
1128
    switch ( layer->geometryType() )
11✔
1129
    {
1130
      case Qgis::GeometryType::Point: return QStringLiteral( "point" );
20✔
1131
      case Qgis::GeometryType::Line: return QStringLiteral( "linestring" );
2✔
1132
      case Qgis::GeometryType::Polygon: return QStringLiteral( "polygon" );
×
1133
      case Qgis::GeometryType::Null: return QStringLiteral( "nullGeo" );
×
1134
      default: return QString();
×
1135
    }
1136
  }
1137
  return QString();
×
1138
}
1139

1140
bool InputUtils::isPointLayer( QgsVectorLayer *layer )
×
1141
{
1142
  return geometryFromLayer( layer ) == "point";
×
1143
}
1144

1145
bool InputUtils::isLineLayer( QgsVectorLayer *layer )
×
1146
{
1147
  return geometryFromLayer( layer ) == "linestring";
×
1148
}
1149

1150
bool InputUtils::isPolygonLayer( QgsVectorLayer *layer )
×
1151
{
1152
  return geometryFromLayer( layer ) == "polygon";
×
1153
}
1154

1155
bool InputUtils::isNoGeometryLayer( QgsVectorLayer *layer )
×
1156
{
1157
  return geometryFromLayer( layer ) == "nullGeo";
×
1158
}
1159

1160
bool InputUtils::isMultiPartLayer( QgsVectorLayer *layer )
×
1161
{
1162
  if ( !layer )
×
1163
  {
1164
    return false;
×
1165
  }
1166
  return QgsWkbTypes::isMultiType( layer->wkbType() );
×
1167
}
1168

1169
bool InputUtils::isSpatialLayer( QgsVectorLayer *layer )
×
1170
{
1171
  if ( !layer )
×
1172
  {
1173
    return false;
×
1174
  }
1175
  return layer->isSpatial();
×
1176
}
1177

1178
qreal InputUtils::calculateScreenDpr()
1✔
1179
{
1180
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1181

1182
  if ( !screens.isEmpty() )
1✔
1183
  {
1184
    QScreen *screen = screens.at( 0 );
1✔
1185
    double dpiX = screen->physicalDotsPerInchX();
1✔
1186
    double dpiY = screen->physicalDotsPerInchY();
1✔
1187

1188
    qreal realDpi = dpiX < dpiY ? dpiX : dpiY;
1✔
1189
    realDpi = realDpi * screen->devicePixelRatio();
1✔
1190

1191
    return realDpi / 160.;
1✔
1192
  }
1193

1194
  return 1;
×
1195
}
1✔
1196

1197
qreal InputUtils::calculateDpRatio()
1✔
1198
{
1199
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1200

1201
  if ( !screens.isEmpty() )
1✔
1202
  {
1203
    QScreen *screen = screens.at( 0 );
1✔
1204

1205
    qreal realDpr = calculateScreenDpr();
1✔
1206
    return realDpr / screen->devicePixelRatio();
1✔
1207
  }
1208

1209
  return 1;
×
1210
}
1✔
1211

1212
bool InputUtils::equals( const QPointF &a, const QPointF &b, double epsilon )
8✔
1213
{
1214
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
8✔
1215
}
1216

1217
bool InputUtils::equals( const QgsPointXY &a, const QgsPointXY &b, double epsilon )
42✔
1218
{
1219
  if ( a.isEmpty() && b.isEmpty() )
42✔
1220
    return true;
3✔
1221
  if ( a.isEmpty() != b.isEmpty() )
39✔
1222
    return false;
4✔
1223

1224
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
35✔
1225
}
1226

1227
bool InputUtils::equals( const QgsPoint &a, const QgsPoint &b, double epsilon )
316✔
1228
{
1229
  if ( a.isEmpty() && b.isEmpty() )
316✔
1230
    return true;
35✔
1231
  if ( a.isEmpty() != b.isEmpty() )
281✔
1232
    return false;
2✔
1233

1234
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
279✔
1235
}
1236

1237
QString InputUtils::formatPoint(
1✔
1238
  const QgsPoint &point,
1239
  QgsCoordinateFormatter::Format format,
1240
  int decimals,
1241
  QgsCoordinateFormatter::FormatFlags flags )
1242
{
1243
  return QgsCoordinateFormatter::format( point, format, decimals, flags );
1✔
1244
}
1245

1246
QString InputUtils::formatDistance( double distance,
10✔
1247
                                    Qgis::DistanceUnit units,
1248
                                    int decimals,
1249
                                    Qgis::SystemOfMeasurement destSystem )
1250
{
1251
  double destDistance;
1252
  Qgis::DistanceUnit destUnits;
1253

1254
  humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
10✔
1255

1256
  return QStringLiteral( "%1 %2" )
20✔
1257
         .arg( QString::number( destDistance, 'f', decimals ) )
20✔
1258
         .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
30✔
1259
}
1260

1261
void InputUtils::humanReadableDistance( double srcDistance, Qgis::DistanceUnit srcUnits,
14✔
1262
                                        Qgis::SystemOfMeasurement destSystem,
1263
                                        double &destDistance, Qgis::DistanceUnit &destUnits )
1264
{
1265
  if ( ( destSystem == Qgis::SystemOfMeasurement::Metric ) || ( destSystem == Qgis::SystemOfMeasurement::Unknown ) )
14✔
1266
  {
1267
    return formatToMetricDistance( srcDistance, srcUnits, destDistance, destUnits );
11✔
1268
  }
1269
  else if ( destSystem == Qgis::SystemOfMeasurement::Imperial )
3✔
1270
  {
1271
    return formatToImperialDistance( srcDistance, srcUnits, destDistance, destUnits );
2✔
1272
  }
1273
  else if ( destSystem == Qgis::SystemOfMeasurement::USCS )
1✔
1274
  {
1275
    return formatToUSCSDistance( srcDistance, srcUnits, destDistance, destUnits );
1✔
1276
  }
1277
  else
1278
  {
1279
    Q_ASSERT( false ); //should never happen
×
1280
  }
1281
}
1282

1283
void InputUtils::formatToMetricDistance( double srcDistance,
11✔
1284
    Qgis::DistanceUnit srcUnits,
1285
    double &destDistance,
1286
    Qgis::DistanceUnit &destUnits )
1287
{
1288
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Millimeters );
11✔
1289
  if ( dist < 0 )
11✔
1290
  {
1291
    destDistance = 0;
1✔
1292
    destUnits = Qgis::DistanceUnit::Millimeters;
1✔
1293
    return;
1✔
1294
  }
1295

1296
  double mmToKm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Kilometers, Qgis::DistanceUnit::Millimeters );
10✔
1297
  if ( dist > mmToKm )
10✔
1298
  {
1299
    destDistance = dist / mmToKm;
5✔
1300
    destUnits = Qgis::DistanceUnit::Kilometers;
5✔
1301
    return;
5✔
1302
  }
1303

1304
  double mmToM = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, Qgis::DistanceUnit::Millimeters );
5✔
1305
  if ( dist > mmToM )
5✔
1306
  {
1307
    destDistance = dist / mmToM;
4✔
1308
    destUnits = Qgis::DistanceUnit::Meters;
4✔
1309
    return;
4✔
1310
  }
1311

1312
  double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Centimeters, Qgis::DistanceUnit::Millimeters );
1✔
1313
  if ( dist > mmToCm )
1✔
1314
  {
1315
    destDistance = dist / mmToCm;
1✔
1316
    destUnits = Qgis::DistanceUnit::Centimeters;
1✔
1317
    return;
1✔
1318
  }
1319

1320
  destDistance = dist;
×
1321
  destUnits = Qgis::DistanceUnit::Millimeters;
×
1322
}
1323

1324
void InputUtils::formatToImperialDistance( double srcDistance,
2✔
1325
    Qgis::DistanceUnit srcUnits,
1326
    double &destDistance,
1327
    Qgis::DistanceUnit &destUnits )
1328
{
1329
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
2✔
1330
  if ( dist < 0 )
2✔
1331
  {
1332
    destDistance = 0;
×
1333
    destUnits = Qgis::DistanceUnit::Feet;
×
1334
    return;
×
1335
  }
1336

1337
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Miles, Qgis::DistanceUnit::Feet );
2✔
1338
  if ( dist > feetToMile )
2✔
1339
  {
1340
    destDistance = dist / feetToMile;
1✔
1341
    destUnits = Qgis::DistanceUnit::Miles;
1✔
1342
    return;
1✔
1343
  }
1344

1345
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
1✔
1346
  if ( dist > feetToYard )
1✔
1347
  {
1348
    destDistance = dist / feetToYard;
1✔
1349
    destUnits = Qgis::DistanceUnit::Yards;
1✔
1350
    return;
1✔
1351
  }
1352

1353
  destDistance = dist;
×
1354
  destUnits = Qgis::DistanceUnit::Feet;
×
1355
  return;
×
1356
}
1357

1358
void InputUtils::formatToUSCSDistance( double srcDistance,
1✔
1359
                                       Qgis::DistanceUnit srcUnits,
1360
                                       double &destDistance,
1361
                                       Qgis::DistanceUnit &destUnits )
1362
{
1363
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
1✔
1364
  if ( dist < 0 )
1✔
1365
  {
1366
    destDistance = 0;
×
1367
    destUnits = Qgis::DistanceUnit::Feet;
×
1368
    return;
×
1369
  }
1370

1371
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::NauticalMiles, Qgis::DistanceUnit::Feet );
1✔
1372
  if ( dist > feetToMile )
1✔
1373
  {
1374
    destDistance = dist / feetToMile;
1✔
1375
    destUnits = Qgis::DistanceUnit::NauticalMiles;
1✔
1376
    return;
1✔
1377
  }
1378

1379
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
×
1380
  if ( dist > feetToYard )
×
1381
  {
1382
    destDistance = dist / feetToYard;
×
1383
    destUnits = Qgis::DistanceUnit::Yards;
×
1384
    return;
×
1385
  }
1386

1387
  destDistance = dist;
×
1388
  destUnits = Qgis::DistanceUnit::Feet;
×
1389
  return;
×
1390
}
1391

1392
QString InputUtils::dumpScreenInfo() const
1✔
1393
{
1394
  QString msg;
1✔
1395
  // take the first top level window
1396
  const QWindowList windows = QGuiApplication::topLevelWindows();
1✔
1397
  if ( !windows.isEmpty() )
1✔
1398
  {
1399
    QScreen *screen = windows.at( 0 )->screen();
×
1400
    double dpiX = screen->physicalDotsPerInchX();
×
1401
    double dpiY = screen->physicalDotsPerInchY();
×
1402
    int height = screen->geometry().height();
×
1403
    int width = screen->geometry().width();
×
1404
    double sizeX = static_cast<double>( width ) / dpiX * 25.4;
×
1405
    double sizeY = static_cast<double>( height ) / dpiY * 25.4;
×
1406

1407
    msg += tr( "screen resolution: %1x%2 px\n" ).arg( width ).arg( height );
×
1408
    msg += tr( "screen DPI: %1x%2\n" ).arg( dpiX ).arg( dpiY );
×
1409
    msg += tr( "screen size: %1x%2 mm\n" ).arg( QString::number( sizeX, 'f', 0 ), QString::number( sizeY, 'f', 0 ) );
×
1410
    msg += tr( "reported device pixel ratio: %1\n" ).arg( screen->devicePixelRatio() );
×
1411
    msg += tr( "calculated device pixel ratio: %1\n" ).arg( calculateScreenDpr() );
×
1412
    msg += tr( "used dp scale: %1" ).arg( calculateDpRatio() );
×
1413
  }
1414
  else
1415
  {
1416
    msg += QLatin1String( "screen info: application is not initialized!" );
1✔
1417
  }
1418
  return msg;
2✔
1419
}
1✔
1420

1421
QString InputUtils::evaluateExpression( const FeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
1✔
1422
{
1423
  QList<QgsExpressionContextScope *> scopes;
1✔
1424
  scopes << QgsExpressionContextUtils::globalScope();
1✔
1425
  scopes << QgsExpressionContextUtils::projectScope( activeProject );
1✔
1426
  scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
1✔
1427

1428
  QgsExpressionContext context( scopes );
1✔
1429
  context.setFeature( pair.feature() );
1✔
1430
  QgsExpression expr( expression );
1✔
1431
  return expr.evaluate( &context ).toString();
3✔
1432
}
1✔
1433

1434
QString InputUtils::fieldType( const QgsField &field )
×
1435
{
1436
  return QVariant( field.type() ).typeName();
×
1437
}
1438

1439
QString InputUtils::dateTimeFieldFormat( const QString &fieldFormat )
×
1440
{
1441
  if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
×
1442
  {
1443
    return QString( "Date" );
×
1444
  }
1445
  else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
×
1446
  {
1447
    return QString( "Time" );
×
1448
  }
1449
  // cppcheck-suppress duplicateBranch
1450
  else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
×
1451
  {
1452
    return QString( "Date Time" );
×
1453
  }
1454
  else
1455
  {
1456
    return QString( "Date Time" );
×
1457
  }
1458
}
1459

1460
bool InputUtils::isFeatureIdValid( qint64 featureId )
2✔
1461
{
1462
  return !FID_IS_NEW( featureId ) && !FID_IS_NULL( featureId );
2✔
1463
}
1464

1465
QgsRectangle InputUtils::stakeoutPathExtent(
5✔
1466
  MapPosition *mapPosition,
1467
  const FeatureLayerPair &targetFeature,
1468
  InputMapSettings *mapSettings,
1469
  double mapExtentOffset
1470
)
1471
{
1472
  if ( !mapPosition || !mapSettings || !targetFeature.isValid() )
5✔
1473
    return QgsRectangle();
×
1474

1475
  QgsRectangle extent = mapSettings->extent();
5✔
1476

1477
  // We currently support only point geometries
1478
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
5✔
1479
    return extent;
×
1480

1481
  if ( !mapPosition->positionKit() || !mapPosition->mapSettings() )
5✔
1482
    return extent;
×
1483

1484
  //
1485
  // In order to compute stakeout extent, we first compute distance to target feature and
1486
  // based on that we update the extent and scale. Logic for scale computation is in distanceToScale function.
1487
  // Moreover, when distance to target point is lower then 1 meter, extent is centered to target point, otherwise
1488
  // it is centered to GPS position. This has been added in order to reduce "jumps" of canvas when user is near the target.
1489
  //
1490

1491
  QgsPoint gpsPointRaw = mapPosition->positionKit()->positionCoordinate();
5✔
1492

1493
  qreal distance = distanceBetweenGpsAndFeature( gpsPointRaw, targetFeature, mapSettings );
5✔
1494
  qreal scale = distanceToScale( distance );
5✔
1495
  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✔
1496

1497
  if ( mapExtentOffset > 0 )
5✔
1498
  {
1499
    panelOffset = mapExtentOffset / 2.0;
×
1500
  }
1501

1502
  if ( distance <= 1 )
5✔
1503
  {
1504
    // center to target point
1505
    QgsPoint targetPointRaw( extractPointFromFeature( targetFeature ) );
1✔
1506
    QgsPointXY targetPointInMapCRS = transformPoint(
2✔
1507
                                       targetFeature.layer()->crs(),
2✔
1508
                                       mapSettings->destinationCrs(),
2✔
1509
                                       mapSettings->transformContext(),
2✔
1510
                                       targetPointRaw
1511
                                     );
1✔
1512

1513
    if ( targetPointInMapCRS.isEmpty() )
1✔
1514
    {
1515
      // unsuccessful transform
1516
      return extent;
×
1517
    }
1518

1519
    QgsPointXY targetPointInCanvasXY = mapSettings->coordinateToScreen( QgsPoint( targetPointInMapCRS ) );
1✔
1520
    QgsPointXY centerInCanvasXY( targetPointInCanvasXY.x(), targetPointInCanvasXY.y() + panelOffset );
1✔
1521
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
1✔
1522

1523
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
1✔
1524
  }
1✔
1525
  else
1526
  {
1527
    // center to GPS position
1528
    QgsPointXY gpsPointInCanvasXY = mapPosition->screenPosition();
4✔
1529
    QgsPointXY centerInCanvasXY( gpsPointInCanvasXY.x(), gpsPointInCanvasXY.y() + panelOffset );
4✔
1530
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
4✔
1531

1532
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
4✔
1533
  }
1534

1535
  return extent;
5✔
1536
}
5✔
1537

1538
QgsGeometry InputUtils::stakeoutGeometry( const QgsPoint &mapPosition, const FeatureLayerPair &target, InputMapSettings *mapSettings )
×
1539
{
1540
  if ( !mapSettings || !target.isValid() )
×
1541
    return QgsGeometry();
×
1542

1543
  QgsPointXY targetInLayerCoordinates = target.feature().geometry().asPoint();
×
1544
  QgsPointXY t = transformPointXY( target.layer()->crs(), mapSettings->destinationCrs(), mapSettings->transformContext(), targetInLayerCoordinates );
×
1545

1546
  QVector<QgsPoint> points { mapPosition, QgsPoint( t ) };
×
1547

1548
  return QgsGeometry::fromPolyline( points );
×
1549
}
×
1550

1551
qreal InputUtils::distanceToScale( qreal distance )
29✔
1552
{
1553
  // Stakeout extent scale is computed based on these (empirically found) conditions:
1554
  //   - if distance is > 10m, use 1:205 scale (~ 5m on mobile)
1555
  //   - if distance is 3-10m, use 1:105 scale (~ 2m on mobile)
1556
  //   - if distance is 1-3m,  use 1:55 scale  (~ 1m on mobile)
1557
  //   - if distance is < 1m,  use 1:25 scale  (~ 0.5m on mobile)
1558

1559
  qreal scale = 205;
29✔
1560

1561
  if ( distance <= 1 )
29✔
1562
  {
1563
    scale = 25;
9✔
1564
  }
1565
  else if ( distance <= 3 && distance > 1 )
20✔
1566
  {
1567
    scale = 55;
7✔
1568
  }
1569
  else if ( distance <= 10 && distance > 3 )
13✔
1570
  {
1571
    scale = 105;
5✔
1572
  }
1573

1574
  return scale;
29✔
1575
}
1576

1577
qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
6✔
1578
{
1579
  if ( !mapSettings || !targetFeature.isValid() )
6✔
1580
    return -1;
×
1581

1582
  // We calculate distance only between points
1583
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
6✔
1584
    return -1;
×
1585

1586
  // Transform gps position to map CRS
1587
  QgsPointXY transformedPosition = transformPoint(
12✔
1588
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
12✔
1589
                                     mapSettings->destinationCrs(),
12✔
1590
                                     mapSettings->transformContext(),
12✔
1591
                                     gpsPosition
1592
                                   );
6✔
1593

1594
  if ( transformedPosition.isEmpty() )
6✔
1595
  {
1596
    return -1;
×
1597
  }
1598

1599
  // Transform target point to map CRS
1600
  QgsPoint target( extractPointFromFeature( targetFeature ) );
6✔
1601
  QgsPointXY transformedTarget = transformPoint(
12✔
1602
                                   targetFeature.layer()->crs(),
12✔
1603
                                   mapSettings->destinationCrs(),
12✔
1604
                                   mapSettings->transformContext(),
12✔
1605
                                   target
1606
                                 );
6✔
1607

1608
  if ( transformedTarget.isEmpty() )
6✔
1609
  {
1610
    return -1;
×
1611
  }
1612

1613
  QgsDistanceArea distanceArea;
6✔
1614
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
6✔
1615

1616
  qreal distance = distanceArea.measureLine( transformedPosition, transformedTarget );
6✔
1617
  distance = distanceArea.convertLengthMeasurement( distance, Qgis::DistanceUnit::Meters );
6✔
1618

1619
  return distance;
6✔
1620
}
6✔
1621

1622
qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
1✔
1623
{
1624
  if ( !mapSettings || !targetFeature.isValid() )
1✔
1625
    return -1;
×
1626

1627
  QgsVectorLayer *layer = targetFeature.layer();
1✔
1628
  QgsFeature f = targetFeature.feature();
1✔
1629

1630
  // Only points are supported
1631
  if ( layer->geometryType() != Qgis::GeometryType::Point )
1✔
1632
    return -1;
×
1633

1634
  // Transform gps position to map CRS
1635
  QgsPointXY transformedPosition = transformPoint(
2✔
1636
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
2✔
1637
                                     mapSettings->destinationCrs(),
2✔
1638
                                     mapSettings->transformContext(),
2✔
1639
                                     gpsPoint
1640
                                   );
1✔
1641

1642
  if ( transformedPosition.isEmpty() )
1✔
1643
  {
1644
    return -1;
×
1645
  }
1646

1647
  // Transform target point to map CRS
1648
  QgsPoint target( extractPointFromFeature( targetFeature ) );
1✔
1649
  QgsPointXY transformedTarget = transformPoint(
2✔
1650
                                   targetFeature.layer()->crs(),
2✔
1651
                                   mapSettings->destinationCrs(),
2✔
1652
                                   mapSettings->transformContext(),
2✔
1653
                                   target
1654
                                 );
1✔
1655

1656
  if ( transformedTarget.isEmpty() )
1✔
1657
  {
1658
    return -1;
×
1659
  }
1660

1661
  QgsDistanceArea distanceArea;
1✔
1662
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
1✔
1663

1664
  return distanceArea.bearing( transformedPosition, transformedTarget );
1✔
1665
}
1✔
1666

1667
QString InputUtils::featureTitle( const FeatureLayerPair &pair, QgsProject *project )
×
1668
{
1669
  if ( !project || !pair.isValid() )
×
1670
    return QString();
×
1671

1672
  QString title;
×
1673

1674
  QgsVectorLayer *layer = pair.layer();
×
1675

1676
  // can't use QgsExpressionContextUtils::globalProjectLayerScopes() because it uses QgsProject::instance()
1677
  QList<QgsExpressionContextScope *> scopes;
×
1678
  scopes << QgsExpressionContextUtils::globalScope();
×
1679
  scopes << QgsExpressionContextUtils::projectScope( project );
×
1680
  scopes << QgsExpressionContextUtils::layerScope( layer );
×
1681

1682
  QgsExpressionContext context( scopes );
×
1683
  context.setFeature( pair.feature() );
×
1684
  QgsExpression expr( pair.layer()->displayExpression() );
×
1685
  title = expr.evaluate( &context ).toString();
×
1686

1687
  if ( title.isEmpty() )
×
1688
    title = QStringLiteral( "Feature %1" ).arg( pair.feature().id() );
×
1689

1690
  return title;
×
1691
}
×
1692

1693
FeatureLayerPair InputUtils::createFeatureLayerPair(
1✔
1694
  QgsVectorLayer *layer,
1695
  const QgsGeometry &geometry,
1696
  VariablesManager *variablesmanager,
1697
  QgsExpressionContextScope *additionalScope )
1698
{
1699
  if ( !layer )
1✔
1700
    return FeatureLayerPair();
×
1701

1702
  QgsAttributes attrs( layer->fields().count() );
1✔
1703
  QgsExpressionContext context = layer->createExpressionContext();
1✔
1704

1705
  if ( variablesmanager )
1✔
1706
    context << variablesmanager->positionScope();
×
1707

1708
  if ( additionalScope )
1✔
1709
    context << additionalScope;
1✔
1710

1711
  QgsFeature feat = QgsVectorLayerUtils::createFeature( layer, geometry, attrs.toMap(), &context );
1✔
1712
  return FeatureLayerPair( feat, layer );
1✔
1713
}
1✔
1714

1715
void InputUtils::createEditBuffer( QgsVectorLayer *layer )
×
1716
{
1717
  if ( layer )
×
1718
  {
1719
    if ( !layer->editBuffer() )
×
1720
    {
1721
      layer->startEditing();
×
1722
    }
1723
  }
1724
}
×
1725

1726
FeatureLayerPair InputUtils::changeFeaturePairGeometry( FeatureLayerPair featurePair, const QgsGeometry &geometry )
×
1727
{
1728
  QgsVectorLayer *vlayer = featurePair.layer();
×
1729
  if ( vlayer )
×
1730
  {
1731
    InputUtils::createEditBuffer( vlayer );
×
1732
    QgsGeometry g( geometry );
×
1733
    vlayer->changeGeometry( featurePair.feature().id(), g );
×
1734
    vlayer->triggerRepaint();
×
1735
    QgsFeature f = vlayer->getFeature( featurePair.feature().id() );
×
1736
    return FeatureLayerPair( f, featurePair.layer() );
×
1737
  }
×
1738
  else
1739
  {
1740
    // invalid pair
1741
    return FeatureLayerPair();
×
1742
  }
1743
}
1744

1745
QgsPointXY InputUtils::extractPointFromFeature( const FeatureLayerPair &feature )
11✔
1746
{
1747
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
11✔
1748
    return QgsPointXY();
2✔
1749

1750
  QgsFeature f = feature.feature();
9✔
1751
  const QgsAbstractGeometry *g = f.geometry().constGet();
9✔
1752

1753
  return QgsPoint( dynamic_cast< const QgsPoint * >( g )->toQPointF() );
18✔
1754
}
9✔
1755

1756
bool InputUtils::isPointLayerFeature( const FeatureLayerPair &feature )
4✔
1757
{
1758
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
4✔
1759
    return false;
3✔
1760
  const QgsAbstractGeometry *g = feature.feature().geometry().constGet();
1✔
1761
  const QgsPoint *point = dynamic_cast< const QgsPoint * >( g );
1✔
1762
  return point != nullptr;
1✔
1763
}
1764

1765
void InputUtils::zoomToProject( QgsProject *qgsProject, InputMapSettings *mapSettings )
×
1766
{
1767
  if ( !qgsProject || !mapSettings )
×
1768
  {
1769
    qDebug() << "Cannot zoom to extent, MapSettings or QgsProject is not defined";
×
1770
    return;
×
1771
  }
1772
  QgsRectangle extent;
×
1773

1774
  QgsProjectViewSettings *viewSettings = qgsProject->viewSettings();
×
1775
  extent = viewSettings->presetFullExtent();
×
1776
  if ( extent.isNull() )
×
1777
  {
1778
    bool hasWMS;
1779
    QStringList WMSExtent = qgsProject->readListEntry( "WMSExtent", QStringLiteral( "/" ), QStringList(), &hasWMS );
×
1780

1781
    if ( hasWMS && ( WMSExtent.length() == 4 ) )
×
1782
    {
1783
      extent.set( WMSExtent[0].toDouble(), WMSExtent[1].toDouble(), WMSExtent[2].toDouble(), WMSExtent[3].toDouble() );
×
1784
    }
1785
    else // set layers extent
1786
    {
1787
      const QVector<QgsMapLayer *> layers = qgsProject->layers<QgsMapLayer *>();
×
1788
      for ( const QgsMapLayer *layer : layers )
×
1789
      {
1790
        QgsRectangle layerExtent = mapSettings->mapSettings().layerExtentToOutputExtent( layer, layer->extent() );
×
1791
        extent.combineExtentWith( layerExtent );
×
1792
      }
1793
    }
×
1794
  }
×
1795

1796
  if ( extent.isEmpty() )
×
1797
  {
1798
    extent.grow( qgsProject->crs().isGeographic() ? 0.01 : 1000.0 );
×
1799
  }
1800
  extent.scale( 1.05 );
×
1801
  mapSettings->setExtent( extent );
×
1802
}
1803

1804
QString InputUtils::loadIconFromLayer( QgsMapLayer *layer )
18✔
1805
{
1806
  if ( !layer )
18✔
1807
    return QString();
×
1808

1809
  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
18✔
1810

1811
  if ( vectorLayer )
18✔
1812
  {
1813
    Qgis::GeometryType geometry = vectorLayer->geometryType();
17✔
1814
    return iconFromGeometry( geometry );
17✔
1815
  }
1816
  else
1817
    return QString( "qrc:/mIconRasterLayer.svg" );
1✔
1818
}
1819

1820
QString InputUtils::loadIconFromFeature( QgsFeature feature )
4✔
1821
{
1822
  return iconFromGeometry( feature.geometry().type() );
8✔
1823
}
1824

1825
QString InputUtils::iconFromGeometry( const Qgis::GeometryType &geometry )
21✔
1826
{
1827
  switch ( geometry )
21✔
1828
  {
1829
    case Qgis::GeometryType::Point: return QString( "qrc:/mIconPointLayer.svg" );
6✔
1830
    case Qgis::GeometryType::Line: return QString( "qrc:/mIconLineLayer.svg" );
6✔
1831
    case Qgis::GeometryType::Polygon: return QString( "qrc:/mIconPolygonLayer.svg" );
6✔
1832
    default: return QString( "qrc:/mIconTableLayer.svg" );
3✔
1833
  }
1834
}
1835

1836
bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
×
1837
{
1838
  int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
×
1839
  return ImageUtils::rescale( path, quality );
×
1840
}
1841

1842
QgsGeometry InputUtils::createGeometryForLayer( QgsVectorLayer *layer )
36✔
1843
{
1844
  QgsGeometry geometry;
36✔
1845

1846
  if ( !layer )
36✔
1847
  {
1848
    return geometry;
×
1849
  }
1850

1851
  bool isMulti = QgsWkbTypes::isMultiType( layer->wkbType() );
36✔
1852

1853
  switch ( layer->geometryType() )
36✔
1854
  {
1855
    case Qgis::GeometryType::Point:
15✔
1856
    {
1857
      if ( isMulti )
15✔
1858
      {
1859
        QgsMultiPoint *multiPoint = new QgsMultiPoint();
3✔
1860
        geometry.set( multiPoint );
3✔
1861
      }
1862
      else
1863
      {
1864
        QgsPoint *point = new QgsPoint();
12✔
1865
        geometry.set( point );
12✔
1866
      }
1867
      break;
15✔
1868
    }
1869

1870
    case Qgis::GeometryType::Line:
10✔
1871
    {
1872
      if ( isMulti )
10✔
1873
      {
1874
        QgsMultiLineString *multiLine = new QgsMultiLineString();
4✔
1875
        geometry.set( multiLine );
4✔
1876
      }
1877
      else
1878
      {
1879
        QgsLineString *line = new QgsLineString();
6✔
1880
        geometry.set( line );
6✔
1881
      }
1882
      break;
10✔
1883
    }
1884

1885
    case Qgis::GeometryType::Polygon:
11✔
1886
    {
1887
      if ( isMulti )
11✔
1888
      {
1889
        QgsLineString *line = new QgsLineString();
2✔
1890
        QgsPolygon *polygon = new QgsPolygon( line );
2✔
1891
        QgsMultiPolygon *multiPolygon = new QgsMultiPolygon();
2✔
1892
        multiPolygon->addGeometry( polygon );
2✔
1893
        geometry.set( multiPolygon );
2✔
1894
      }
1895
      else
1896
      {
1897
        QgsLineString *line = new QgsLineString();
9✔
1898
        QgsPolygon *polygon = new QgsPolygon( line );
9✔
1899
        geometry.set( polygon );
9✔
1900
      }
1901
      break;
11✔
1902
    }
1903

1904
    default:
×
1905
      break;
×
1906
  }
1907

1908
  if ( QgsWkbTypes::hasZ( layer->wkbType() ) )
36✔
1909
  {
1910
    geometry.get()->addZValue( 0 );
12✔
1911
  }
1912

1913
  if ( QgsWkbTypes::hasM( layer->wkbType() ) )
36✔
1914
  {
1915
    geometry.get()->addMValue( 0 );
7✔
1916
  }
1917

1918
  return geometry;
36✔
1919
}
×
1920

1921
QString InputUtils::invalidGeometryWarning( QgsVectorLayer *layer )
6✔
1922
{
1923
  QString msg;
6✔
1924
  if ( !layer )
6✔
1925
  {
1926
    return msg;
×
1927
  }
1928

1929
  int nPoints = 1;
6✔
1930
  if ( layer->geometryType() == Qgis::GeometryType::Line )
6✔
1931
  {
1932
    nPoints = 2;
2✔
1933
  }
1934
  else if ( layer->geometryType() == Qgis::GeometryType::Polygon )
4✔
1935
  {
1936
    nPoints = 3;
2✔
1937
  }
1938

1939
  if ( QgsWkbTypes::isMultiType( layer->wkbType() ) )
6✔
1940
  {
1941
    return tr( "You need to add at least %1 point(s) to every part." ).arg( nPoints );
3✔
1942
  }
1943
  else
1944
  {
1945
    return tr( "You need to add at least %1 point(s)." ).arg( nPoints );
3✔
1946
  }
1947
}
6✔
1948

1949
void InputUtils::updateFeature( const FeatureLayerPair &pair )
×
1950
{
1951
  if ( !pair.layer() )
×
1952
  {
1953
    return;
×
1954
  }
1955

1956
  if ( !pair.feature().isValid() )
×
1957
  {
1958
    return;
×
1959
  }
1960

1961
  if ( !pair.layer()->isEditable() )
×
1962
  {
1963
    pair.layer()->startEditing();
×
1964
  }
1965

1966
  QgsFeature f( pair.feature() );
×
1967
  pair.layer()->updateFeature( f );
×
1968
  pair.layer()->commitChanges();
×
1969
  pair.layer()->triggerRepaint();
×
1970
}
×
1971

1972
QString InputUtils::imageGalleryLocation()
×
1973
{
1974
  QStringList galleryPaths = QStandardPaths::standardLocations( QStandardPaths::PicturesLocation );
×
1975

1976
  if ( galleryPaths.isEmpty() )
×
1977
  {
1978
    CoreUtils::log( QStringLiteral( "Image Picker" ), QStringLiteral( "Could not find standard path to image gallery" ) );
×
1979
    return QString();
×
1980
  }
1981

1982
  return galleryPaths.last();
×
1983
}
×
1984

1985
QString InputUtils::layerAttribution( QgsMapLayer *layer )
2✔
1986
{
1987
  if ( !layer || !layer->isValid() )
2✔
1988
  {
1989
    return QString();
×
1990
  }
1991

1992
  QStringList rights = layer->metadata().rights();
2✔
1993
  if ( !rights.isEmpty() )
2✔
1994
  {
1995
    return rights.join( QStringLiteral( ", " ) );
2✔
1996
  }
1997

1998
  return QString();
1✔
1999
}
2✔
2000

2001
const double PROFILER_THRESHOLD = 0.001;
2002
static double qgsRuntimeProfilerExtractModelAsText( QStringList &lines, const QString &group, const QModelIndex &parent, int level )
×
2003
{
2004
  double total_elapsed = 0.0;
×
2005

2006
  const int rc = QgsApplication::profiler()->rowCount( parent );
×
2007
  for ( int r = 0; r < rc; r++ )
×
2008
  {
2009
    QModelIndex rowIndex = QgsApplication::profiler()->index( r, 0, parent );
×
2010
    if ( QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Group ).toString() != group )
×
2011
      continue;
×
2012
    bool ok;
2013
    double elapsed = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Elapsed ).toDouble( &ok );
×
2014
    if ( !ok )
×
2015
      elapsed = 0.0;
×
2016
    total_elapsed += elapsed;
×
2017

2018
    if ( elapsed > PROFILER_THRESHOLD )
×
2019
    {
2020
      QString name = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Name ).toString();
×
2021
      lines << QStringLiteral( "  %1 %2: %3 sec" ).arg( QStringLiteral( ">" ).repeated( level + 1 ),  name, QString::number( elapsed, 'f', 3 ) );
×
2022
    }
×
2023
    total_elapsed += qgsRuntimeProfilerExtractModelAsText( lines, group, rowIndex, level + 1 );
×
2024
  }
2025
  return total_elapsed;
×
2026
}
2027

2028
QVector<QString> InputUtils::qgisProfilerLog()
×
2029
{
2030
  QVector<QString> lines;
×
2031
  const QString project = QgsProject::instance()->fileName();
×
2032

2033
  if ( !project.isEmpty() )
×
2034
  {
2035
    lines << QStringLiteral( "QgsProject filename: %1" ).arg( project );
×
2036
  }
2037

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

2040
  const auto groups = QgsApplication::profiler()->groups();
×
2041
  for ( const QString &g : groups )
×
2042
  {
2043
    QVector<QString> groupLines;
×
2044
    double elapsed = qgsRuntimeProfilerExtractModelAsText( groupLines, g, QModelIndex(), 0 );
×
2045
    if ( elapsed > PROFILER_THRESHOLD )
×
2046
    {
2047
      lines << QStringLiteral( "  %1: total %2 sec" ).arg( g, QString::number( elapsed, 'f', 3 ) );
×
2048
      lines << groupLines;
×
2049
    }
2050
  }
×
2051
  return lines;
×
2052
}
×
2053

2054
QList<QgsPoint> InputUtils::parsePositionUpdates( const QString &data )
8✔
2055
{
2056
  QList<QgsPoint> parsedUpdates;
8✔
2057
  QStringList positions = data.split( '\n', Qt::SkipEmptyParts );
8✔
2058

2059
  if ( positions.isEmpty() )
8✔
2060
  {
2061
    return parsedUpdates;
2✔
2062
  }
2063

2064
  for ( int ix = 0; ix < positions.size(); ix++ )
14✔
2065
  {
2066
    QStringList coordinates = positions[ix].split( ' ', Qt::SkipEmptyParts );
8✔
2067

2068
    if ( coordinates.size() != 4 )
8✔
2069
    {
2070
      continue;
4✔
2071
    }
2072

2073
    QgsPoint geop;
4✔
2074
    geop.setX( coordinates[0].toDouble() ); // long
4✔
2075
    geop.setY( coordinates[1].toDouble() ); // lat
4✔
2076
    geop.setZ( coordinates[2].toDouble() ); // alt
4✔
2077
    geop.setM( coordinates[3].toDouble() ); // UTC time in secs
4✔
2078
    parsedUpdates << geop;
4✔
2079
  }
8✔
2080

2081
  return parsedUpdates;
6✔
2082
}
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