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

MerginMaps / input / 6393674710

03 Oct 2023 01:28PM UTC coverage: 61.83% (-0.08%) from 61.908%
6393674710

Pull #2826

github

iiLubos
Use QGis functions for converting distance units
Pull Request #2826: Use distance unit from project's CRS for Stakeout panel

7576 of 12253 relevant lines covered (61.83%)

102.02 hits per line

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

53.2
/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::formatDistanceInProjectUnit( InputMapSettings *mapSettings, const double distanceInMeters, int precision )
×
164
{
165
  QgsCoordinateReferenceSystem projectCrs = mapSettings->destinationCrs();
×
166

167
  if ( !projectCrs.isValid() || projectCrs.mapUnits() == Qgis::DistanceUnit::Unknown )
×
168
  {
169
    return QString::number( distanceInMeters, 'f', precision );
×
170
  }
171

172
  double factor = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, projectCrs.mapUnits() );
×
173
  double distance = distanceInMeters * factor;
×
174
  QString abbreviation = QgsUnitTypes::toAbbreviatedString( projectCrs.mapUnits() );
×
175

176
  return QString( "%1 %2" ).arg( QString::number( distance, 'f', precision ), abbreviation );
×
177
}
×
178

179
QString InputUtils::formatDateTimeDiff( const QDateTime &tMin, const QDateTime &tMax )
15✔
180
{
181
  qint64 daysDiff = tMin.daysTo( tMax );
15✔
182

183
  // datetime is invalid
184
  if ( daysDiff < 0 )
15✔
185
  {
186
    return INVALID_DATETIME_STR;
×
187
  }
188

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

237
  return INVALID_DATETIME_STR;
238
}
239

240
void InputUtils::setExtentToFeature( const FeatureLayerPair &pair, InputMapSettings *mapSettings, double panelOffsetRatio )
×
241
{
242

243
  if ( !mapSettings )
×
244
    return;
×
245

246
  if ( !pair.layer() )
×
247
    return;
×
248

249
  if ( !pair.feature().isValid() )
×
250
    return;
×
251

252
  QgsGeometry geom = pair.feature().geometry();
×
253
  if ( geom.isNull() || !geom.constGet() )
×
254
    return;
×
255

256
  QgsRectangle bbox = mapSettings->mapSettings().layerExtentToOutputExtent( pair.layer(), geom.boundingBox() );
×
257
  QgsRectangle currentExtent = mapSettings->mapSettings().extent();
×
258
  QgsPointXY currentExtentCenter = currentExtent.center();
×
259
  QgsPointXY featureCenter = bbox.center();
×
260

261
  double panelOffset = ( currentExtent.yMaximum() - currentExtent.yMinimum() ) * panelOffsetRatio / 2;
×
262
  double offsetX = currentExtentCenter.x() - featureCenter.x();
×
263
  double offsetY = currentExtentCenter.y() - featureCenter.y();
×
264
  currentExtent.setXMinimum( currentExtent.xMinimum() - offsetX );
×
265
  currentExtent.setXMaximum( currentExtent.xMaximum() - offsetX );
×
266
  currentExtent.setYMinimum( currentExtent.yMinimum() - offsetY - panelOffset );
×
267
  currentExtent.setYMaximum( currentExtent.yMaximum() - offsetY - panelOffset );
×
268
  mapSettings->setExtent( currentExtent );
×
269
}
×
270

271
double InputUtils::convertCoordinateString( const QString &rationalValue )
×
272
{
273
  QStringList values = rationalValue.split( "," );
×
274
  if ( values.size() != 3 ) return 0;
×
275

276
  double degrees = ratherZeroThanNaN( convertRationalNumber( values.at( 0 ) ) );
×
277
  double minutes = ratherZeroThanNaN( convertRationalNumber( values.at( 1 ) ) );
×
278
  double seconds = ratherZeroThanNaN( convertRationalNumber( values.at( 2 ) ) );
×
279

280
  double result = degrees + minutes / 60 + seconds / 3600;
×
281
  return result;
×
282
}
×
283

284
QString InputUtils::degreesString( const QgsPoint &point )
×
285
{
286
  if ( point.isEmpty() )
×
287
  {
288
    return QLatin1String();
×
289
  }
290

291
  // QGeoCoordinate formatter uses lat/long order, but we (and QGIS) use long/lat order,
292
  // so here we need to first pass y and then x.
293
  return QGeoCoordinate( point.y(), point.x() ).toString( QGeoCoordinate::DegreesMinutesWithHemisphere );
×
294
}
295

296
double InputUtils::convertRationalNumber( const QString &rationalValue )
×
297
{
298
  if ( rationalValue.isEmpty() )
×
299
    return std::numeric_limits<double>::quiet_NaN();
×
300

301
  QStringList number = rationalValue.split( "/" );
×
302
  if ( number.size() != 2 )
×
303
    return std::numeric_limits<double>::quiet_NaN();
×
304

305
  double numerator = number.at( 0 ).toDouble();
×
306
  double denominator = number.at( 1 ).toDouble();
×
307
  if ( denominator == 0 )
×
308
    return denominator;
×
309

310
  return numerator / denominator;
×
311
}
×
312

313
double InputUtils::mapSettingsScale( InputMapSettings *ms )
×
314
{
315
  if ( !ms ) return 1;
×
316
  return 1 / ms->mapUnitsPerPixel();
×
317
}
318

319
double InputUtils::mapSettingsOffsetX( InputMapSettings *ms )
×
320
{
321
  if ( !ms ) return 0;
×
322
  return -ms->visibleExtent().xMinimum();
×
323
}
324

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

331
double InputUtils::mapSettingsDPR( InputMapSettings *ms )
×
332
{
333
  if ( !ms ) return 1;
×
334
  return ms->devicePixelRatio();
×
335
}
336

337
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, const QgsCoordinateReferenceSystem &destinationCRS, const QgsCoordinateTransformContext &context )
1✔
338
{
339
  QgsGeometry g( geometry );
1✔
340

341
  QgsCoordinateTransform ct( sourceCRS, destinationCRS, context );
1✔
342
  if ( !ct.isShortCircuited() )
1✔
343
  {
344
    try
345
    {
346
      g.transform( ct );
×
347
    }
348
    catch ( QgsCsException &e )
×
349
    {
350
      Q_UNUSED( e )
351
      return QgsGeometry();
×
352
    }
×
353
  }
354

355
  return g;
1✔
356
}
1✔
357

358
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, QgsVectorLayer *targetLayer )
1✔
359
{
360
  if ( !targetLayer || !targetLayer->isValid() )
1✔
361
  {
362
    return QgsGeometry();
×
363
  }
364

365
  return transformGeometry( geometry, sourceCRS, targetLayer->crs(), targetLayer->transformContext() );
2✔
366
}
367

368
QgsGeometry InputUtils::transformGeometryToMapWithLayer( const QgsGeometry &geometry, QgsVectorLayer *sourceLayer, InputMapSettings *targetSettings )
×
369
{
370
  if ( !sourceLayer || !sourceLayer->isValid() || !targetSettings )
×
371
  {
372
    return QgsGeometry();
×
373
  }
374

375
  return transformGeometry( geometry, sourceLayer->crs(), targetSettings->destinationCrs(), targetSettings->transformContext() );
×
376
}
377

378
QgsGeometry InputUtils::transformGeometryToMapWithCRS( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, InputMapSettings *targetSettings )
×
379
{
380
  if ( !targetSettings )
×
381
  {
382
    return QgsGeometry();
×
383
  }
384

385
  return transformGeometry( geometry, sourceCRS, targetSettings->destinationCrs(), targetSettings->transformContext() );
×
386
}
387

388
QgsGeometry InputUtils::extractGeometry( const FeatureLayerPair &pair )
×
389
{
390
  if ( !pair.isValid() )
×
391
    return QgsGeometry();
×
392

393
  return pair.feature().geometry();
×
394
}
395

396
QString InputUtils::geometryLengthAsString( const QgsGeometry &geometry )
×
397
{
398
  QgsDistanceArea distanceArea;
×
399
  distanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
×
400
  distanceArea.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() );
×
401

402
  qreal length = distanceArea.measureLength( geometry );
×
403

404
  if ( qgsDoubleNear( length, 0 ) )
×
405
  {
406
    return "0 m";
×
407
  }
408

409
  return distanceArea.formatDistance( length, 2, distanceArea.lengthUnits() );
×
410
}
×
411

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

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

437
    case Qgis::GeometryType::Line:
×
438
    {
439
      const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom );
×
440
      if ( line )
×
441
      {
442
        data << 1;
×
443
        addLineString( line, data );
×
444
      }
445
      break;
×
446
    }
447

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

470
    case Qgis::GeometryType::Unknown:
×
471
    case Qgis::GeometryType::Null:
472
      break;
×
473
  }
474
}
×
475

476
QVector<double> InputUtils::extractGeometryCoordinates( const QgsGeometry &geometry )
×
477
{
478
  if ( geometry.isNull() )
×
479
    return QVector<double>();
×
480

481
  QVector<double> data;
×
482

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

498
  return data;
×
499
}
×
500

501
QString InputUtils::filesToString( QList<MerginFile> files )
×
502
{
503
  QStringList resultList;
×
504
  for ( MerginFile file : files )
×
505
  {
506
    resultList << file.path;
×
507
  }
×
508
  return resultList.join( ", " );
×
509
}
×
510

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

536
bool InputUtils::acquireCameraPermission()
×
537
{
538
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
539
  {
540
    return mAndroidUtils->requestCameraPermission();
×
541
  }
542
  return true;
×
543
}
544

545
bool InputUtils::isBluetoothTurnedOn()
×
546
{
547
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
548
  {
549
    return mAndroidUtils->isBluetoothTurnedOn();
×
550
  }
551
  return true;
×
552
}
553

554
void InputUtils::turnBluetoothOn()
×
555
{
556
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
557
  {
558
    mAndroidUtils->turnBluetoothOn();
×
559
  }
560
}
×
561

562
void InputUtils::quitApp()
×
563
{
564
  if ( appPlatform() == QStringLiteral( "android" ) )
×
565
  {
566
    AndroidUtils::quitApp();
×
567
  }
568
  else
569
  {
570
    QCoreApplication::quit();
×
571
  }
572
}
×
573

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

592
bool InputUtils::isMobilePlatform()
3✔
593
{
594
  QString platform = appPlatform();
3✔
595
  return platform == QStringLiteral( "android" ) || platform == QStringLiteral( "ios" );
12✔
596
}
3✔
597

598
void InputUtils::onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level )
17✔
599
{
600
  QString levelStr;
17✔
601
  switch ( level )
17✔
602
  {
603
    case Qgis::MessageLevel::Warning:
17✔
604
      levelStr = "Warning";
17✔
605
      break;
17✔
606
    case Qgis::MessageLevel::Critical:
×
607
      levelStr = "Error";
×
608
      break;
×
609
    default:
×
610
      break;
×
611
  }
612

613
  CoreUtils::log( "QGIS " + tag, levelStr + ": " + message );
17✔
614
}
17✔
615

616
bool InputUtils::cpDir( const QString &srcPath, const QString &dstPath, bool onlyDiffable )
57✔
617
{
618
  bool result  = true;
57✔
619
  QDir parentDstDir( QFileInfo( dstPath ).path() );
114✔
620
  if ( !parentDstDir.mkpath( dstPath ) )
57✔
621
  {
622
    CoreUtils::log( "cpDir", QString( "Cannot make path %1" ).arg( dstPath ) );
×
623
    return false;
×
624
  }
625

626
  QDir srcDir( srcPath );
57✔
627
  const QFileInfoList fileInfoList = srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden );
57✔
628
  foreach ( const QFileInfo &info, fileInfoList )
546✔
629
  {
630
    QString fileName = info.fileName();
489✔
631

632
#ifdef ANDROID
633
    // https://bugreports.qt.io/browse/QTBUG-114219
634
    if ( fileName.startsWith( "assets:/" ) )
635
    {
636
      fileName.remove( 0, 8 );
637
    }
638
#endif
639

640
    QString srcItemPath = srcPath + "/" + fileName;
489✔
641
    QString dstItemPath = dstPath + "/" + fileName;
489✔
642

643
    if ( info.isDir() )
489✔
644
    {
645
      if ( !cpDir( srcItemPath, dstItemPath ) )
2✔
646
      {
647
        CoreUtils::log( "cpDir", QString( "Cannot copy a dir from %1 to %2" ).arg( srcItemPath ).arg( dstItemPath ) );
×
648
        result = false;
×
649
      }
650
    }
651
    else if ( info.isFile() )
487✔
652
    {
653
      if ( onlyDiffable && !MerginApi::isFileDiffable( fileName ) )
487✔
654
        continue;
×
655

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

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

688
    if ( QFile::rename( srcPath, newPath ) ) return newPath;
×
689
  }
×
690

691
  return QString();
×
692
}
693

694
bool InputUtils::renameFile( const QString &srcPath, const QString &dstPath )
1✔
695
{
696
  QFileInfo fi( dstPath );
1✔
697
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
1✔
698
  {
699
    return false;
×
700
  }
701
  return QFile::rename( srcPath, dstPath );
1✔
702
}
1✔
703

704
void InputUtils::showNotification( const QString &message )
×
705
{
706
  emit showNotificationRequested( message );
×
707
}
×
708

709
double InputUtils::ratherZeroThanNaN( double d )
×
710
{
711
  return ( isnan( d ) ) ? 0.0 : d;
×
712
}
713

714
/**
715
 * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
716
 */
717
QgsCoordinateReferenceSystem InputUtils::coordinateReferenceSystemFromEpsgId( long epsg )
12✔
718
{
719
  return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
12✔
720
}
721

722
QgsPointXY InputUtils::pointXY( double x, double y )
1✔
723
{
724
  return QgsPointXY( x, y );
1✔
725
}
726

727
QgsPoint InputUtils::point( double x, double y, double z, double m )
2✔
728
{
729
  return QgsPoint( x, y, z, m );
2✔
730
}
731

732
QgsGeometry InputUtils::emptyGeometry()
×
733
{
734
  return QgsGeometry();
×
735
}
736

737
QgsFeature InputUtils::emptyFeature()
×
738
{
739
  return QgsFeature();
×
740
}
741

742
bool InputUtils::isEmptyGeometry( const QgsGeometry &geometry )
×
743
{
744
  return geometry.isEmpty();
×
745
}
746

747
QgsPoint InputUtils::coordinateToPoint( const QGeoCoordinate &coor )
×
748
{
749
  return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
×
750
}
751

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

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

785
  return QgsPointXY();
×
786
}
787

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

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

822
  return QgsPoint();
×
823
}
824

825
QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint )
×
826
{
827
  if ( !mapSettings || srcPoint.isEmpty() )
×
828
    return QPointF();
×
829

830
  QgsPoint mapcrsPoint = transformPoint( srcCrs, mapSettings->destinationCrs(), mapSettings->transformContext(), srcPoint );
×
831
  return mapSettings->coordinateToScreen( mapcrsPoint );
×
832
}
×
833

834
double InputUtils::screenUnitsToMeters( InputMapSettings *mapSettings, int baseLengthPixels )
30✔
835
{
836
  if ( mapSettings == nullptr ) return 0.0;
30✔
837

838
  QgsDistanceArea mDistanceArea;
30✔
839
  mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
30✔
840
  mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
30✔
841

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

851
QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSettings )
5✔
852
{
853
  if ( !mapSettings )
5✔
854
    return QgsPoint();
×
855

856
  if ( mapPosition.isNull() )
5✔
857
    return QgsPoint();
×
858

859
  QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition );
5✔
860
  QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 );
5✔
861

862
  const QgsPointXY transformedXY = transformPoint(
10✔
863
                                     mapSettings->destinationCrs(),
10✔
864
                                     crsGPS,
865
                                     QgsCoordinateTransformContext(),
10✔
866
                                     positionMapCrs
867
                                   );
5✔
868

869
  if ( transformedXY.isEmpty() )
5✔
870
  {
871
    // point could not be transformed
872
    return QgsPoint();
1✔
873
  }
874

875
  return QgsPoint( transformedXY );
4✔
876
}
5✔
877

878
bool InputUtils::fileExists( const QString &path )
8✔
879
{
880
  QFileInfo check_file( path );
8✔
881
  // check if file exists and if yes: Is it really a file and no directory?
882
  return ( check_file.exists() && check_file.isFile() );
16✔
883
}
8✔
884

885
QString InputUtils::resolveTargetDir( const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
7✔
886
{
887
  QString expression;
7✔
888
  QMap<QString, QVariant> collection = config.value( QStringLiteral( "PropertyCollection" ) ).toMap();
21✔
889
  QMap<QString, QVariant> props = collection.value( QStringLiteral( "properties" ) ).toMap();
21✔
890

891
  if ( !props.isEmpty() )
7✔
892
  {
893
    QMap<QString, QVariant> propertyRootPath = props.value( QStringLiteral( "propertyRootPath" ) ).toMap();
3✔
894
    expression = propertyRootPath.value( QStringLiteral( "expression" ), QString() ).toString();
2✔
895
  }
1✔
896

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

915
QString InputUtils::resolvePrefixForRelativePath( int relativeStorageMode, const QString &homePath, const QString &targetDir )
4✔
916
{
917
  if ( relativeStorageMode == 1 )
4✔
918
  {
919
    return homePath;
2✔
920
  }
921
  else if ( relativeStorageMode == 2 )
2✔
922
  {
923
    return targetDir;
1✔
924
  }
925
  else
926
  {
927
    return QString();
1✔
928
  }
929
}
930

931
QString InputUtils::getAbsolutePath( const QString &path, const QString &prefixPath )
5✔
932
{
933
  return ( prefixPath.isEmpty() ) ? path : QStringLiteral( "%1/%2" ).arg( prefixPath ).arg( path );
9✔
934
}
935

936
QString InputUtils::resolvePath( const QString &path, const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
3✔
937
{
938
  int relativeStorageMode = config.value( QStringLiteral( "RelativeStorage" ) ).toInt();
6✔
939
  QString targetDir = resolveTargetDir( homePath, config, pair, activeProject );
3✔
940
  QString prefixToRelativePath = resolvePrefixForRelativePath( relativeStorageMode, homePath, targetDir );
3✔
941

942
  return getAbsolutePath( path, prefixToRelativePath );
6✔
943
}
3✔
944

945
QString InputUtils::getRelativePath( const QString &path, const QString &prefixPath )
5✔
946
{
947
  QString modPath = path;
5✔
948
  QString filePrefix( "file://" );
5✔
949

950
  if ( path.startsWith( filePrefix ) )
5✔
951
  {
952
    modPath = modPath.replace( filePrefix, QString() );
1✔
953
  }
954

955
  if ( prefixPath.isEmpty() ) return modPath;
5✔
956

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

972
    if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
×
973
    {
974
      return canonicalPath.replace( prefixCanonicalPath, QString() );
×
975
    }
976
  }
×
977

978
  return QString();
1✔
979
}
5✔
980

981
void InputUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
×
982
{
983
  QgsMessageLog::logMessage( message, tag, level );
×
984
}
×
985

986
void InputUtils::log( const QString &context, const QString &message )
×
987
{
988
  CoreUtils::log( context, message );
×
989
}
×
990

991
const QUrl InputUtils::getThemeIcon( const QString &name )
1✔
992
{
993
  QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
1✔
994
  QgsDebugMsgLevel( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ), 2 );
995
  return QUrl( path );
2✔
996
}
1✔
997

998
const QUrl InputUtils::getEditorComponentSource( const QString &widgetName, const QVariantMap &config, const QgsField &field )
2✔
999
{
1000
  QString path( "../editor/input%1.qml" );
2✔
1001

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

1018
  if ( field.name().contains( "qrcode", Qt::CaseInsensitive ) || field.alias().contains( "qrcode", Qt::CaseInsensitive ) )
2✔
1019
  {
1020
    return QUrl( path.arg( QStringLiteral( "qrcodereader" ) ) );
×
1021
  }
1022

1023
  if ( widgetName == QStringLiteral( "textedit" ) )
2✔
1024
  {
1025
    if ( config.value( "IsMultiline" ).toBool() )
×
1026
    {
1027
      return QUrl( path.arg( QStringLiteral( "texteditmultiline" ) ) );
×
1028
    }
1029
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1030
  }
1031

1032
  if ( widgetName == QStringLiteral( "valuerelation" ) )
2✔
1033
  {
1034
    const QgsMapLayer *referencedLayer = QgsProject::instance()->mapLayer( config.value( "Layer" ).toString() );
×
1035
    const QgsVectorLayer *layer = qobject_cast<const QgsVectorLayer *>( referencedLayer );
×
1036

1037
    if ( layer )
×
1038
    {
1039
      int featuresCount = layer->dataProvider()->featureCount();
×
1040
      if ( featuresCount > 4 )
×
1041
        return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1042
    }
1043

1044
    if ( config.value( "AllowMulti" ).toBool() )
×
1045
    {
1046
      return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1047
    }
1048

1049
    return QUrl( path.arg( QLatin1String( "valuerelationcombobox" ) ) );
×
1050
  }
1051

1052
  QStringList supportedWidgets = { QStringLiteral( "textedit" ),
×
1053
                                   QStringLiteral( "valuemap" ),
2✔
1054
                                   QStringLiteral( "valuerelation" ),
2✔
1055
                                   QStringLiteral( "checkbox" ),
2✔
1056
                                   QStringLiteral( "externalresource" ),
2✔
1057
                                   QStringLiteral( "datetime" ),
2✔
1058
                                   QStringLiteral( "range" ),
2✔
1059
                                   QStringLiteral( "relation" ),
2✔
1060
                                   QStringLiteral( "relationreference" )
2✔
1061
                                 };
38✔
1062
  if ( supportedWidgets.contains( widgetName ) )
2✔
1063
  {
1064
    return QUrl( path.arg( widgetName ) );
2✔
1065
  }
1066
  else
1067
  {
1068
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
2✔
1069
  }
1070
}
2✔
1071

1072
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field )
8✔
1073
{
1074
  if ( field.isNumeric() )
8✔
1075
    return getEditorWidgetSetup( field, QStringLiteral( "Range" ) );
12✔
1076
  else if ( field.isDateOrTime() )
4✔
1077
    return getEditorWidgetSetup( field, QStringLiteral( "DateTime" ) );
×
1078
  else if ( field.type() == QVariant::Bool )
4✔
1079
    return getEditorWidgetSetup( field, QStringLiteral( "CheckBox" ) );
×
1080
  else
1081
    return getEditorWidgetSetup( field, QStringLiteral( "TextEdit" ) );
12✔
1082
}
1083

1084
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field, const QString &widgetType, const QVariantMap &additionalArgs )
9✔
1085
{
1086
  if ( field.name() == QStringLiteral( "fid" ) )
9✔
1087
    return QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
4✔
1088

1089
  if ( widgetType.isEmpty() )
7✔
1090
  {
1091
    return QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() );
×
1092
  }
1093
  else
1094
  {
1095
    QMultiMap<QString, QVariant> config;
7✔
1096
    config = config.unite( QMultiMap( additionalArgs ) );
7✔
1097

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

1129
    QVariantMap cfg;
7✔
1130
    QList<QString> keys = config.uniqueKeys();
7✔
1131
    for ( int i = 0; i < keys.size(); i++ )
28✔
1132
    {
1133
      cfg.insert( keys.at( i ), config.value( keys.at( i ) ) );
21✔
1134
    }
1135

1136
    return QgsEditorWidgetSetup( widgetType, cfg );
7✔
1137
  }
7✔
1138
}
1139

1140
QString InputUtils::geometryFromLayer( QgsVectorLayer *layer )
11✔
1141
{
1142
  if ( layer )
11✔
1143
  {
1144
    switch ( layer->geometryType() )
11✔
1145
    {
1146
      case Qgis::GeometryType::Point: return QStringLiteral( "point" );
20✔
1147
      case Qgis::GeometryType::Line: return QStringLiteral( "linestring" );
2✔
1148
      case Qgis::GeometryType::Polygon: return QStringLiteral( "polygon" );
×
1149
      case Qgis::GeometryType::Null: return QStringLiteral( "nullGeo" );
×
1150
      default: return QString();
×
1151
    }
1152
  }
1153
  return QString();
×
1154
}
1155

1156
bool InputUtils::isPointLayer( QgsVectorLayer *layer )
×
1157
{
1158
  return geometryFromLayer( layer ) == "point";
×
1159
}
1160

1161
bool InputUtils::isLineLayer( QgsVectorLayer *layer )
×
1162
{
1163
  return geometryFromLayer( layer ) == "linestring";
×
1164
}
1165

1166
bool InputUtils::isPolygonLayer( QgsVectorLayer *layer )
×
1167
{
1168
  return geometryFromLayer( layer ) == "polygon";
×
1169
}
1170

1171
bool InputUtils::isNoGeometryLayer( QgsVectorLayer *layer )
×
1172
{
1173
  return geometryFromLayer( layer ) == "nullGeo";
×
1174
}
1175

1176
bool InputUtils::isMultiPartLayer( QgsVectorLayer *layer )
×
1177
{
1178
  if ( !layer )
×
1179
  {
1180
    return false;
×
1181
  }
1182
  return QgsWkbTypes::isMultiType( layer->wkbType() );
×
1183
}
1184

1185
bool InputUtils::isSpatialLayer( QgsVectorLayer *layer )
×
1186
{
1187
  if ( !layer )
×
1188
  {
1189
    return false;
×
1190
  }
1191
  return layer->isSpatial();
×
1192
}
1193

1194
qreal InputUtils::calculateScreenDpr()
1✔
1195
{
1196
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1197

1198
  if ( !screens.isEmpty() )
1✔
1199
  {
1200
    QScreen *screen = screens.at( 0 );
1✔
1201
    double dpiX = screen->physicalDotsPerInchX();
1✔
1202
    double dpiY = screen->physicalDotsPerInchY();
1✔
1203

1204
    qreal realDpi = dpiX < dpiY ? dpiX : dpiY;
1✔
1205
    realDpi = realDpi * screen->devicePixelRatio();
1✔
1206

1207
    return realDpi / 160.;
1✔
1208
  }
1209

1210
  return 1;
×
1211
}
1✔
1212

1213
qreal InputUtils::calculateDpRatio()
1✔
1214
{
1215
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1216

1217
  if ( !screens.isEmpty() )
1✔
1218
  {
1219
    QScreen *screen = screens.at( 0 );
1✔
1220

1221
    qreal realDpr = calculateScreenDpr();
1✔
1222
    return realDpr / screen->devicePixelRatio();
1✔
1223
  }
1224

1225
  return 1;
×
1226
}
1✔
1227

1228
bool InputUtils::equals( const QPointF &a, const QPointF &b, double epsilon )
8✔
1229
{
1230
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
8✔
1231
}
1232

1233
bool InputUtils::equals( const QgsPointXY &a, const QgsPointXY &b, double epsilon )
42✔
1234
{
1235
  if ( a.isEmpty() && b.isEmpty() )
42✔
1236
    return true;
3✔
1237
  if ( a.isEmpty() != b.isEmpty() )
39✔
1238
    return false;
4✔
1239

1240
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
35✔
1241
}
1242

1243
bool InputUtils::equals( const QgsPoint &a, const QgsPoint &b, double epsilon )
316✔
1244
{
1245
  if ( a.isEmpty() && b.isEmpty() )
316✔
1246
    return true;
35✔
1247
  if ( a.isEmpty() != b.isEmpty() )
281✔
1248
    return false;
2✔
1249

1250
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
279✔
1251
}
1252

1253
QString InputUtils::formatPoint(
1✔
1254
  const QgsPoint &point,
1255
  QgsCoordinateFormatter::Format format,
1256
  int decimals,
1257
  QgsCoordinateFormatter::FormatFlags flags )
1258
{
1259
  return QgsCoordinateFormatter::format( point, format, decimals, flags );
1✔
1260
}
1261

1262
QString InputUtils::formatDistance( double distance,
10✔
1263
                                    Qgis::DistanceUnit units,
1264
                                    int decimals,
1265
                                    Qgis::SystemOfMeasurement destSystem )
1266
{
1267
  double destDistance;
1268
  Qgis::DistanceUnit destUnits;
1269

1270
  humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
10✔
1271

1272
  return QStringLiteral( "%1 %2" )
20✔
1273
         .arg( QString::number( destDistance, 'f', decimals ) )
20✔
1274
         .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
30✔
1275
}
1276

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

1299
void InputUtils::formatToMetricDistance( double srcDistance,
11✔
1300
    Qgis::DistanceUnit srcUnits,
1301
    double &destDistance,
1302
    Qgis::DistanceUnit &destUnits )
1303
{
1304
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Millimeters );
11✔
1305
  if ( dist < 0 )
11✔
1306
  {
1307
    destDistance = 0;
1✔
1308
    destUnits = Qgis::DistanceUnit::Millimeters;
1✔
1309
    return;
1✔
1310
  }
1311

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

1320
  double mmToM = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, Qgis::DistanceUnit::Millimeters );
5✔
1321
  if ( dist > mmToM )
5✔
1322
  {
1323
    destDistance = dist / mmToM;
4✔
1324
    destUnits = Qgis::DistanceUnit::Meters;
4✔
1325
    return;
4✔
1326
  }
1327

1328
  double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Centimeters, Qgis::DistanceUnit::Millimeters );
1✔
1329
  if ( dist > mmToCm )
1✔
1330
  {
1331
    destDistance = dist / mmToCm;
1✔
1332
    destUnits = Qgis::DistanceUnit::Centimeters;
1✔
1333
    return;
1✔
1334
  }
1335

1336
  destDistance = dist;
×
1337
  destUnits = Qgis::DistanceUnit::Millimeters;
×
1338
}
1339

1340
void InputUtils::formatToImperialDistance( double srcDistance,
2✔
1341
    Qgis::DistanceUnit srcUnits,
1342
    double &destDistance,
1343
    Qgis::DistanceUnit &destUnits )
1344
{
1345
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
2✔
1346
  if ( dist < 0 )
2✔
1347
  {
1348
    destDistance = 0;
×
1349
    destUnits = Qgis::DistanceUnit::Feet;
×
1350
    return;
×
1351
  }
1352

1353
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Miles, Qgis::DistanceUnit::Feet );
2✔
1354
  if ( dist > feetToMile )
2✔
1355
  {
1356
    destDistance = dist / feetToMile;
1✔
1357
    destUnits = Qgis::DistanceUnit::Miles;
1✔
1358
    return;
1✔
1359
  }
1360

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

1369
  destDistance = dist;
×
1370
  destUnits = Qgis::DistanceUnit::Feet;
×
1371
  return;
×
1372
}
1373

1374
void InputUtils::formatToUSCSDistance( double srcDistance,
1✔
1375
                                       Qgis::DistanceUnit srcUnits,
1376
                                       double &destDistance,
1377
                                       Qgis::DistanceUnit &destUnits )
1378
{
1379
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
1✔
1380
  if ( dist < 0 )
1✔
1381
  {
1382
    destDistance = 0;
×
1383
    destUnits = Qgis::DistanceUnit::Feet;
×
1384
    return;
×
1385
  }
1386

1387
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::NauticalMiles, Qgis::DistanceUnit::Feet );
1✔
1388
  if ( dist > feetToMile )
1✔
1389
  {
1390
    destDistance = dist / feetToMile;
1✔
1391
    destUnits = Qgis::DistanceUnit::NauticalMiles;
1✔
1392
    return;
1✔
1393
  }
1394

1395
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
×
1396
  if ( dist > feetToYard )
×
1397
  {
1398
    destDistance = dist / feetToYard;
×
1399
    destUnits = Qgis::DistanceUnit::Yards;
×
1400
    return;
×
1401
  }
1402

1403
  destDistance = dist;
×
1404
  destUnits = Qgis::DistanceUnit::Feet;
×
1405
  return;
×
1406
}
1407

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

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

1437
QString InputUtils::evaluateExpression( const FeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
1✔
1438
{
1439
  QList<QgsExpressionContextScope *> scopes;
1✔
1440
  scopes << QgsExpressionContextUtils::globalScope();
1✔
1441
  scopes << QgsExpressionContextUtils::projectScope( activeProject );
1✔
1442
  scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
1✔
1443

1444
  QgsExpressionContext context( scopes );
1✔
1445
  context.setFeature( pair.feature() );
1✔
1446
  QgsExpression expr( expression );
1✔
1447
  return expr.evaluate( &context ).toString();
3✔
1448
}
1✔
1449

1450
QString InputUtils::fieldType( const QgsField &field )
×
1451
{
1452
  return QVariant( field.type() ).typeName();
×
1453
}
1454

1455
QString InputUtils::dateTimeFieldFormat( const QString &fieldFormat )
×
1456
{
1457
  if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
×
1458
  {
1459
    return QString( "Date" );
×
1460
  }
1461
  else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
×
1462
  {
1463
    return QString( "Time" );
×
1464
  }
1465
  // cppcheck-suppress duplicateBranch
1466
  else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
×
1467
  {
1468
    return QString( "Date Time" );
×
1469
  }
1470
  else
1471
  {
1472
    return QString( "Date Time" );
×
1473
  }
1474
}
1475

1476
bool InputUtils::isFeatureIdValid( qint64 featureId )
2✔
1477
{
1478
  return !FID_IS_NEW( featureId ) && !FID_IS_NULL( featureId );
2✔
1479
}
1480

1481
QgsRectangle InputUtils::stakeoutPathExtent(
5✔
1482
  MapPosition *mapPosition,
1483
  const FeatureLayerPair &targetFeature,
1484
  InputMapSettings *mapSettings,
1485
  double mapExtentOffset
1486
)
1487
{
1488
  if ( !mapPosition || !mapSettings || !targetFeature.isValid() )
5✔
1489
    return QgsRectangle();
×
1490

1491
  QgsRectangle extent = mapSettings->extent();
5✔
1492

1493
  // We currently support only point geometries
1494
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
5✔
1495
    return extent;
×
1496

1497
  if ( !mapPosition->positionKit() || !mapPosition->mapSettings() )
5✔
1498
    return extent;
×
1499

1500
  //
1501
  // In order to compute stakeout extent, we first compute distance to target feature and
1502
  // based on that we update the extent and scale. Logic for scale computation is in distanceToScale function.
1503
  // Moreover, when distance to target point is lower then 1 meter, extent is centered to target point, otherwise
1504
  // it is centered to GPS position. This has been added in order to reduce "jumps" of canvas when user is near the target.
1505
  //
1506

1507
  QgsPoint gpsPointRaw = mapPosition->positionKit()->positionCoordinate();
5✔
1508

1509
  qreal distance = distanceBetweenGpsAndFeature( gpsPointRaw, targetFeature, mapSettings );
5✔
1510
  qreal scale = distanceToScale( distance );
5✔
1511
  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✔
1512

1513
  if ( mapExtentOffset > 0 )
5✔
1514
  {
1515
    panelOffset = mapExtentOffset / 2.0;
×
1516
  }
1517

1518
  if ( distance <= 1 )
5✔
1519
  {
1520
    // center to target point
1521
    QgsPoint targetPointRaw( extractPointFromFeature( targetFeature ) );
1✔
1522
    QgsPointXY targetPointInMapCRS = transformPoint(
2✔
1523
                                       targetFeature.layer()->crs(),
2✔
1524
                                       mapSettings->destinationCrs(),
2✔
1525
                                       mapSettings->transformContext(),
2✔
1526
                                       targetPointRaw
1527
                                     );
1✔
1528

1529
    if ( targetPointInMapCRS.isEmpty() )
1✔
1530
    {
1531
      // unsuccessful transform
1532
      return extent;
×
1533
    }
1534

1535
    QgsPointXY targetPointInCanvasXY = mapSettings->coordinateToScreen( QgsPoint( targetPointInMapCRS ) );
1✔
1536
    QgsPointXY centerInCanvasXY( targetPointInCanvasXY.x(), targetPointInCanvasXY.y() + panelOffset );
1✔
1537
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
1✔
1538

1539
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
1✔
1540
  }
1✔
1541
  else
1542
  {
1543
    // center to GPS position
1544
    QgsPointXY gpsPointInCanvasXY = mapPosition->screenPosition();
4✔
1545
    QgsPointXY centerInCanvasXY( gpsPointInCanvasXY.x(), gpsPointInCanvasXY.y() + panelOffset );
4✔
1546
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
4✔
1547

1548
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
4✔
1549
  }
1550

1551
  return extent;
5✔
1552
}
5✔
1553

1554
QgsGeometry InputUtils::stakeoutGeometry( const QgsPoint &mapPosition, const FeatureLayerPair &target, InputMapSettings *mapSettings )
×
1555
{
1556
  if ( !mapSettings || !target.isValid() )
×
1557
    return QgsGeometry();
×
1558

1559
  QgsPointXY targetInLayerCoordinates = target.feature().geometry().asPoint();
×
1560
  QgsPointXY t = transformPointXY( target.layer()->crs(), mapSettings->destinationCrs(), mapSettings->transformContext(), targetInLayerCoordinates );
×
1561

1562
  QVector<QgsPoint> points { mapPosition, QgsPoint( t ) };
×
1563

1564
  return QgsGeometry::fromPolyline( points );
×
1565
}
×
1566

1567
qreal InputUtils::distanceToScale( qreal distance )
29✔
1568
{
1569
  // Stakeout extent scale is computed based on these (empirically found) conditions:
1570
  //   - if distance is > 10m, use 1:205 scale (~ 5m on mobile)
1571
  //   - if distance is 3-10m, use 1:105 scale (~ 2m on mobile)
1572
  //   - if distance is 1-3m,  use 1:55 scale  (~ 1m on mobile)
1573
  //   - if distance is < 1m,  use 1:25 scale  (~ 0.5m on mobile)
1574

1575
  qreal scale = 205;
29✔
1576

1577
  if ( distance <= 1 )
29✔
1578
  {
1579
    scale = 25;
9✔
1580
  }
1581
  else if ( distance <= 3 && distance > 1 )
20✔
1582
  {
1583
    scale = 55;
7✔
1584
  }
1585
  else if ( distance <= 10 && distance > 3 )
13✔
1586
  {
1587
    scale = 105;
5✔
1588
  }
1589

1590
  return scale;
29✔
1591
}
1592

1593
qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
6✔
1594
{
1595
  if ( !mapSettings || !targetFeature.isValid() )
6✔
1596
    return -1;
×
1597

1598
  // We calculate distance only between points
1599
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
6✔
1600
    return -1;
×
1601

1602
  // Transform gps position to map CRS
1603
  QgsPointXY transformedPosition = transformPoint(
12✔
1604
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
12✔
1605
                                     mapSettings->destinationCrs(),
12✔
1606
                                     mapSettings->transformContext(),
12✔
1607
                                     gpsPosition
1608
                                   );
6✔
1609

1610
  if ( transformedPosition.isEmpty() )
6✔
1611
  {
1612
    return -1;
×
1613
  }
1614

1615
  // Transform target point to map CRS
1616
  QgsPoint target( extractPointFromFeature( targetFeature ) );
6✔
1617
  QgsPointXY transformedTarget = transformPoint(
12✔
1618
                                   targetFeature.layer()->crs(),
12✔
1619
                                   mapSettings->destinationCrs(),
12✔
1620
                                   mapSettings->transformContext(),
12✔
1621
                                   target
1622
                                 );
6✔
1623

1624
  if ( transformedTarget.isEmpty() )
6✔
1625
  {
1626
    return -1;
×
1627
  }
1628

1629
  QgsDistanceArea distanceArea;
6✔
1630
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
6✔
1631

1632
  qreal distance = distanceArea.measureLine( transformedPosition, transformedTarget );
6✔
1633
  distance = distanceArea.convertLengthMeasurement( distance, Qgis::DistanceUnit::Meters );
6✔
1634

1635
  return distance;
6✔
1636
}
6✔
1637

1638
qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
1✔
1639
{
1640
  if ( !mapSettings || !targetFeature.isValid() )
1✔
1641
    return -1;
×
1642

1643
  QgsVectorLayer *layer = targetFeature.layer();
1✔
1644
  QgsFeature f = targetFeature.feature();
1✔
1645

1646
  // Only points are supported
1647
  if ( layer->geometryType() != Qgis::GeometryType::Point )
1✔
1648
    return -1;
×
1649

1650
  // Transform gps position to map CRS
1651
  QgsPointXY transformedPosition = transformPoint(
2✔
1652
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
2✔
1653
                                     mapSettings->destinationCrs(),
2✔
1654
                                     mapSettings->transformContext(),
2✔
1655
                                     gpsPoint
1656
                                   );
1✔
1657

1658
  if ( transformedPosition.isEmpty() )
1✔
1659
  {
1660
    return -1;
×
1661
  }
1662

1663
  // Transform target point to map CRS
1664
  QgsPoint target( extractPointFromFeature( targetFeature ) );
1✔
1665
  QgsPointXY transformedTarget = transformPoint(
2✔
1666
                                   targetFeature.layer()->crs(),
2✔
1667
                                   mapSettings->destinationCrs(),
2✔
1668
                                   mapSettings->transformContext(),
2✔
1669
                                   target
1670
                                 );
1✔
1671

1672
  if ( transformedTarget.isEmpty() )
1✔
1673
  {
1674
    return -1;
×
1675
  }
1676

1677
  QgsDistanceArea distanceArea;
1✔
1678
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
1✔
1679

1680
  return distanceArea.bearing( transformedPosition, transformedTarget );
1✔
1681
}
1✔
1682

1683
QString InputUtils::featureTitle( const FeatureLayerPair &pair, QgsProject *project )
×
1684
{
1685
  if ( !project || !pair.isValid() )
×
1686
    return QString();
×
1687

1688
  QString title;
×
1689

1690
  QgsVectorLayer *layer = pair.layer();
×
1691

1692
  // can't use QgsExpressionContextUtils::globalProjectLayerScopes() because it uses QgsProject::instance()
1693
  QList<QgsExpressionContextScope *> scopes;
×
1694
  scopes << QgsExpressionContextUtils::globalScope();
×
1695
  scopes << QgsExpressionContextUtils::projectScope( project );
×
1696
  scopes << QgsExpressionContextUtils::layerScope( layer );
×
1697

1698
  QgsExpressionContext context( scopes );
×
1699
  context.setFeature( pair.feature() );
×
1700
  QgsExpression expr( pair.layer()->displayExpression() );
×
1701
  title = expr.evaluate( &context ).toString();
×
1702

1703
  if ( title.isEmpty() )
×
1704
    title = QStringLiteral( "Feature %1" ).arg( pair.feature().id() );
×
1705

1706
  return title;
×
1707
}
×
1708

1709
FeatureLayerPair InputUtils::createFeatureLayerPair(
1✔
1710
  QgsVectorLayer *layer,
1711
  const QgsGeometry &geometry,
1712
  VariablesManager *variablesmanager,
1713
  QgsExpressionContextScope *additionalScope )
1714
{
1715
  if ( !layer )
1✔
1716
    return FeatureLayerPair();
×
1717

1718
  QgsAttributes attrs( layer->fields().count() );
1✔
1719
  QgsExpressionContext context = layer->createExpressionContext();
1✔
1720

1721
  if ( variablesmanager )
1✔
1722
    context << variablesmanager->positionScope();
×
1723

1724
  if ( additionalScope )
1✔
1725
    context << additionalScope;
1✔
1726

1727
  QgsFeature feat = QgsVectorLayerUtils::createFeature( layer, geometry, attrs.toMap(), &context );
1✔
1728
  return FeatureLayerPair( feat, layer );
1✔
1729
}
1✔
1730

1731
void InputUtils::createEditBuffer( QgsVectorLayer *layer )
×
1732
{
1733
  if ( layer )
×
1734
  {
1735
    if ( !layer->editBuffer() )
×
1736
    {
1737
      layer->startEditing();
×
1738
    }
1739
  }
1740
}
×
1741

1742
FeatureLayerPair InputUtils::changeFeaturePairGeometry( FeatureLayerPair featurePair, const QgsGeometry &geometry )
×
1743
{
1744
  QgsVectorLayer *vlayer = featurePair.layer();
×
1745
  if ( vlayer )
×
1746
  {
1747
    InputUtils::createEditBuffer( vlayer );
×
1748
    QgsGeometry g( geometry );
×
1749
    vlayer->changeGeometry( featurePair.feature().id(), g );
×
1750
    vlayer->triggerRepaint();
×
1751
    QgsFeature f = vlayer->getFeature( featurePair.feature().id() );
×
1752
    return FeatureLayerPair( f, featurePair.layer() );
×
1753
  }
×
1754
  else
1755
  {
1756
    // invalid pair
1757
    return FeatureLayerPair();
×
1758
  }
1759
}
1760

1761
QgsPointXY InputUtils::extractPointFromFeature( const FeatureLayerPair &feature )
11✔
1762
{
1763
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
11✔
1764
    return QgsPointXY();
2✔
1765

1766
  QgsFeature f = feature.feature();
9✔
1767
  const QgsAbstractGeometry *g = f.geometry().constGet();
9✔
1768

1769
  return QgsPoint( dynamic_cast< const QgsPoint * >( g )->toQPointF() );
18✔
1770
}
9✔
1771

1772
bool InputUtils::isPointLayerFeature( const FeatureLayerPair &feature )
4✔
1773
{
1774
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
4✔
1775
    return false;
3✔
1776
  const QgsAbstractGeometry *g = feature.feature().geometry().constGet();
1✔
1777
  const QgsPoint *point = dynamic_cast< const QgsPoint * >( g );
1✔
1778
  return point != nullptr;
1✔
1779
}
1780

1781
void InputUtils::zoomToProject( QgsProject *qgsProject, InputMapSettings *mapSettings )
×
1782
{
1783
  if ( !qgsProject || !mapSettings )
×
1784
  {
1785
    qDebug() << "Cannot zoom to extent, MapSettings or QgsProject is not defined";
×
1786
    return;
×
1787
  }
1788
  QgsRectangle extent;
×
1789

1790
  QgsProjectViewSettings *viewSettings = qgsProject->viewSettings();
×
1791
  extent = viewSettings->presetFullExtent();
×
1792
  if ( extent.isNull() )
×
1793
  {
1794
    bool hasWMS;
1795
    QStringList WMSExtent = qgsProject->readListEntry( "WMSExtent", QStringLiteral( "/" ), QStringList(), &hasWMS );
×
1796

1797
    if ( hasWMS && ( WMSExtent.length() == 4 ) )
×
1798
    {
1799
      extent.set( WMSExtent[0].toDouble(), WMSExtent[1].toDouble(), WMSExtent[2].toDouble(), WMSExtent[3].toDouble() );
×
1800
    }
1801
    else // set layers extent
1802
    {
1803
      const QVector<QgsMapLayer *> layers = qgsProject->layers<QgsMapLayer *>();
×
1804
      for ( const QgsMapLayer *layer : layers )
×
1805
      {
1806
        QgsRectangle layerExtent = mapSettings->mapSettings().layerExtentToOutputExtent( layer, layer->extent() );
×
1807
        extent.combineExtentWith( layerExtent );
×
1808
      }
1809
    }
×
1810
  }
×
1811

1812
  if ( extent.isEmpty() )
×
1813
  {
1814
    extent.grow( qgsProject->crs().isGeographic() ? 0.01 : 1000.0 );
×
1815
  }
1816
  extent.scale( 1.05 );
×
1817
  mapSettings->setExtent( extent );
×
1818
}
1819

1820
QString InputUtils::loadIconFromLayer( QgsMapLayer *layer )
18✔
1821
{
1822
  if ( !layer )
18✔
1823
    return QString();
×
1824

1825
  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
18✔
1826

1827
  if ( vectorLayer )
18✔
1828
  {
1829
    Qgis::GeometryType geometry = vectorLayer->geometryType();
17✔
1830
    return iconFromGeometry( geometry );
17✔
1831
  }
1832
  else
1833
    return QString( "qrc:/mIconRasterLayer.svg" );
1✔
1834
}
1835

1836
QString InputUtils::loadIconFromFeature( QgsFeature feature )
4✔
1837
{
1838
  return iconFromGeometry( feature.geometry().type() );
8✔
1839
}
1840

1841
QString InputUtils::iconFromGeometry( const Qgis::GeometryType &geometry )
21✔
1842
{
1843
  switch ( geometry )
21✔
1844
  {
1845
    case Qgis::GeometryType::Point: return QString( "qrc:/mIconPointLayer.svg" );
6✔
1846
    case Qgis::GeometryType::Line: return QString( "qrc:/mIconLineLayer.svg" );
6✔
1847
    case Qgis::GeometryType::Polygon: return QString( "qrc:/mIconPolygonLayer.svg" );
6✔
1848
    default: return QString( "qrc:/mIconTableLayer.svg" );
3✔
1849
  }
1850
}
1851

1852
bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
×
1853
{
1854
  int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
×
1855
  return ImageUtils::rescale( path, quality );
×
1856
}
1857

1858
QgsGeometry InputUtils::createGeometryForLayer( QgsVectorLayer *layer )
36✔
1859
{
1860
  QgsGeometry geometry;
36✔
1861

1862
  if ( !layer )
36✔
1863
  {
1864
    return geometry;
×
1865
  }
1866

1867
  bool isMulti = QgsWkbTypes::isMultiType( layer->wkbType() );
36✔
1868

1869
  switch ( layer->geometryType() )
36✔
1870
  {
1871
    case Qgis::GeometryType::Point:
15✔
1872
    {
1873
      if ( isMulti )
15✔
1874
      {
1875
        QgsMultiPoint *multiPoint = new QgsMultiPoint();
3✔
1876
        geometry.set( multiPoint );
3✔
1877
      }
1878
      else
1879
      {
1880
        QgsPoint *point = new QgsPoint();
12✔
1881
        geometry.set( point );
12✔
1882
      }
1883
      break;
15✔
1884
    }
1885

1886
    case Qgis::GeometryType::Line:
10✔
1887
    {
1888
      if ( isMulti )
10✔
1889
      {
1890
        QgsMultiLineString *multiLine = new QgsMultiLineString();
4✔
1891
        geometry.set( multiLine );
4✔
1892
      }
1893
      else
1894
      {
1895
        QgsLineString *line = new QgsLineString();
6✔
1896
        geometry.set( line );
6✔
1897
      }
1898
      break;
10✔
1899
    }
1900

1901
    case Qgis::GeometryType::Polygon:
11✔
1902
    {
1903
      if ( isMulti )
11✔
1904
      {
1905
        QgsLineString *line = new QgsLineString();
2✔
1906
        QgsPolygon *polygon = new QgsPolygon( line );
2✔
1907
        QgsMultiPolygon *multiPolygon = new QgsMultiPolygon();
2✔
1908
        multiPolygon->addGeometry( polygon );
2✔
1909
        geometry.set( multiPolygon );
2✔
1910
      }
1911
      else
1912
      {
1913
        QgsLineString *line = new QgsLineString();
9✔
1914
        QgsPolygon *polygon = new QgsPolygon( line );
9✔
1915
        geometry.set( polygon );
9✔
1916
      }
1917
      break;
11✔
1918
    }
1919

1920
    default:
×
1921
      break;
×
1922
  }
1923

1924
  if ( QgsWkbTypes::hasZ( layer->wkbType() ) )
36✔
1925
  {
1926
    geometry.get()->addZValue( 0 );
12✔
1927
  }
1928

1929
  if ( QgsWkbTypes::hasM( layer->wkbType() ) )
36✔
1930
  {
1931
    geometry.get()->addMValue( 0 );
7✔
1932
  }
1933

1934
  return geometry;
36✔
1935
}
×
1936

1937
QString InputUtils::invalidGeometryWarning( QgsVectorLayer *layer )
6✔
1938
{
1939
  QString msg;
6✔
1940
  if ( !layer )
6✔
1941
  {
1942
    return msg;
×
1943
  }
1944

1945
  int nPoints = 1;
6✔
1946
  if ( layer->geometryType() == Qgis::GeometryType::Line )
6✔
1947
  {
1948
    nPoints = 2;
2✔
1949
  }
1950
  else if ( layer->geometryType() == Qgis::GeometryType::Polygon )
4✔
1951
  {
1952
    nPoints = 3;
2✔
1953
  }
1954

1955
  if ( QgsWkbTypes::isMultiType( layer->wkbType() ) )
6✔
1956
  {
1957
    return tr( "You need to add at least %1 point(s) to every part." ).arg( nPoints );
3✔
1958
  }
1959
  else
1960
  {
1961
    return tr( "You need to add at least %1 point(s)." ).arg( nPoints );
3✔
1962
  }
1963
}
6✔
1964

1965
void InputUtils::updateFeature( const FeatureLayerPair &pair )
×
1966
{
1967
  if ( !pair.layer() )
×
1968
  {
1969
    return;
×
1970
  }
1971

1972
  if ( !pair.feature().isValid() )
×
1973
  {
1974
    return;
×
1975
  }
1976

1977
  if ( !pair.layer()->isEditable() )
×
1978
  {
1979
    pair.layer()->startEditing();
×
1980
  }
1981

1982
  QgsFeature f( pair.feature() );
×
1983
  pair.layer()->updateFeature( f );
×
1984
  pair.layer()->commitChanges();
×
1985
  pair.layer()->triggerRepaint();
×
1986
}
×
1987

1988
QString InputUtils::imageGalleryLocation()
×
1989
{
1990
  QStringList galleryPaths = QStandardPaths::standardLocations( QStandardPaths::PicturesLocation );
×
1991

1992
  if ( galleryPaths.isEmpty() )
×
1993
  {
1994
    CoreUtils::log( QStringLiteral( "Image Picker" ), QStringLiteral( "Could not find standard path to image gallery" ) );
×
1995
    return QString();
×
1996
  }
1997

1998
  return galleryPaths.last();
×
1999
}
×
2000

2001
QString InputUtils::layerAttribution( QgsMapLayer *layer )
2✔
2002
{
2003
  if ( !layer || !layer->isValid() )
2✔
2004
  {
2005
    return QString();
×
2006
  }
2007

2008
  QStringList rights = layer->metadata().rights();
2✔
2009
  if ( !rights.isEmpty() )
2✔
2010
  {
2011
    return rights.join( QStringLiteral( ", " ) );
2✔
2012
  }
2013

2014
  return QString();
1✔
2015
}
2✔
2016

2017
const double PROFILER_THRESHOLD = 0.001;
2018
static double qgsRuntimeProfilerExtractModelAsText( QStringList &lines, const QString &group, const QModelIndex &parent, int level )
×
2019
{
2020
  double total_elapsed = 0.0;
×
2021

2022
  const int rc = QgsApplication::profiler()->rowCount( parent );
×
2023
  for ( int r = 0; r < rc; r++ )
×
2024
  {
2025
    QModelIndex rowIndex = QgsApplication::profiler()->index( r, 0, parent );
×
2026
    if ( QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Group ).toString() != group )
×
2027
      continue;
×
2028
    bool ok;
2029
    double elapsed = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Elapsed ).toDouble( &ok );
×
2030
    if ( !ok )
×
2031
      elapsed = 0.0;
×
2032
    total_elapsed += elapsed;
×
2033

2034
    if ( elapsed > PROFILER_THRESHOLD )
×
2035
    {
2036
      QString name = QgsApplication::profiler()->data( rowIndex, QgsRuntimeProfilerNode::Name ).toString();
×
2037
      lines << QStringLiteral( "  %1 %2: %3 sec" ).arg( QStringLiteral( ">" ).repeated( level + 1 ),  name, QString::number( elapsed, 'f', 3 ) );
×
2038
    }
×
2039
    total_elapsed += qgsRuntimeProfilerExtractModelAsText( lines, group, rowIndex, level + 1 );
×
2040
  }
2041
  return total_elapsed;
×
2042
}
2043

2044
QVector<QString> InputUtils::qgisProfilerLog()
×
2045
{
2046
  QVector<QString> lines;
×
2047
  const QString project = QgsProject::instance()->fileName();
×
2048

2049
  if ( !project.isEmpty() )
×
2050
  {
2051
    lines << QStringLiteral( "QgsProject filename: %1" ).arg( project );
×
2052
  }
2053

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

2056
  const auto groups = QgsApplication::profiler()->groups();
×
2057
  for ( const QString &g : groups )
×
2058
  {
2059
    QVector<QString> groupLines;
×
2060
    double elapsed = qgsRuntimeProfilerExtractModelAsText( groupLines, g, QModelIndex(), 0 );
×
2061
    if ( elapsed > PROFILER_THRESHOLD )
×
2062
    {
2063
      lines << QStringLiteral( "  %1: total %2 sec" ).arg( g, QString::number( elapsed, 'f', 3 ) );
×
2064
      lines << groupLines;
×
2065
    }
2066
  }
×
2067
  return lines;
×
2068
}
×
2069

2070
QList<QgsPoint> InputUtils::parsePositionUpdates( const QString &data )
8✔
2071
{
2072
  QList<QgsPoint> parsedUpdates;
8✔
2073
  QStringList positions = data.split( '\n', Qt::SkipEmptyParts );
8✔
2074

2075
  if ( positions.isEmpty() )
8✔
2076
  {
2077
    return parsedUpdates;
2✔
2078
  }
2079

2080
  for ( int ix = 0; ix < positions.size(); ix++ )
14✔
2081
  {
2082
    QStringList coordinates = positions[ix].split( ' ', Qt::SkipEmptyParts );
8✔
2083

2084
    if ( coordinates.size() != 4 )
8✔
2085
    {
2086
      continue;
4✔
2087
    }
2088

2089
    QgsPoint geop;
4✔
2090
    geop.setX( coordinates[0].toDouble() ); // long
4✔
2091
    geop.setY( coordinates[1].toDouble() ); // lat
4✔
2092
    geop.setZ( coordinates[2].toDouble() ); // alt
4✔
2093
    geop.setM( coordinates[3].toDouble() ); // UTC time in secs
4✔
2094
    parsedUpdates << geop;
4✔
2095
  }
8✔
2096

2097
  return parsedUpdates;
6✔
2098
}
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